初めに
LambdaアプリケーションをSAMで開発するにあたりコンテナでデプロイを初めて取り扱うにあたり色々試行していたのですが、
せっかくコンテナで環境をそのまま持ってけるんだからローカル環境も同じコンテナで処理したい!
でもDockerfileの多重管理はしたくない!となっていい方法がないかと模索していたところ
割と良さげな方法がありましたので備忘録がてらまとめます。
AWSのサポートに問い合わせてみたら記事執筆時点(2021/08/27)では
まだ公式のドキュメントが追いついてなく記載がなく追記予定らしいです。
こんなことしなくてももっと簡単にできるよっていうのがあったらご教示いただければと思います。
何がしたかったか
今回はAWS公式で提供されているpython3.8のイメージをベースとします。
大元のDockerfileはドキュメントに記載の通り以下で
ENTRYPOINT ["/lambda-entrypoint.sh"]
が実装されており、これに対して
CMD
で引数を渡すような作りをすることで対象のスクリプトを実行できます。
https://github.com/aws/aws-lambda-base-images/blob/python3.8/Dockerfile.python3.8
ただしこのコンテナをローカル開発で利用するにあたり以下の点が不満でした
- sam経由で実行する場合
sam build
とsam local invoke
が必要- せっかくのスクリプト言語なのにいちいちビルドが必要で確認がめんどくさい
- build問題はsamを経由せずに
ボリュームをマウントdockerコマンド経由で実行してやれば解決はするが次項の問題がある
- コンテナは起動するとスクリプトの実行が終わるのでコンテナを立ち上げっぱなしにできない
- ちょっと確認のためにコンテナ内で作業したいとかがやりづらい
なのでローカル用にはENTRYPOINT ["tail", "-f"]
を実装したDockerfileを利用し、
通常時は内部でpythonコマンド経由で実行しある程度整理できたところで、
sam経由で実行のようなことを考えていました。
ただしこのためにDockerfileを複数作って管理というのは共通部分を多重に管理しないといけないため回避したい問題でした。
解決方法
Dockerのマルチステージビルドを使うことで解決できました。
今回調べるまで知らなかったのですがdocker build
の--target
オプションで指定したステージを最終成果物としてできることを知りました。
ただdocker build --target=xxx
と同じ挙動をするものがSAMに見当たらないという点でつまづきました。
sam buildのドキュメントにもなくSAMのアプリケーションビルドのページにもなかったのでAWSに問い合わせしてみたのですが、
ドキュメントが追いついていないだけでSAM CLI 1.12.0
で実装されていたようです。
(SAM側のテンプレートファイルのDockerBuildTarget
で利用するステージの指定が可能)
https://github.com/aws/aws-sam-cli/issues/2578
各種実装
ファイルの配置
testフォルダとか一部省略してます。
docker-compose.yml
が増えている以外はsam init
で作られるのと同じです。
.
├── src
│ ├── Dockerfile
│ ├── docker-compose.yml
│ ├── __init__.py
│ ├── requirements.txt
│ └── app.py
└── template.yaml
Dockerfileの実装
最初にデプロイ用のprod
イメージを作り、
その後にそこからローカル開発用に一部を書き換えたdev
イメージを作成します。
FROM public.ecr.aws/lambda/python:3.8 as prod
#ソースコード類をコピー
COPY ./* ./
RUN python3.8 -m pip install -r requirements.txt
CMD ["app.lambda_handler"]
FROM prod AS dev
ENTRYPOINT [ "tail", "-f"]
docker-compose.yml
個人的にはdocker-compose
経由の起動が楽なのでdocker-compose.yml
も作っておきます。
このdocker-compose.yml
はローカルの開発専用でSAMでデプロイする場合は後述のSAMテンプレート側が読まれます。
target: dev
を指定することでdocker-compose経由で起動する場合はdev側のイメージを利用します。
version: "3"
services:
app:
build:
context: .
dockerfile: Dockerfile
target: dev
volumes:
- .:/var/task
template.yml
SAM側のコンテナの設定系はMetadata
で設定します(詳しくはSAMのアプリケーションビルドを参照)
このMetadataにDockerBuildTarget
で対象のステージを指定することができるようです。
(2021/08/27時点で記載がなかったのはこのパラメータ)
なおこのパラメータをなしで実行すると最終のdev
まで実行されたものが利用されました。
template.yml
は以下のような形で実装します(一部省略)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
...
Resources:
CreateMntFolder:
Type: AWS::Serverless::Function
Properties:
PackageType: Image
...
Metadata:
Dockerfile: Dockerfile
DockerContext: ./src
DockerTag: python3.8-v1
DockerBuildTarget: prod
こうすることでDockerfileを多重管理する必要もなくなり、
ちょっと確認したい時はdocker-compose up -d
経由でコンテナを立ち上げ、
docker-compose exec app bash
で内部に入って実行もできますし、
sam build
からのsam local invoke
やsam local start-api
等の各種ローカル実行系のコマンドでの確認もできます。
終わりに
マルチステージビルドはDocker公式チュートリアルちょっと読む限りは中間成果物があるような環境のために
使い捨ての作業イメージが作れるくらいのイメージでした。
実際はそうではなくSAMに限らずこれまで環境ごとに複数Dockerfileを作っていたのが、
一つのファイルで共通の部分の中間イメージを作りそこから個別実装ということができたり、
今回みたいに用途によって途中のステージのものを利用することもできたりと
少し視野を広く持っていればいろいろなことができそうです。
一つのDockerfileにアプリとDBのイメージを両方記載してdocker-compose.yml
でtarget
パラメータをうまく指定することで
Dockerfileを一つにまとめられそうなんですがどうなんでしょうか。
同じこと考えている人はいそうなのでこの辺りはまたチュートリアルとかフォーラムとか色々みてみようかと思います。