docker-composeを使ってLambda用のAWSプロファイルを切り替える
Dockerを使ってLambdaをデプロイします。ローカルではAWSの名前付きプロファイルを使って、コードに変更を加えずに、AWSにデプロイする場合は工夫が必要になります。docker-composeのenvironmentを使うとそこをよしなにできました。
目次
はじめに
やりたいこと
- AWS LambdaにDockerイメージからデプロイしたい
- ローカルでLambdaのイメージをテストしたときのみ特定プロファイルを読み込ませたいが、AWS環境上ではデフォルトプロファイルを読み込ませたい
- ローカル限定でアドホックな環境変数を定義するときに、docker-composeのenvironmentでやったら便利だった
作るもの
AWS LambdaからAWS SNSを経由してメールを送信するプログラム
正直この程度だと、DockerでなくLmabda上でコードを直書きしたほうが早いが、もっと複雑なプログラムだとDockerの旨味が出てくるのでサンプル程度に使う。
前提知識
前提知識として、ロールやポリシーを設定した上で、Lambdaにコード直書きしてSNS経由でメール送信できるようにしておきます。以下の記事がわかりやすいです。
AWS Lambdaで遊ぼう #3 LambdaからSNSでメール送信
https://zenn.dev/nakam_aws/articles/6562e07cfae4fd
LambdaのベースのDockerイメージ
LambdaをDockerイメージからデプロイしたい場合、ベースのDockerイメージの選択肢が2つあります。
- AWS公式のlambda/python
https://gallery.ecr.aws/lambda/python
Tag見ればわかりますが、Pythonの場合ものすごい勢いで更新されています、結論から言うとこっちが楽でおすすめです。
- スクラッチビルド
UbuntuのようなLambdaに対応していないDockerイメージを使って、1からビルドする方法です。後で確認しますが、ローカルで動かすためにRIEのインストールや取り回しが必要になるので、ややめんどいです。
今回紹介するdocker-composeを使ったやり方はどちらのベースイメージでも使えます。
AWS公式のlambda/pythonの場合
以下のようなディレクトリ構成にしました。
- app.py
- docker-compose.yaml
- Dockerfile
- requirements.txt
コード
※requirements.txtは何も書いていないので割愛
app.py
import boto3
client = boto3.client('sns')
def lambda_handler(event, context):
params = {
'TopicArn': '<your-sns-arn>',
'Subject': 'Lambda -> SNS送信テスト',
'Message': 'Message\n\nLambda -> SNSでの送信テスト'
}
client.publish(**params)
Lambdaにコードを埋め込む場合と同じです。boto3に特にプロファイル名を指定しないで各サービスのクライアントを起動すると、デフォルトプロファイルが読み込まれるというのがポイントです。
デフォルトプロファイルとは、aws configureをしたときに登録されるプロファイルで、「~/.aws/config」や「~/.aws/credentials」では[default]と書かれています。後で見るように、このデフォルトプロファイルの扱いがトリッキーなので注意が必要です。
Dockefile
FROM public.ecr.aws/lambda/python:3.10.2023.07.13.16
# Install requirements
COPY requirements.txt .
RUN pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"
# Copy function code
COPY app.py ${LAMBDA_TASK_ROOT}
# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "app.lambda_handler" ]
AWS公式のサンプルをほぼコピペです。ベースのイメージ名や、CMDの引数を変えています。app.lambda_handlerのコンマ以降はエントリーポイントの関数名を登録するので適宜変更しましょう。
docker-compose.yaml
services:
lambda:
build: .
image: lambda_email_pre
ports:
- 9000:8080
volumes:
- ~/.aws/:/root/.aws
environment:
- AWS_PROFILE=develop
主にローカルで動かすためのdocker-composeです。environment以外の部分は、docker-composeを使わない書き方だと、以下のようになるはずです。
# ビルド
docker build -t lambda_email_pre .
# 起動
docker run --rm -it -p 9000:8080 -v ~/.aws/:/root/.aws --env AWS_PROFILE=develop lambda_email_pre
長ったらしいのでいちいちコマンドコピペするの面倒くさいですよね。docker-composeだとビルドも起動も簡単です。
# ビルド
docker-compose build
# 起動
docker-compose up
# 終了
docker-compose down
volumeオプションでのローカルからの認証情報のマウントは、Lambdaであっても機能します。このケースでは認証情報が必要なのは、実質的にPythonコードで呼んでいるboto3なので。
Windowsでの実行の注意点
Windowsの場合はWSLを2ウィンドウ立ち上げておきます。AWS公式にあるとおり、curlを実行します。
curl http://127.0.0.1:9000/2015-03-31/functions/function/invocations -s -d '{}'
コマンドプロンプトで実行しようとすると、以下のようなエラーになります。これは実装がおかしいのではなく、コマンドプロンプトの特有のエラーなので、WSLで実行すれば正常に実行できます。
curl http://127.0.0.1:9000/2015-03-31/functions/function/invocations -s -d ‘{}’
{“errorMessage”: “Unable to unmarshal input: Expecting value: line 1 column 1 (char 0)”, “errorType”: “Runtime.UnmarshalError”, “requestId”: “f260a31c-bb64-4971-a0d4-8bc3fb6ee48b”, “stackTrace”: []}
ポイント
実際にこれはECRにプッシュ→AWSで実行しても、ローカルで実行しても動きます(デプロイ方法はネット上にいくつも記事あるので割愛)
docker-compose.yamlのenvironmentはdocker runに有効な環境変数です。docker buildのときは反映されません。
- https://qiita.com/hokutoasari/items/9043ed26402d6860d0a5
- https://docs.docker.jp/compose/environment-variables.html
したがって、ローカルの実行はdocker-composeで行ってしまい、DockerイメージだけECRに投げて、AWS上ではあとはよしなにしてもらえば、docker-compose.yamlのenvironmentで設定した環境変数は無視されるということです。
これはAWSプロファイルの切り替えに便利で、ローカル環境では名前つきプロファイルを使っている(この例だとdevelop)に対し、AWS環境だとAWSが用意したデフォルトプロファイルを使うということが、ソースファイルの書き換えなしでできます。
AWSで使うプロファイルは、環境変数AWS_PROFILEで任意の名前つきプロファイルに切り替えできますが、例えばAWS_PROFILEを空文字(””)にしても、aws configureで指定したプロファイルには一致しません(AWS側で実行しようとするとエラーになりました)。aws configureで指定されるデフォルトプロファイルは、これらの名前付きプロファイルの設定がないときに読み込まれるプロファイルなので、愚直にやろうとすると、環境変数の設定ではなく、
- unsetのような形でビルド時の環境変数を消去するか
- runのときに動的に環境変数を付与するか
のいずれかになりますが、実行時の引数に応じたif的な処理はDockerfileの仕様上あまり得意ではありません。
なので、そもそも発想を変えて、docker-composeはローカル用と割り切って、docker-compose経由でrunしたときに付与される環境変数を定義してしまうというのが一つのやり方です(CI/CD考え始めるとまた面倒くさくなりますが)
Dockerイメージをスクラッチビルドする場合
スクラッチビルドの場合は考えることが増えます。ディレクトリ構成は以下のようになります。
+ src
- app.py
- entry_script.sh
+ docker-compose.yaml
+ Dockerfile
+ requirements.txt
ローカルではLambdaのエミュレートのためにRIEを使い、それ以外では普通に実行するということを行うため、別途entry_script.shを用意します。
- https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-test.html
- https://qiita.com/a_b_/items/954c682f38735e3ec427
RIEのセットアップも別途必要になります。
src/app.py
import boto3
client = boto3.client('sns')
def lambda_handler(event, context):
params = {
'TopicArn': '<your-sns-arn>',
'Subject': 'Lambda -> SNS送信テスト',
'Message': 'Message\n\nLambda -> SNSでの送信テスト'
}
client.publish(**params)
AWSが用意したベースイメージを使う場合と変わりません
src/entry_script.sh
#!/bin/sh
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
echo AWS_PROFILE=${AWS_PROFILE}
exec /usr/bin/aws-lambda-rie /usr/bin/python3 -m awslambdaric $@
else
exec /usr/bin/python3 -m awslambdaric $@
fi
LAMBDA_RUNTIME_APIが用意されていない(ローカル環境の場合は)RIEを使い、それ以外は普通にPythonを実行しています。
Dockerfile
Ubuntu:22.04をベースイメージとした場合です。Lambda RIEの組み込みも行っています。正直そこまで大した容量ではないので組み込んでもいいと思います。
FROM ubuntu:22.04
RUN apt-get update
ENV TZ=Asia/Tokyo
ENV LANG=en_US.UTF-8
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get install -yq --no-install-recommends python3-pip \
python3-dev \
curl \
tzdata && apt-get upgrade -y && apt-get clean
RUN ln -s /usr/bin/python3 /usr/bin/python
# Install RIE
RUN curl -Lo /usr/bin/aws-lambda-rie \
https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie && \
chmod +x /usr/bin/aws-lambda-rie
# Install requirements
COPY requirements.txt .
RUN pip install -U pip &&\
pip install --no-cache-dir -r requirements.txt
COPY src .
ENTRYPOINT [ "/entry_script.sh"]
CMD ["app.lambda_handler"]
requirements.txt
awslambdaric==2.0.4
boto3==1.28.3
docker-compose.yaml
services:
lambda:
build: .
image: lambda_email
ports:
- 9000:8080
volumes:
- ~/.aws/:/root/.aws
environment:
- AWS_PROFILE=develop
スクラッチビルドと公式イメージの比較
Dockerイメージをスクラッチビルドするのと、公式イメージを使うのどう変わるのか比較してみました。
手軽さ
公式イメージのほうが楽です。
イメージ容量
今回のケースでは、スクラッチビルドのほうが軽かったです
REPOSITORY TAG IMAGE ID CREATED SIZE
lambda_email latest 4f10beeb059d 19 minutes ago 328MB
lambda_email_pre latest 1246275decbc 3 hours ago 619MB
lambda_emailがスクラッチビルド、lambda_email_preが公式イメージです。無圧縮状態で倍程度の容量差がありました。今回はDynamoDBなど使っていませんでしたが、公式イメージでそこらへんカバーされているためちょっと重いのかもしれません。
ただ、ECRにプッシュしたらかなり微妙な差となりました。
- 328MB -> 114.61MB
- 619MB -> 184.24MB
ECRの料金がGB/月あたり0.10USDとかなり安いので、正直1~2円程度の差でしかないです。無料枠(月500MB)が使えれば0円になります。
Lambdaの速度
Lambdaの実行速度は、公式イメージのほうが速い結果となりました。コンソールから3回テストケースを叩いた場合です。
公式イメージ
START RequestId: 30593aff-6c12-487b-ac14-326acc4673c2 Version: $LATEST
END RequestId: 30593aff-6c12-487b-ac14-326acc4673c2
REPORT RequestId: 30593aff-6c12-487b-ac14-326acc4673c2 Duration: 249.23 ms Billed Duration: 734 ms Memory Size: 128 MB Max Memory Used: 67 MB Init Duration: 484.38 ms
START RequestId: aeab0ced-3ff7-40ce-92c6-6fd2eae1f77f Version: $LATEST
END RequestId: aeab0ced-3ff7-40ce-92c6-6fd2eae1f77f
REPORT RequestId: aeab0ced-3ff7-40ce-92c6-6fd2eae1f77f Duration: 187.79 ms Billed Duration: 188 ms Memory Size: 128 MB Max Memory Used: 68 MB
START RequestId: 8c7b54bf-378e-4b66-af7b-e7b0925b762e Version: $LATEST
END RequestId: 8c7b54bf-378e-4b66-af7b-e7b0925b762e
REPORT RequestId: 8c7b54bf-378e-4b66-af7b-e7b0925b762e Duration: 137.53 ms Billed Duration: 138 ms Memory Size: 128 MB Max Memory Used: 68 MB
1回目が2.2秒、それ以降が1秒でした。
スクラッチビルド
START RequestId: a7f62aa0-77b3-470c-8845-3ee5b11bdcbd Version: $LATEST
END RequestId: a7f62aa0-77b3-470c-8845-3ee5b11bdcbd
REPORT RequestId: a7f62aa0-77b3-470c-8845-3ee5b11bdcbd Duration: 1118.60 ms Billed Duration: 2200 ms Memory Size: 128 MB Max Memory Used: 62 MB Init Duration: 1080.64 ms
START RequestId: 431c18a7-2b33-422d-9b88-c6db8f352608 Version: $LATEST
END RequestId: 431c18a7-2b33-422d-9b88-c6db8f352608
REPORT RequestId: 431c18a7-2b33-422d-9b88-c6db8f352608 Duration: 1075.32 ms Billed Duration: 1076 ms Memory Size: 128 MB Max Memory Used: 63 MB
START RequestId: 01c843d3-5311-4382-8da0-a41e4258cafa Version: $LATEST
END RequestId: 01c843d3-5311-4382-8da0-a41e4258cafa
REPORT RequestId: 01c843d3-5311-4382-8da0-a41e4258cafa Duration: 1048.83 ms Billed Duration: 1049 ms Memory Size: 128 MB Max Memory Used: 64 MB
1回目が0.7秒、2回目以降が0.1~0.2秒でした。
Dockerイメージサイズだけ見るとスクラッチビルドのほうが速いですが、公式イメージは何らかの最適化がかかっているのでしょうね。特に問題なければ公式イメージ使うでいいと思います。
感想
LambdaのDocker周り簡単かなと思ったら結構クセがあった。AWSのプロファイル周りはハマりがち
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー