Serverless FrameworkでCustom RuntimeなLambdaをコンテナ対応する
6/24追記
更新版書きました。
ServerlessがLambdaのコンテナ化に対応していたようなので早速試してみました。
今回は記事中のサンプルでは言語はCrystal、表題の通りCustom Runtimeを採用していますが他の環境でも参考になると思います。
また今回は言語としてCustom Runtimeを使用するのに必要な実装については特に解説しませんので、もし必要な場合はこちらから過去の記事をを参照していただければと思います。
いくつかの言語でのサンプルをよういしているので下記も参考にしてください。
Serverlessでコンテナを利用する方法
とりあえず動かしてみるだけなら特に難しくはありません。
下記の例(Serverlessの公式の紹介記事より拝借)のように、今までserverless.ymlで指定していたhandlerの代わりにimageという項目名でECR上にプッシュされている使用したいイメージのURIを指定するだけです。
一応注意点として、タグなどではなく必ずdigestを指定する必要があります。
functions:
someFunction:
image: <account>.dkr.ecr.<region>.amazonaws.com/<repository>@<digest>
つまり実践ではデプロイの前準備として「コンテナをビルドしてECRへプッシュする処理」と「プッシュしたコンテナの情報を取得する処理」が必要になるということでもあります。
コンテナをビルドしてECRへプッシュする
兎にも角にもまずECRにコンテナをプッシュしなくてはなにも始まりません。
ということでCrystalの場合のDockerfileの例を貼ってしまいます。
FROM crystallang/crystal:latest as build-image
WORKDIR /work
COPY ./ ./
RUN crystal build --link-flags -static -o bootstrap src/main.cr
RUN chmod +x bootstrap
FROM public.ecr.aws/lambda/provided:al2
COPY /work/ /var/runtime/
# NOTE: ここで指定した文字列がhandlerとして利用される
CMD ["hello"]
みていただければ分かる通り、そんなに難しいものではありません。
Crystalの公式イメージを利用してビルドをします。
Custom Runtimeの場合は /var/runtime/bootstrap
を実行するようになっているのでビルドしたバイナリをAWSが公式で配布しているLambda用のイメージにへコピーするだけです。
ここで1点注意しなければならないことは、Lambdaに使用するイメージは必ずAWSの公式のLambda用イメージでなくてはならないということです。
この例の場合はCustom Runtime用のイメージを使用しています。
というわけでこのイメージをビルドしてプッシュするのですが、まずECRにプッシュするためのリポジトリが必要です。
aws cliを利用する場合は下記のコマンドで作成します。
$ aws ecr create-repository --repository-name <リポジトリ名> --image-scanning-configuration scanOnPush=true
そうしたら次はECRへログインします。
$ aws ecr get-login-password --region <リージョン> |
$ docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com
参考として、AWSのアカウントIDは下記のようにすると自動で取得できます。
$ aws sts get-caller-identity | jq -r .Account
ログインをしたら下記のコマンドでビルドとプッシュが出来ます。
$ docker build -t <コンテナ名> .
$ docker tag <コンテナ名>:<タグ名> <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/<コンテナ名>:<タグ名>
$ docker push <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/<コンテナ名>:<タグ名>
プッシュしたコンテナの情報を取得する
次に前章でプッシュしたコンテナの情報を取得し、それを使用してServerlessでLambdaのデプロイを実行します。
前提として、serverless.ymlを下記のように記述することによって、オプションから値を渡せるようにします。
custom:
# NOTE: sls removeなどを実行するのにオプションを指定せず実行できるようにするためのもの
defaultAccount: dummy
defaultDigest: dummy
functions:
hello:
image: ${opt:account, self:custom.defaultAccount}.dkr.ecr.<リージョン>.<コンテナ名>@${opt:digest, self:custom.defaultDigest}
events:
- http:
path: test
method: get
ということでServerlessへ引き渡すための情報を取得していきます。
まず前述の通り、アカウントIDは下記のコマンドで取得出来ます。
$ aws sts get-caller-identity | jq -r .Account
そしてコンテナのdigestですが、aws-cliを利用してリポジトリの一覧を取得できるのでjqを使って使いたいイメージのタグ利用してを絞り込んでやるとdigestが取得出来ます。
$ aws ecr list-images --repository-name <コンテナ名> |
$ jq '.imageIds[] | select(.imageTag=="<タグ名>") | .imageDigest' |
$ tr -d '"'
あとは下記のように取得した値をServerlessへ引き渡してやればデプロイ出来ます。
$ sls deploy --account <アカウントID> --digest <digest>
ここまでの一連の流れをスクリプトにまとめるとこんな感じになります。
region="ap-northeast-1"
account=$(aws sts get-caller-identity | jq -r .Account)
tag=$(uuidgen)
aws ecr get-login-password --region $region |
docker login --username AWS --password-stdin $account.dkr.ecr.$region.amazonaws.com
container="lambda_container_sample"
target="$account.dkr.ecr.ap-northeast-1.amazonaws.com/$container"
docker build -t $container .
docker tag $container:$tag $target:$tag
docker push $target:$tag
digest=$(aws ecr list-images --repository-name $container | jq '.imageIds[] | select(.imageTag=="latest") | .imageDigest' | tr -d '"')
sls deploy --account $account --digest $digest
まとめ
というわけでCustom Runtimeな環境でのServerless Frameworkのコンテナ対応の紹介でした。
正直なところ触ってみた感じまだとりあえず対応しましたという感じでこれから便利使えるように改善されていくのかな?という感じですね。
少なくとも今回例に出したCrystalや公式対応してる言語ではGoなんかの可搬性の高いシングルバイナリを作れる言語の場合なんかでは今すぐ飛びついて対応する必要があるかというと微妙な感じです。
逆にスクリプト言語とかでコンテナを使って前準備とかをしたいなら使ってもいいかもですね。
先程のデプロイ用スクリプトなんかをみてもらえればわかる通り、正直そんなに難しいことはしていないので近いうちにローカルのDockerfileを自動でデプロイしてくれるようになる気はしています。
おまけ
Github Actions用のデプロイ用のworkflowsを作ったので載せておきます。
もしよかったら参考にしてください。
name: lambda_container_sample
on:
push:
branches: [ develop ]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: setup node.js
uses: actions/setup-node@v1
- name: install sls
run: npm i -g serverless
- name: checkout
uses: actions/checkout@v1
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: login ecr
id: login-ecr
uses: aws-actions/amazon-ecr-login@master
- name: build and push ecr
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: lambda_container_sample
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: get aws account id
id: get-aws-account
run: |
echo "::set-output name=ACCOUNT_ID::$(aws sts get-caller-identity | jq -r .Account)"
- name: get ecr digest
id: get-ecr-digest
env:
IMAGE_TAG: ${{ github.sha }}
run: |
echo "::set-output name=DIGEST::$(aws ecr list-images --repository-name lambda_container_sample | jq '.imageIds[] | select(.imageTag=="'$IMAGE_TAG'") | .imageDigest' | tr -d '"')"
- name: deploy
env:
ACCOUNT_ID: ${{ steps.get-aws-account.outputs.ACCOUNT_ID }}
DIGEST: ${{ steps.get-ecr-digest.outputs.DIGEST }}
run: sls deploy --verbose --account $ACCOUNT_ID --digest $DIGEST
Discussion
バージョン2.44.0で、digestでなくタグでもデプロイできました