Serverless Framework を使い、AWS の API Gateway から Lambda 関数を非同期で呼び出す方法を調べてみました。
目次
背景
最近、クライアントからのイベントを受け取るエンドポイントを設置する機会があり AWS の API Gateway + Lambda を使用しました。
サービスの品質アップやクライアントの仕様によって、いち早くレスポンスを返したいケースがあります。
具体的な例としてはこちらです。 blog.linkode.co.jp
Slack のイベントを受け取り、3秒以内にレスポンスをするために Lambda 関数から別の Lambda 関数を非同期で呼び出す方法を紹介していますが、今回は API Gateway から非同期で Lambda 関数を呼び出す方法を調査したので、サービス構築の一例を紹介します。
環境
API Gateway、Lambda 関数のデプロイに Serverless Framework を使用しています。
Serverless Framework はNode.jsで作られたCLIツールで AWS Lambda、Azure Functions、Google Cloud Functions等の Serverless Applicationを構成管理、デプロイするためのツールです。
また、Lambda関数のランタイムとしてGo言語を、goのビルドにmakeを使用しました。
各ツールのインストールや環境構築については割愛します。
プロジェクトの作成
まず、Serverless Flamework のコマンドでプロジェクトのテンプレートを作成します。
Lambdaのgoランタイムテンプレートは aws-go
、aws-go-dep
、aws-go-mod
の3種類から選択できますが、モジュール名を明示的に指定したいので aws-go
を選択しました。
$ sls create --template aws-go
モジュール名を linkode.co.jp/TestAsync
として、プロジェクトを初期化します。
$ go mod init linkode.co.jp/TestAsync
Lambda関数のビルド
フォルダー構成
次に、テンプレートで生成されたファイルとフォルダーを以下のように配置し直します。
./ │ .gitignore │ go.mod │ Makefile │ serverless.yml └─handler └─TestAsync main.go
フォルダー構成はプロジェクトやチームの方針によってさまざまですが、今回の例では TestAsync
というLambda関数名を意識した配置にしています。
main.go
Lambda関数本体は時間のかかる処理を想定して、5秒間のスリープを行います。
package main import ( "log" "time" "github.com/aws/aws-lambda-go/lambda" ) // Handler is lambda handler func Handler() { log.Printf("Start processing.") time.Sleep(time.Second * 5) log.Printf("Process is finished.") } func main() { lambda.Start(Handler) }
Makefile
Makefile は次のように編集します。
.PHONY: build clean deploy
build:
env GOOS=linux go build -ldflags="-s -w" -o bin/TestAsync/app.lambda_handler handler/TestAsync/main.go
clean:
rm -rf ./bin
deploy: clean build
sls deploy --verbose
これで、make
コマンドでビルドが行える状態になりました。
デプロイの設定
API Gateway からの Lambda関数の非同期呼び出しは、serverless.yml
で設定を行います。
全体の内容は次の通りです。
service: testasync frameworkVersion: '>=1.28.0 <2.0.0' provider: name: aws runtime: go1.x stage: dev region: us-east-2 iamRoleStatements: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: - "*" package: individually: true exclude: - ./** functions: TestAsync: package: include: - ./bin/TestAsync/** handler: bin/TestAsync/app.lambda_handler events: - http: method: get path: testasync async: true # 非同期呼び出しを有効化 response: statusCodes: 202: pattern: '' # デフォルトのレスポンス
TestAsync
関数の events
で async: true
(デフォルトはfalse)とすることで、API Gateway からの Lambda の呼び出しが非同期になります。
また、response
は何も指定しない場合、200 OKがデフォルトとなりますが、今回は非同期でLambdaを呼び出すため、202 Acceptedとしています。
ステータスコードのカスタマイズ詳細は Serverless Framework のドキュメントを参照してください。
https://www.serverless.com/framework/docs/providers/aws/events/apigateway#custom-status-codes
あとは、Serverless Framework のコマンド sls deploy
を実行することでデプロイが行なえます。
AWS マネジメントコンソール
余談ですが、マネジメントコンソールからも同じ設定が行なえます。
マネジメントコンソールでの設定は、次の記事が参考になりました。
【AWS】API GatewayからLambdaを非同期で実行する
動作確認
ビルドとデプロイを行い、動作確認をします。
curl
コマンドで、レスポンスのステータスコードを表示させて、time
コマンドでその実行時間を計測します。
※URLのRESTAPIID
の部分は適宜置き換えてください。
$ time curl https://RESTAPIID.execute-api.us-east-2.amazonaws.com/dev/testasync -o /dev/null -w '%{http_code}\n' -s 202 real 0m0.904s user 0m0.016s sys 0m0.016s
202 Acceptedが返却されて、1秒以内にレスポンスされている事がわかります。
一方 Lambda 関数のログを確認すると、5秒の間隔を開けてログが出力されていることがわかりました。
2020/09/30 07:28:53 Start processing. 2020/09/30 07:28:58 Process is finished.
今度は試しに以下のように serverless.yml
の functions
を書き換えて、同期呼び出しを確認してみます。
functions: TestAsync: package: include: - ./bin/TestAsync/** handler: bin/TestAsync/app.lambda_handler events: - http: method: get path: testasync integration: lambda async: false response: statusCodes: 202: pattern: ''
integration: lambda
と async: false
で明示的にLambda非プロキシ統合で同期呼び出し、となるように指定しています。
これをデプロイしてみて、非同期と同様に動作確認してみます。
$ time curl https://RESTAPIID.execute-api.us-east-2.amazonaws.com/dev/testasync -o /dev/null -w '%{http_code}\n' -s 202 real 0m5.802s user 0m0.016s sys 0m0.063s
コマンドの実行全体で5秒以上経過していることがわかります。
まとめ
今回の例では呼び出した Lambda 関数が5秒間スリープするだけでしたが、サーバー側の処理内容にかかわらず同じ構成が使えます。
クライアントに対して即座にレスポンスを返すだけで良いケースでは、シンプルな方法かと思います。