ようへいの日々精進XP

よかろうもん

AWS Lambda で Docker イメージがサポートされたのでチュートリアルしてみました

この記事は

YAMAP エンジニア Advent Calendar 2020 の五日目になる予定です。

qiita.com

tl;dr

従来、AWS Lambda では、ソースコードや関連するパッケージを zip 形式でまとめたファイルをアップロードして Lambda 関数を作成していましたが、Docker イメージでも Lambda 関数を作成出来るようになったとのアナウンスがあったので、サラッとチュートリアルしてみました。

aws.amazon.com

この手のアップデートでは、いきなり東京リージョンでは使えないのかなーと思っていましたが、既に東京リージョンでも利用出来る状態でした。ありがたや。

チュートリアル

アプリケーション

以下の記事を参考に、せっかくなので Ruby コンテナでチュートリアルしています。

aws.amazon.com

早速、コンテナ内で動かすアプリケーションを書きます。

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 関数を作成

マネジメントコンソールポチポチで作成します。

f:id:inokara:20201202213529p:plain

API Gateway との連携

API Gateway と連携させてみます。

今回は、マネジメントコンソールからポチポチで連携を設定します (詳細は割愛します)。

f:id:inokara:20201202211314p:plain

連携の設定が完了したら、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 時点で、コンテナイメージの更新を行った場合、マネジメントコンソールからコンテナイメージを指定し直す必要がありました。

f:id:inokara:20201202212828p:plain

マネジメントコンソールを「日本語」にしておくと、この「画像を参照」っていうのが、ジワジワきますね (笑

以上

Lambda で Docker イメージを利用出来るメリットってなんだろうって考えてみました。

  1. 実装や検証にあたって、これまで Docker と共に使われてきたツール等が利用可能
  2. サードパーティパッケージも丸っとコンテナイメージにパッケージ出来る (レイヤーとか考えなくても良い)
  3. ローカルでの動作確認を行い易い

また、デメリットとしては、

  • あくまでも、コンテナイメージなので、マネジメントコンソール上でソースコードを見ることが出来ない
  • docker build と zip を比較すると、docker build の方がお手軽感は小さい (zip の方がお手軽のように感じる)

という感じでしょうか。あと、普段は Serverless Framework を使って Lambda を扱っているので、Serverless Framework が、この Docker イメージに対応したら嬉しいなあと思った次第です。

このリリースを見た時に、「おおおーーーーっ」と激しく興奮しましたが、実際に触ってみると、今すぐに既存の Lambda 関数について、Docker イメージ版に乗り換えるモチベーションは湧かなかったのが正直なところです。ごめんなさい。

ユースケースによっては、Docker イメージ版の方が良いという場合もあるので、今後、用法用量を守って適切な使い分けを考えたいと思います。

参考

aws.amazon.com

aws.amazon.com

Creating Lambda container images - AWS Lambda

www.keisuke69.net

dev.classmethod.jp