概要
AWS Lambdaがコンテナイメージをサポートしました。
コンテナイメージ作成には、AWSの提供しているベースイメージを使うのが手っ取り早いです。
が、2021/01/15現在、Java15は提供されていません。
私の参画しているプロジェクトでは現在Java15を採用しており、
11だと色々めんどくさいので15ベースでコンテナイメージを作れ! と言われ、
俺には無理だろ...と思いながら色々調べてやったら動いたので、共有します。
ソースコード
早速ですが成果物としてのソースコードを公開します。
多分動くと思います。
200 OK
を返すだけのサンプルです。
intx24/java-15-lambda-container
Handlerの処理はほとんど awsdocs/aws-lambda-developer-guideのサンプル から拝借しています。
変更点として、Java15対応を確認するため、不要なswitch式を付け足しました。
どうやったか
1. まずJava11ベースイメージで動かしてみる
Java11のベースイメージを元に、まず動かしてみます。
ありがたいことに関連記事があったため、参考にして丸パクリし、Java11ベースのLambdaイメージを作成しました。
AWS LambdaのコンテナサポートをJavaで試してみた。
2. Java11ベースイメージDockerfileを見てみる
「Java11ベースイメージのDockerfileを参考にしてみましょう」と上司に言われたので、見てみます。
よくわからない tar.xz
を大量にADDしていることがわかります。
git lfs pull
することで tar.xz
ファイルが解凍可能になります。(最初これがわからなくて詰んだと思った)
以下、理解していったことを書いていきます
lambda-entrypoint.sh
DockerfileのENTRYPOINTです。
どうやら /var/runtime/bootstrap
が実行されるらしいです。
${AWS_LAMBDA_RUNTIME_API}
がない場合(多分ローカル環境)では エミュレータを使ってローカル実行を可能にしているっぽい。
エミュレータに関しては、Runtime support for Lambda container images
からダウンロード出来るので、これをローカルからコンテナにCOPYすれば良さそうです。
#!/bin/sh
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
if [ $# -ne 1 ]; then
echo "entrypoint requires the handler name to be the first argument" 1>&2
exit 142
fi
export _HANDLER="$1"
RUNTIME_ENTRYPOINT=/var/runtime/bootstrap
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
exec /usr/local/bin/aws-lambda-rie $RUNTIME_ENTRYPOINT
else
exec $RUNTIME_ENTRYPOINT
fi
/var/runtime/bootstrap
よくわからないファイルでした。
コンテナにコピペしても動かなかった & 中身がブラックボックス過ぎと判断したので、代替手段を探します。
どうやらカスタムランタイムを作る手順でbootstrapを作ればいけるらしい?
カスタムランタイムについて調べつつ、無理だから諦めようと思った頃に、
aws-lambda-java-runtime-interface-client でLambda関数が実行できることを知りました。
このコマンドを aws-lambda-rie
の引数にすれば良さそうです。
3. 実装してみる
Dockerfile
Java15が入ってるAmazonLinux2のイメージがあるのでこれをベースにします。
後述する、自作の lambda-entrypoint.sh
をENTRYPOINTとして,
Lambda関数 example.Handler::handleRequest
を渡します。
FROM amazoncorretto:15
# set environment variables
ENV CLASSPATH /var/task/*
WORKDIR /var/task
# copy lambda execution files
COPY aws-lambda-rie /usr/local/bin/aws-lambda-rie
COPY lambda-entrypoint.sh /lambda-entrypoint.sh
# Copy function code
COPY build/libs/java-15-lambda-container-1.0-SNAPSHOT-all.jar /var/task/
# Set the Entrypoint
ENTRYPOINT ["/lambda-entrypoint.sh"]
# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "example.Handler::handleRequest" ]
lambda-entrypoint.sh
aws-lambda-java-runtime-interface-client
を実行します。
$HANDLER_NAME
には、 Dockerfileの CMD
で指定したLambda関数が渡されます。
aws-lambda-java-runtime-interface-client
の README通りに java -cp ./* com.amazonaws.services.lambda.runtime.api.client.AWSLambda example.App::sayHello
を引数として渡すと、
aws-lambda-rie
が java
を bootstrap
のあるパスだと解釈して死ぬため、/usr/bin/java
に置き換えます。
また、ここではクラスパスをDockerfile内で環境変数として指定済みです。
#!/bin/sh
if [ $# -ne 1 ]; then
echo "entrypoint requires the handler name to be the first argument" 1>&2
exit 142
fi
HANDLER_NAME=$1
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
# local environment
exec /usr/local/bin/aws-lambda-rie \
/usr/bin/java \
com.amazonaws.services.lambda.runtime.api.client.AWSLambda "$HANDLER_NAME"
else
exec /usr/bin/java \
com.amazonaws.services.lambda.runtime.api.client.AWSLambda "$HANDLER_NAME"
fi
/var/runtime/bootstrap
lambda-entrypoint.sh
内に統合したので不要となりました。
苦労したこと
javaのビルド周りがわからん
java自体の経験が1年ほど + intelliJ経由で触ってた + プロトタイプが出来ているプロジェクトに途中参画した 人間なので、
javaはgradleタスク実行したらとりあえず動くぐらいの認識でした。
そもそもクラスパスを通さなければ実行できないということも知らず, Class Not Found
エラーで半日ほど消耗しました。
同じ原因で、カスタムランタイムのビルドについても1分で諦めました。
AWS提供のベースイメージをrun -it
オプションでコンテナを探索できない
最初は ENTRYPOINT
の概念すら知らなかったため、
各種ファイルを確認するため、 run -it
でコンテナを探索しようと思ったら出来ず困ってました。
結果的には docker exec
経由で cat
したり、
IntelliJのdocker のFilesタブで確認していました。
まとめ
今回、Lambda Container Image を Java 15ベースで作ってみて、一応動くところまで出来ました。
調査・実装含めて4日ほど作業にあてましたが、
まだまだ知るべきことが多くあるなと思わされる経験でした。