com4dc’s blog

Javaプログラマーのはずだけど運用してます

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の公式リファレンスにちゃんと書いてあった。

docs.aws.amazon.com

GOOS を linux に設定すると、非 Linux 環境でコンパイルする場合でも、コンパイルされた実行可能ファイルと Go ランタイムとの互換性を確保できます。

ドキュメントを読め。本当にすいませんでした。

参照