AWS Lambda における Go の init() の振る舞い
2021-07-01AWS Lambda にはコールドスタートとウォームスタートの 2 パターンありますが、それぞれのパターンで Go の init() 関数の振る舞いが異なるようなので実際に試してみました。
結論
AWS Lambda のウォームスタート時には Go の init()
関数は実行されません。
手元で確認されたい場合は、下記のリポジトリを clone してお試しください。
michimani/base-of-lambda-container-image | GitHub
試してみる
実際に AWS 上に作った Lambda のコールドスタート/ウォームスタートはこちらでは制御できないので、 Lambda のコンテナイメージをローカルで実行して試します。
試すために用意した Go のコードは下記です。
package main
import (
"log"
"net/http"
runtime "github.com/aws/aws-lambda-go/lambda"
)
type Response struct {
Message string `json:"message"`
StatusCode int `json:"statusCode"`
}
var count int
func handleRequest() (Response, error) {
log.Println("start handler")
defer log.Println("end handler")
count++
message := "Hello AWS Lambda"
for i := 0; i < count; i++ {
message = message + "!"
}
return Response{
Message: message,
StatusCode: http.StatusOK,
}, nil
}
func init() {
count = 0
log.Println("init function called")
}
func main() {
runtime.Start(handleRequest)
}
このコード、実行するとどんなログ出力、レスポンスになるでしょうか。
下記のような Dockerfile を用意してイメージをビルドします。
FROM public.ecr.aws/lambda/provided:al2 as build
# install compiler
RUN yum install -y golang
RUN go env -w GOPROXY=direct
# cache dependencies
ADD go.mod go.sum ./
RUN go mod download
# build
ADD . .
RUN go build -o /main
# copy artifacts to a clean image
FROM public.ecr.aws/lambda/provided:al2
COPY --from=build /main /main
COPY entry.sh /
RUN chmod 755 /entry.sh
ENTRYPOINT [ "/entry.sh" ]
docker build -t base-of-lambda-container-image .
コンテナを起動します。
docker run \
--rm \
-p 9000:8080 \
base-of-lambda-container-image:latest /main
別のターミナルで Lambda を invoke します。
curl "http://localhost:9000/2015-03-31/functions/function/invocations"
すると、コンテナを起動している方のターミナルでは下記のような出力が得られます。
START RequestId: 7910c8d7-1394-455a-b253-64f6322044bc Version: $LATEST
time="2021-07-01T14:34:23.324" level=info msg="extensionsDisabledByLayer(/opt/disable-extensions-jwigqn8j) -> stat /opt/disable-extensions-jwigqn8j: no such file or directory"
time="2021-07-01T14:34:23.324" level=warning msg="Cannot list external agents" error="open /opt/extensions: no such file or directory"
2021/07/01 14:34:23 init function called
2021/07/01 14:34:23 start handler
2021/07/01 14:34:23 end handler
END RequestId: 7910c8d7-1394-455a-b253-64f6322044bc
REPORT RequestId: 7910c8d7-1394-455a-b253-64f6322044bc Init Duration: 0.18 ms Duration: 6.83 ms Billed Duration: 100 ms Memory Size: 3008 MB Max Memory Used: 3008 MB
REPORT
の中に Init Duration
の情報が含まれているので、コールドスタートであることがわかります。 init()
が実行されていることもわかります。
一方、 invoke した方のターミナルでは、レスポンスとして下記の出力が得られます。
{"message":"Hello AWS Lambda!","statusCode":200}
では、続けてもう一度実行するとどうなるでしょうか。
コンテナ側と invoke 側、それぞれの出力は下記のようになります。
START RequestId: 28c51c4b-cd7a-43b7-a2ae-fa8ef9017015 Version: $LATEST
2021/07/01 14:37:01 start handler
2021/07/01 14:37:01 end handler
END RequestId: 28c51c4b-cd7a-43b7-a2ae-fa8ef9017015
REPORT RequestId: 28c51c4b-cd7a-43b7-a2ae-fa8ef9017015 Duration: 1.17 ms Billed Duration: 100 ms Memory Size: 3008 MB Max Memory Used: 3008 MB
REPORT
の中に Init Duration
の情報が含まれておらず、ウォームスタートであることがわかります。そして、 init function called
のログがないため init()
は実行されていないことがわかります。
{"message":"Hello AWS Lambda!!","statusCode":200}
message
の語尾の !
が 2 つになっています。このようになるのは下記が原因です。
init()
が実行されないためcount
の値が 0 で初期化されなかったcount
はグローバル変数 (ハンドラ外で定義した変数) のため、前回実行時の1
が格納されていた
このまま連続で実行していくと !
の数は増えていきます。
$ for i in `seq 5`; do
curl "http://localhost:9000/2015-03-31/functions/function/invocations" && echo "\n"
done
{"message":"Hello AWS Lambda!!!","statusCode":200}
{"message":"Hello AWS Lambda!!!!","statusCode":200}
{"message":"Hello AWS Lambda!!!!!","statusCode":200}
{"message":"Hello AWS Lambda!!!!!!","statusCode":200}
{"message":"Hello AWS Lambda!!!!!!!","statusCode":200}
コンテナを一旦停止して再度起動すれば、最初の一回だけはコールドスタートで実行されます。
まとめ
AWS Lambda のウォームスタート時には Go の init()
関数は実行されません。
実行ごとに初期化したい変数がある場合は、グローバル変数で定義せずにハンドラ内で宣言・初期化する、もしくは beforeHandler()
みたいな初期化用の関数を作って、ハンドラの最初でそれを実行するみたいなことをする必要がありそうです。
下記のリポジトリに今回の挙動を試すことができる一式を置いているので、手元で確認したい場合は clone して試してみてください。
comments powered by Disqus