この記事は
YAMAP エンジニア Advent Calendar 2020 の五日目になる予定です。
tl;dr
従来、AWS Lambda では、ソースコードや関連するパッケージを zip 形式でまとめたファイルをアップロードして Lambda 関数を作成していましたが、Docker イメージでも Lambda 関数を作成出来るようになったとのアナウンスがあったので、サラッとチュートリアルしてみました。
この手のアップデートでは、いきなり東京リージョンでは使えないのかなーと思っていましたが、既に東京リージョンでも利用出来る状態でした。ありがたや。
チュートリアル
アプリケーション
以下の記事を参考に、せっかくなので Ruby コンテナでチュートリアルしています。
早速、コンテナ内で動かすアプリケーションを書きます。
require 'json' def handler(event:, context:) { statusCode: '200', body: JSON.generate('Test!+1') } end
app.rb という名前で保存します。
Docker イメージだからと言って、特別なコードを書く必要はありません。
Dockerfile
超シンプル Dockerfile です。
FROM amazon/aws-lambda-ruby:latest COPY app.rb ./ CMD [ "app.handler" ]
肝となるのは、ベースイメージである amazon/aws-lambda-ruby
で、このイメージには、Lambda のランタイム API と連携する為の AWS Lambda Runtime Interface Clients というライブラリが同梱されている状態とのことです。
尚、このコンテナイメージで動く Ruby のバージョンは 2.7.2 でした。(2020/12/02 現在)
$ docker run -d --rm --name=ruby-docker-lambda -p 9000:8080 ruby-docker-lambda:latest $ docker exec -t -i ruby-docker-lambda ruby --version ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
entrypoint として指定されている /lambda-entrypoint.sh
を見てみたいと思います。
$ docker exec -t -i ruby-docker-lambda cat /lambda-entrypoint.sh #!/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
/usr/local/bin/aws-lambda-rie ってなんだろうって思ったら、Lambda をローカル上で実行出来るエミュレータのようです。これを使うことで、ローカル環境での動作確認が可能になるということですね。
ビルド
コンテナイメージをビルドします。
$ docker build -t ruby-docker-lambda .
一旦、確認
以下のようにコンテナを起動します。
$ docker run --rm --name=ruby-docker-lambda -p 9000:8080 ruby-docker-lambda:latest
起動したら、以下のように出力されました。
time="2020-12-02T11:45:55.14" level=info msg="exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)"
コンテナが起動したら、curl
コマンドを使ってアクセスすると、以下のように出力されました。
$ curl http://localhost:9000/2015-03-31/functions/function/invocations -d '{}' {"statusCode":"200","body":"\"Test!+1\""}
おおー、Lambda が動いている感じ。なるほど、先程の /usr/local/bin/aws-lambda-rie
が頑張ってくださっていますね。
コンテナイメージを push
ECR にコンテナイメージを push します。
$ aws ecr create-repository --repository-name=ruby-docker-lambda --image-scanning-configuration scanOnPush=true $ docker tag ruby-docker-lambda:latest 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ruby-docker-lambda:latest $ docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ruby-docker-lambda:latest
Lamda 関数を作成
マネジメントコンソールポチポチで作成します。
API Gateway との連携
今回は、マネジメントコンソールからポチポチで連携を設定します (詳細は割愛します)。
連携の設定が完了したら、curl
コマンドを使ってアクセスすると、以下のように出力されました。
$ curl https://xxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/default/ruby-docker-lambda "Test!+1"
おおー。なんか、満足。
Gem パッケージを追加する場合
試しに terminal-table を組み込んでみたいと思います。
コードは以下のように修正します。
require 'json' require 'terminal-table' def handler(event:, context:) rows = [] rows << ['One', 1] rows << ['Two', 2] rows << ['Three', 3] table = Terminal::Table.new :headings => ['Word', 'Number'], :rows => rows { statusCode: '200', body: table } end
Dockerfile は以下のように修正します。
FROM amazon/aws-lambda-ruby:latest COPY app.rb Gemfile ./ RUN bundle install --path vendor/bundle CMD [ "app.handler" ]
Gemfile は以下のように書きました。
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem 'terminal-table'
あとは、コンテナイメージをビルドして、ローカル環境でコンテナを起動して確認します。
$ curl -s http://localhost:9000/2015-03-31/functions/function/invocations -d '{}' | jq -r .body +-------+--------+ | Word | Number | +-------+--------+ | One | 1 | | Two | 2 | | Three | 3 | +-------+--------+
いい感じですね。あとは、Lambda のコンテナイメージを更新すれば、API Gateway からも確認出来るようになるはずです。
ちなみに、12/02 時点で、コンテナイメージの更新を行った場合、マネジメントコンソールからコンテナイメージを指定し直す必要がありました。
マネジメントコンソールを「日本語」にしておくと、この「画像を参照」っていうのが、ジワジワきますね (笑
以上
Lambda で Docker イメージを利用出来るメリットってなんだろうって考えてみました。
- 実装や検証にあたって、これまで Docker と共に使われてきたツール等が利用可能
- サードパーティパッケージも丸っとコンテナイメージにパッケージ出来る (レイヤーとか考えなくても良い)
- ローカルでの動作確認を行い易い
また、デメリットとしては、
- あくまでも、コンテナイメージなので、マネジメントコンソール上でソースコードを見ることが出来ない
- docker build と zip を比較すると、docker build の方がお手軽感は小さい (zip の方がお手軽のように感じる)
という感じでしょうか。あと、普段は Serverless Framework を使って Lambda を扱っているので、Serverless Framework が、この Docker イメージに対応したら嬉しいなあと思った次第です。
このリリースを見た時に、「おおおーーーーっ」と激しく興奮しましたが、実際に触ってみると、今すぐに既存の Lambda 関数について、Docker イメージ版に乗り換えるモチベーションは湧かなかったのが正直なところです。ごめんなさい。
ユースケースによっては、Docker イメージ版の方が良いという場合もあるので、今後、用法用量を守って適切な使い分けを考えたいと思います。