Go Lambda + aws cdk でLambda実行エラー
最近コードを書く機会が極端に減ってしまっている(読む機会はたくさんある)ので、業務外で会社の人たちがたくさん公開しているLambdaの資料を見ながら、お酒飲みながら雑なコードを書いてなるべく筋トレしています(圧倒的に記述量が足りないので色々忘れる速度の方が早いのが悩み)
しょうもない話です
少し考えればわかることでしたが。以下しょうもない経緯の話なのでここに結論を書いておきます。
aws-cdk はほぼ関係ありません。
macで単純に go build
したバイナリをLambdaとして登録すると「当然アーキテクチャやらOSが違うからまともに動かないので、ちゃんとアーキテクチャやOSを指定してターゲットに合わせてビルドしたものを登録しようね」という話でした。
$ GOARCH=amd64 GOOS=linux go build -o bin/main
これを指定しないと以下のエラーが出力され、何をやってもLambdaが起動しません。
{ "errorMessage": "fork/exec /var/task/main: exec format error", "errorType": "PathError" }
stackoverflow - exec format error when running AWS Golang Lambda
"exec format error" from SAM Local with Golang on Mac
Qiita - alpineのイメージでAWS Lambda用のgoバイナリをビルドしたらPathErrorになった話
長らく環境に合わせたバイナリを作って云々というのをやっていなかったのもあって、このあたりの考慮がさっぱり抜けてました。
経緯
Lambda を書く際には Typescript が圧倒的に多いようですが、自分の場合なかなか慣れることができておらず、別の言語(大体 Go )を選択することが多いのです。
aws-cdk は TypeScript を書きつつ、Go で Lambda を Deploy した際に変なところでハマったので記録しておきます(多分頭が回っていなかった)
go lambda + cdk の構成
こちらの記事を参考にさせていただきました。
aws cdk でGolangのLambdaをデプロイしてみる
ふむふむ。わかりやすいですね。これなら悩むことなくササッとできそう。
バージョン情報は以下のとおりです。
$ cdk --version 1.51.0 (build 8c2d53c)
基本的に cdk の作り方はいつもどおり。
$ mkdir lambda-go $ cd lambda-go $ cdk init --language=typescript
雛形ができました。
go lambda
Lambda は handers
というディレクトリを作成し、こちらの配下に配置することにしました(このあたり、ちゃんと読まずに適当にやっていることがバレる)
package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/lambda" ) // MyEvent lambda用カスタムEvent type MyEvent struct { Name string `json:"name"` Age int `json:"How old are you?"` } // MyResponse lambda用カスタムレスポンス type MyResponse struct { Message string `json:"Answer:"` } // HandleRequest Lambdaリクエストハンドラー func HandleRequest(ctx context.Context, event MyEvent) (MyResponse, error) { return MyResponse{Message: fmt.Sprintf("%s is %d years old!", event.Name, event.Age)}, nil } func main() { lambda.Start(HandleRequest) }
go の Lambda パッケージは導入済みとしています。必要であれば go get github.com/aws/aws-lambda-go/lambda
して導入しておきます。
参考にさせていただいた記事の通り記載しました。記述した go のコードはビルドしてバイナリに変換します。はい。もう一度いいます。変換します。
$ go build -o bin/main
生成したバイナリは handlers/bin/main
に配置されました。Lambda の準備が完了したので Lambda を配置する Stack のコードを修正します。
lambda-go-stack.ts
@aws-cdk/aws-lambda
を導入しておきます。
import * as cdk from '@aws-cdk/core'; import * as lambda from '@aws-cdk/aws-lambda'; export class LambdaGoStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // The code that defines your stack goes here new lambda.Function(this, 'GoFunction', { runtime: lambda.Runtime.GO_1_X, handler: 'main', code: lambda.Code.asset('./handlers/bin') }); } }
runtime
に GO を指定し、 handler
には main
, code
には変換したバイナリが配置されている相対パスを指定します。
stack を編集した内容を変換する必要があるので、npm run build
を実行すると、jsコードに変換されます。参照した記事にもあるように、tsc;
する際に同時に go build
をしてくれると楽なので package.json
を以下に変更。
"scripts": { "build": "tsc; cd handlers; go build -o bin/main", "watch": "tsc -w", "test": "jest", "cdk": "cdk" },
これで npm run build
した際に同時に go build
も実行してくれるようになりました。ではcdk deployします。
$ cdk deploy --profile hoge LambdaGoStack: deploying... .... arn:aws:cloudfromation:us-east-1:xxxxxxxx
Lambdaをテストするとエラー
簡単なコードだから普通に動くだろうと高を括っていたので、以下のエラーを見て鳩が豆鉄砲を食ったよう。全く想定外。
{ "errorMessage": "fork/exec /var/task/main: exec format error", "errorType": "PathError" }
exec format error なんだかそもそも根本が間違ってそうなエラーメッセージ。
そういえば Go の Lambda ってソースコード上げてるわけじゃないしあたりで気づいた結果、そもそも起動する対象がmacと違うというのに今更気づき、調べてみた結果が冒頭の結論です。
まとめるほどでもないもの
冒頭の結論が全てです。
バイナリを生成するタイプのものは、どこで動かすのかをきちんと意識してやりましょうという話でした。
2020/07/19 追記
GoのLambdaの公式リファレンスにちゃんと書いてあった。
GOOS を linux に設定すると、非 Linux 環境でコンパイルする場合でも、コンパイルされた実行可能ファイルと Go ランタイムとの互換性を確保できます。
ドキュメントを読め。本当にすいませんでした。