AWS CDKで作成したlambdaをAWS SAMでローカル実行する
背景
serverlessがv4から有償化になるという話を耳にしました。
代替ツールを検討する中でcdkとsamを使った開発が便利そうだったので記事として残しておきます。この記事で行うこと
aws cdkで作成したlambdaをsamを用いてローカルで実行します。
lambdaは関数URLを発行する形で作成します。
cdk、sam、goなどの詳しい解説はこの記事では行いません。公式を参照してください。
今回の内容のgithubレポジトリはこちらです
対象者/所要時間
この記事はcdk、sam、goをこれから扱う方がローカル実行まで到達できることを目指しています。
aws cliやgoを少し触ったことがある方は所要時間は1h程度かと思います。
お気軽に初めてみてください🙂
前提
- aws cli、aws cdk cliがインストールされていること
https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/getting_started.html - aws sam cliがインストールされていること
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/install-sam-cli.html
構成
.
└── backend
├── README.md
├── bin
├── cdk.json
├── cdk.out
├── hello-world
├── jest.config.js
├── lib
├── node_modules
├── package-lock.json
├── package.json
├── test
└── tsconfig.json
cdkプロジェクトのセットアップ
backendディレクトリで下記を実行しcdkプロジェクトをセットアップします。
cdk init app --language typescript
backendのstackを修正
backendのstackに関数URLを発行するlambdaを追加します。
handlerにはGoで作成したmain.goをビルドした実行ファイル名を記載しています。
codeには実行ファイルが配置してあるパスを記載しています。
import * as path from "node:path";
import * as cdk from 'aws-cdk-lib';
import * as lambda from "aws-cdk-lib/aws-lambda";
import { Construct } from 'constructs';
export class BackendStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
+ const helloWorld = new lambda.Function(this, "helloWorld", {
+ runtime: lambda.Runtime.PROVIDED_AL2023,
+ handler: "bootstrap",
+ code: lambda.Code.fromAsset(path.join(__dirname, "../hello-world"))
+ });
+ helloWorld.addFunctionUrl({
+ authType: lambda.FunctionUrlAuthType.NONE,
+ cors: {
+ allowedOrigins: ["*"],
+ allowedHeaders: ["*"],
+ allowedMethods: [lambda.HttpMethod.GET],
+ }
+ })
}
}
lambdaの処理を実装
hello-worldディレクトリを作成し、Goプロジェクトを初期化します。
go mod init main
main.goを作成し下記を実装します。
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type Response struct {
Message string `json:"string"`
}
func handler(ctx context.Context, event events.LambdaFunctionURLRequest) (events.LambdaFunctionURLResponse, error) {
msg := "Hello world"
fmt.Println(msg)
responseBody, err := json.Marshal(Response{Message: msg})
return events.LambdaFunctionURLResponse{StatusCode: 200, Body: string(responseBody)}, err
}
func main() {
lambda.Start(handler)
}
moduleの依存関係を解決します。
go mod tidy
lambdaで実行する処理をbuild
hello-worldディレクトリで、作成したmain.goをビルドします。
GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap main.go
lamdaで実行する環境に合わせてGOOS=linux GOARCH=amd64
を指定しています。
-
GOOS
の指定が抜けていた😖
私はintel macで開発していたため、GOOSを指定しなければ、GOOSが異なるためローカル実行の際に下記のエラーが出力されました。START RequestId: 93808cb6-cbb5-45cc-9e15-a12c200a0004 Version: $LATEST 12 Aug 2024 14:20:38,283 [ERROR] (rapid) Init failed InvokeID= error=fork/exec /var/task/bootstrap: exec format error 12 Aug 2024 14:20:38,288 [ERROR] (rapid) Invoke failed error=fork/exec /var/task/bootstrap: exec format error InvokeID=6779a19e-3060-4b90-a449-76a935ad05ea 12 Aug 2024 14:20:38,288 [ERROR] (rapid) Invoke DONE failed: Runtime.InvalidEntrypoint
-
ビルドしたファイル名が
bootstrap
じゃないといけなかった😖
runtimeの制約のようです。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-handler.html#golang-handler-naming異なる名前にすると下記のようなエラーが出力されました。
START RequestId: 1c996e00-0d0d-4d56-bc01-06ffd24b7944 Version: $LATEST 12 Aug 2024 14:22:31,987 [ERROR] (rapid) Init failed error=fork/exec /var/task/bootstrap: exec format error InvokeID= 12 Aug 2024 14:22:31,995 [ERROR] (rapid) Invoke failed error=fork/exec /var/task/bootstrap: exec format error InvokeID=764531af-4c9b-4690-a7c2-5a48bc69078d 12 Aug 2024 14:22:31,995 [ERROR] (rapid) Invoke DONE failed: Runtime.InvalidEntrypoint
templateを作成
backendディレクトリで、templateを作成します
cdk synth --no-staging
samを使ってlocalで実行するためであれば、cdk synth --no-staging
でOKです。
--no-staging
をつけると、cdk.out/asset.XXXX
が生成されなくなります。
もちろんcdk synth
でも同様にローカル実行は可能です。
ローカルで実行
backendディレクトリで実行します。
lambdaを一度だけ実行する
sam local invoke helloWorld -t ./cdk.out/BackendStack.template.json
helloWorld
はstackで指定したlambda関数名です。
-t
はtemplate.jsonへのパスです。
リクエストを待ち受ける
コマンドを実行した際に関数URLのlmabdaを実行するのではなく、ローカルでリクエストを待ち受けたままの状態にしたいこともありますよね。
そのような場合は下記でリクエストを待ち受ける状態にできます。
sam local start-lambda -t ./cdk.out/BackendStack.template.json
リクエストを待ち受けた状態で、curl等でリクエストを送ることができます。
curl "http://localhost:3001/2015-03-31/functions/helloWorld/invocations" -d '{}'
awsコマンドでも可能です。
aws lambda invoke --function-name "helloWorld" --endpoint-url "http://127.0.0.1:3001" --no-verify-ssl out.txt --profile ${profile}
上記のコマンドを実行するとレスポンス結果が書き込まれたout.txt
が生成されます。
お疲れ様でした!🎉🎉🎉
所感
stackの作成やローカル実行が今の所スムーズに実行できたので導入のハードルは低く感じています。
実際にどのようなstackが作成されるかもcdk.outのtemplateに出力されるのでデプロイ前などに事前に確認することもできそうです。
AWSのサービスを作成する時には良さそうです。
このまま導入してみてデプロイ周りも含め、もう少し使用感を確かめたいと思います。
Discussion