Go言語で実装したAWS Lambda関数をVSCodeでデバッグ実行できるようになるまでの手順を書いてみようと思います。
Lambda関数をローカル環境でデバッグ実行できるようにすることで、AWS上でのデプロイやテストをしなくても、開発を円滑に進めることができます。
また、AWS Lambdaをローカル環境で実行するにはSAM(Serverless Application Model)を使用します。実行時にデバッグポートを割り当てらることができ、VSCodeからデバッガをアタッチしてデバッグ実行を実現します。
- SAMとは
- 前提
- AWS SAM CLIインストール
- 実行するGoプロジェクトを作成する
- 試しにSAM CLIを使って実行してみる
- ビルドする
- 再度SAM CLIで実行してみる
- VSCodeでデバッグ実行できるようにする
- サンプルプロジェクト
- 参考記事
SAMとは
SAM(Serverless Application Model)は、AWSが提供するサーバレスアプリケーションを開発、デプロイ、管理するためのフレームワークです。SAMを使用することで、AWS LambdaやAmazon API Gatewayなどのサーバレスサービスを使用したアプリケーションを簡単に構築したり、管理したりすることができます。
また、SAMはAWS CloudFormationのサブセットであり、CloudFormationのテンプレートを使用してサーバレスアプリケーションを定義します。
前提
※ 本記事で紹介している情報は2022年12月時点のものであり、閲覧時には古くなっている場合があります。最新の情報に関してはAWSの公式ドキュメントにしてください。
AWS SAM CLIインストール
ローカル環境でLambda関数を実行できるようにするために、公式ドキュメントを参考にAWS SAM CLIをインストールします。
$ brew tap aws/tap $ brew install aws-sam-cli
正常にSAM CLIがインストールされていることを確認します。
$ sam --version SAM CLI, version 1.66.0
実行するGoプロジェクトを作成する
まずディレクトリを作り、その中にデバッグ実行するLamba関数である main.go
、SAMテンプレートファイル、go.mod
を作成します。
$ mkdir lambda-debug-sample $ cd lambda-debug-sample $ touch main.go $ touch template.yml $ go mod init lambda-debug-sample
Lambda関数(Go言語)の作成
main.go
にAWS公式のサンプルコードをそのまま書いてみます。
MyEvent構造体の値のNameを出力するだけのシンプルなコードです。
package main import ( "fmt" "context" "github.com/aws/aws-lambda-go/lambda" ) type MyEvent struct { Name string `json:"name"` } func HandleRequest(ctx context.Context, name MyEvent) (string, error) { return fmt.Sprintf("Hello %s!", name.Name ), nil } func main() { lambda.Start(HandleRequest) }
テンプレートファイルの作成
作成した main.go
をLambda関数として動かすためのシンプルなテンプレートファイルを記載してみます。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > lambda-debug-sample # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 5 Resources: HelloGopher: # リソース名を指定 Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: ./ Handler: hello-world Runtime: go1.x Architectures: - x86_64
Resourcesの部分で、HelloGopher
というリソース名を指定しています。他の箇所から参照する時に、このリソース名を使用できます。
Typeに AWS::Serverless::Function
を指定し、CloudFormationがLambdaを生成できるようにします。
また、Propertiesでは以下の4つを指定しています。
- CodeUri
- コードへのパス
- Handler
- Lambdaを実行するバイナリ
- Runtime
- Lambdaの実行ランタイム
- Architectures
※その他のオプションに関しては公式ドキュメントをご確認ください。
試しにSAM CLIを使って実行してみる
今の状態で作ったLambda関数を実行してみます。
$ sam local invoke Invoking hello-world (go1.x) Skip pulling image and use local one: public.ecr.aws/sam/emulation-go1.x:rapid-1.66.0-x86_64. START RequestId: b35b26b2-bfa1-4973-9c55-188775457e2d Version: $LATEST fork/exec /var/task/hello-world: no such file or directory: PathError null END RequestId: b35b26b2-bfa1-4973-9c55-188775457e2d
すると、template.yml
ファイルのHandlerで指定したバイナリファイル hello-world
が存在しないので、PathError が発生することを確認できました。
ビルドする
ビルドすると.aws-sam
にビルド結果が出力されます。
$ go mod tidy $ sam build Running GoModulesBuilder:Build Build Succeeded Built Artifacts : .aws-sam/build Built Template : .aws-sam/build/template.yaml
再度SAM CLIで実行してみる
実行すると Hello !
と出力され、正常に実行できることを確認できます。
$ sam local invoke Invoking hello-world (go1.x) Skip pulling image and use local one: public.ecr.aws/sam/emulation-go1.x:rapid-1.66.0-x86_64. START RequestId: 16b172ed-6023-4ee8-999b-89d907784099 Version: $LATEST END RequestId: 16b172ed-6023-4ee8-999b-89d907784099 REPORT RequestId: 16b172ed-6023-4ee8-999b-89d907784099 Init Duration: 0.20 ms Duration: 191.46 msBilled Duration: 192 ms Memory Size: 128 MB Max Memory Used: 128 MB "Hello !"
イベントを渡して実行してみる
ローカル環境でLambda関数が動いたので、次は色々なやり方でLambda関数を起動してみようと思います。
まずはイベントを作成します。イベントは本来S3やSQSで作成・更新のイベントが発生した際に、Lambda関数が呼ばれる際に渡されるものですが、今回は仮のシンプルなデータを作成します。
$ touch event.json
{ "name": "HogeHoge" }
作成したイベント event.json
を指定して関数を実行すると、Hello HogeHoge!
と表示され、event.json
からの値を確認できます。
$ sam local invoke -e event.json Invoking hello-world (go1.x) Skip pulling image and use local one: public.ecr.aws/sam/emulation-go1.x:rapid-1.66.0-x86_64. START RequestId: a54238e1-41d4-4c15-b6aa-c3b36f20c426 Version: $LATEST END RequestId: a54238e1-41d4-4c15-b6aa-c3b36f20c426 REPORT RequestId: a54238e1-41d4-4c15-b6aa-c3b36f20c426 Init Duration: 0.24 ms Duration: 201.85 msBilled Duration: 202 ms Memory Size: 128 MB Max Memory Used: 128 MB "Hello HogeHoge!"
リソース名を指定して実行してみる
リソース名を指定して起動することもできます。実行内容は同じです。
$ sam local invoke HelloGopher -e event.json
AWS Lambda をエミュレートするローカルエンドポイントを起動して実行してみる
次にsam local start-lambda で、ローカルエンドポイントを起動して実行してみます。
まずは、ローカルエンドポイントの起動からです。デフォルトURLは http://127.0.0.1:3001
です。
$ sam local start-lambda Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint. 2022-12-31 17:28:26 * Running on http://127.0.0.1:3001/ (Press CTRL+C to quit)
$ aws lambda invoke --function-name HelloGopher --endpoint "http://127.0.0.1:3001" --payload '{"name": "HogeHoge"}' --cli-binary-format raw-in-base64-out response.json { "StatusCode": 200 }
response.json
にLambdaの実行結果が出力されます。
"Hello HogeHoge!"
VSCodeでデバッグ実行できるようにする
delveのインストール
Goのデバッグツールである delve
をインストールします。
$ GOARCH=amd64 GOOS=linux go install github.com/go-delve/delve/cmd/dlv@latest
Lambda関数及び delve
が実行されるのはLambdaコンテナ内(Amazon Linux)なので、GOOSに linux
を指定し、インストールします。(参考github issueコメント).
デバッガーパスを指定してLambda関数を実行
delve
が $GOPATH/bin/linux_amd64
されるので、このパスをデバッガーパスに指定、デバッグポートに 8099
を指定して、Lambda関数を実行します。
API server (8099
ポートリッスン)で起動することが確認できます。
$ sam local invoke HelloGopher -e event.json -d 8099 --debugger-path=$GOPATH/bin/linux_amd64 --debug-args="-delveAPI=2" Invoking hello-world (go1.x) Skip pulling image and use local one: public.ecr.aws/sam/emulation-go1.x:rapid-1.66.0-x86_64. START RequestId: 909cfcd8-e0cc-448c-8b55-e8afe022b5b8 Version: $LATEST API server listening at: [::]:8099 2022-12-31T08:47:59Z warning layer=rpc Listening for remote connections (connections are not authenticated nor encrypted) 2022-12-31T08:47:59Z info layer=debugger launching process with args: [/var/task/hello-world]
Visual Studio Codeからアタッチする
最後にVSCodeからアタッチするための情報を設定します。
プロジェクト( main.go
と同じところ)に以下の .vscode/launch.json
を作成します。
{ "configurations": [ { "name": "SAMでデバッグ", "type": "go", "request": "attach", "mode": "remote", "port": 8099, "host": "localhost", "env": {}, "args": [] } ] }
main.go
をVSCodeで開き、ブレイクポイントを設定したら、実行とデバッグを押します。
ブレイクポイントに止まることを確認できます。
1ステップでデバッグ実行できるようにする
以下の2ステップでデバッグ実行を行いましたが、launch.json
の書き方を変えると1ステップでできるようになり、デバッグポートを指定する必要がなくなります。
- デバッガーパスを指定してLambda関数を実行
- Visual Studio Codeからアタッチする
先ほどの launch.json
を以下のように変更し、実行とデバッグから2つ目の方を指定すると同様にブレイクポイントに止まることが確認できます。
{ "configurations": [ { "name": "SAMでデバッグ", "type": "go", "request": "attach", "mode": "remote", "port": 8099, "host": "localhost", "env": {}, "args": [] }, // ↓↓↓ここの部分を追記↓↓↓ { "name": "SAMでデバッグ2", "type": "aws-sam", "request": "direct-invoke", "invokeTarget": { "target": "template", "templatePath": "${workspaceFolder}/template.yml", "logicalId": "HelloGopher" }, "lambda": { "payload": { "path": "event.json" // "json": { "name": "HogeHoge" }, }, "environmentVariables": {} } }, // ↑↑↑ここの部分を追記↑↑↑ ] }