フューチャー技術ブログ

AWS LambdaにおけるGo Contextの取り扱い

サーバーレス連載の3記事目です

TIGの伊藤真彦です。

GoでLambdaにデプロイするコードを書くにあたり、aws-lambda-goを利用できます。
その際のtips紹介記事です。

AWS LambdaにおけるGo Contextの取り扱い

main.go
package main

import (
"github.com/aws/aws-lambda-go/lambda"
)

func hello() (string, error) {
return "Hello ƛ!", nil
}

func main() {
// Make the handler available for Remote Procedure Call by AWS Lambda
lambda.Start(hello)
}

aws-lambda-goライブラリのREADMEに記載の通り、importして利用可能になったaws-lambda-go/lambdaStart関数の引数に、アプリケーションコードを記載した関数を渡す形で、
実行するための土台としてのアレコレを抽象化して、アプリケーションコードに注力することが可能になっています。

lambda.Start(func)に渡せる引数funcinterface型になっており、下記の複数種類の形式の関数を渡すことが可能になっています。

func ()
func () error
func (TIn), error
func () (TOut, error)
func (context.Context) error
func (context.Context, TIn) error
func (context.Context) (TOut, error)
func (context.Context, TIn) (TOut, error)

引数としてcontext.Context型を受け取るシグネチャの関数を用いることで、後続処理でcontextを受け取ることが可能です。

LambdaContext型を利用する

contextというと後続のライブラリに受け渡すか、自前の実装によってタイムアウト等を管理するような用途が想定されます。
公式ドキュメントのサンプル実装はこちらです。
「context の呼び出し情報へのアクセス」の章に記載があるように、lambda.Start(func)で実装した関数が受け取るcontext.Contextには、あらかじめいくつかの値が入っています。
これらの値をやり取りするために、aws-lambda-goにはlambdacontextパッケージが用意されています。
これにより、LambdaContext構造体を用いることが可能です。

// LambdaContext is the set of metadata that is passed for every Invoke.
type LambdaContext struct {
AwsRequestID string
InvokedFunctionArn string
Identity CognitoIdentity
ClientContext ClientContext
}

この構造体は、フィールド名の通りAwsLambdaが実行された際の情報を持たせることが可能です。

context.ContextからLambdaContext構造体を復元する

// FromContext returns the LambdaContext value stored in ctx, if any.
func FromContext(ctx context.Context) (*LambdaContext, bool) {
lc, ok := ctx.Value(contextKey).(*LambdaContext)
return lc, ok
}

LambdaContext構造体は、context.Contextを引数に取り、LambdaContext構造体を返す関数FromContextでデータを生成できます。
FromContextを用いたサンプルコードを書いてみました。

main.go
package main

import (
"context"
"log"

"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-lambda-go/lambdacontext"
)

func helloWithContext(ctx context.Context) (string, error) {
lc, ok := lambdacontext.FromContext(ctx)
if ok {
log.Printf("aws_request_id: %v", lc.AwsRequestID)
}
return "Hello ƛ!", nil
}

func main() {
// Make the handler available for Remote Procedure Call by AWS Lambda
lambda.Start(helloWithContext)
}

このような方法で、受け取ったcontextからライブラリがcontextに含めた情報を取得することが可能です。
実際の運用としては、ログ出力の際にprefixにAwsRequestIDを出力するように開発しておき、Amazon CloudWatch Logsに送信されたログから、同一リクエストにおける一連のログ出力を抽出する際に役立てたりしています。

lambdaで開発したAPIの認証認可にAmazon Cognitoを利用している場合は、LambdaContext構造体からCognitoIdentityIDCognitoIdentityPoolIDを取得することができるようになっています。
新しいcontextLambdaContext構造体の情報を詰めるfunc NewContext(parent context.Context, lc *LambdaContext)も用意されています。
このような公式から提供されているユーティリティを見落とさず使いこなしていきたいですね。

なお、Goの実装でLambdaを起動する際にcontextに任意の値を保持して、リクエストのペイロードとして活用するような使い方はできません。
詳しくは過去記事GoでLambdaからLambdaを呼び出すときに気をつけたいポイント6選をご確認ください。
このようなGoでの実装経験、ハマりどころはServerless連載2: AWS Lambda×Goの開発Tipsなど、昨年の連載でも様々な記事が執筆されています。

この機会に合わせてお読みいただければ幸いです。

まとめ

  • AWS lambdaをgoで実装する際にcontext.Contextを受け取るコードを実装できる
  • contextにはリクエストID等の情報が含まれている
  • contextの情報を扱うためのパッケージが用意されている