こんにちは。
今年7月からあしたのチームでバックエンドリード兼SREをやっている snaka です。
今年も AWS re:Invent が開催され、多くの新しいサービスや既存サービスに対する新しい機能が発表されていますね。
毎年すごい勢いでサービスが追加・更新されるので、そのキャッチアップが大変です;
その中でも特に私が今気になっているのは、下記のブログ記事でも紹介されている AWS Lambda でのコンテナイメージのサポートです。
さっそく、上記記事を参考にコンテナイメージを利用した Lambda を試してみましたので手順を紹介します。
なお、本記事に掲載しているコードは GitHub の以下のリポジトリから取得可能ですので、参考としてください。
https://github.com/snaka/helloworld-lambda-container
Lambda 関数本体の Docker Image を用意する
まず、Lambda 関数の本体となるコードを用意します。
今回は、Ruby を使います。
bundle init
を実行し Gemfile の雛形を作成します。
作成された Gemfile をエディタで編集し、に以下のように gem "prawn"
の1行を追加します。
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "prawn"
次に、Lambda 関数本体の実装(app.rb)です。
以下のような、シンプルなコードになります。
# app.rb require 'base64' require 'json' require 'prawn' def handler(event:, context:) pdf = Prawn::Document.new pdf.text "Hello World!" pdf_base64_encoded = Base64.encode64(pdf.render) { statusCode: 200, headers: { 'Content-Length': pdf_base64_encoded.length, 'Content-Type': 'application/pdf', 'Content-disposition': 'attachment;filename=hello.pdf' }, isBase64Encoded: true, body: pdf_base64_encoded } end
Lambda で実行される関数は Lambda サービスが規定する Runtime API でやりとりを行う必要があります(下図のオレンジの点線部分)
(AWS Lambda - Developer Guide より引用)
AWS からは上記 Runtime API と 関数本体の間を取り持つ Runtime Interface Client が各言語向けに提供されており、それを含む Docker Image を base image という形で Docker Hub で公開しています。
Runtime Interface Client から呼び出される関数本体は Handler と呼ばれます。
Handler は以下の規則に従って実装する必要があります。
- 引数として
event
,context
の2つの引数を受け取ること - 戻り値として JSON(※) を返却すること
※戻り値である JSON の仕様がどこかにあるはずですが、ブログ公開時点で見つけられなかったので見つけ次第追記します。
前述の関数本体を含む Docker Image を作成するための Dockerfile を作成します。
FROM amazon/aws-lambda-ruby:2.7 COPY app.rb Gemfile* ./ RUN bundle config set path 'vendor/bundle' RUN bundle install CMD [ "app.handler" ]
FROM
行を見たらわかるように amazon 公式の Ruby 向け base image を使っています。
それ以外は、関数本体である app.rb と Gemfile 、そして bundle install
で必要なライブラリ等をインストールしています。
bundle config set path
では Gem のインストール先を明示的に vendor/bundle
としています。
この設定が無いと、require
時に Gem が見つけられずに実行時にエラーが出ていました。
最後の CMD
行では、{Handlerを含むファイル名(拡張子を除く)}.{Handlerの関数名}
という形で Runtime Interface Client に Handler を呼び出すための情報を渡しています。
コンテナイメージをビルドし動作確認する
前節までで、コンテナイメージをビルドする準備が整いましたので、引き続きコンテナイメージのビルドを行います。
ビルドは通常どおり以下のようなコマンドで行います。
$ docker build -t hello-lambda-world .
ビルドが完了したら、ローカルでの動作確認として Lambda Runtime Emulator を起動してみます。
以下のようにビルドしたコンテナイメージを使ってコンテナを起動します。
$ docker run -p 9000:8080 hello-lambda-world:latest
コンテナを起動したのとは別にターミナルを開き、 cURL で Lambda Runtime API を叩いてみます。
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}' {"statusCode":200,"headers":{"Content-Length":1261,"Content-Type":"application/pdf","Content-disposition":"attachment;filename=hello.pdf"},"isBase64Encoded":true,"body":"JVBERi0xLjM ... (略) ... RU9GCg==\n"}
実行の結果、上記のように JSON の文字列が返却されれば OK です。
ECR に Docker Image を登録
コンテナイメージのビルドが完了したら、Lambda サービスから利用できるように ECR リポジトリに登録します。
最初に、以下のように ECR リポジトリを作成します。(AWSコンソールから作成しても良いです)
$ aws ecr create-repository --repository-name hello-lambda-world
リポジトリ作成に成功すると実行結果にリポジトリのURI(repositoryUri
)が出力されます。
URIは後述のコマンドで利用するので控えておきます。
{ "repository": { "repositoryArn": "arn:aws:ecr:ap-northeast-1:123456789012:repository/hello-lambda-world", "registryId": "123456789012", "repositoryName": "hello-lambda-world", "repositoryUri": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/hello-lambda-world", "createdAt": "2020-12-17T00:25:40+09:00", "imageTagMutability": "MUTABLE", "imageScanningConfiguration": { "scanOnPush": false }, "encryptionConfiguration": { "encryptionType": "AES256" } } }
コンテナイメージに に tag として リポジトリのURI を設定します。
$ docker tag hello-lambda-world:latest 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/hello-lambda-world:latest
docker login
コマンドで ECR にログインします。
AWS CLI のコマンド aws ecr get-login-password
でログインに必要な一時的なパスワードを取得し、パイプで docker login
コマンドに連携しています。
aws ecr get-login-password | \ docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
ログインが完了したら、ECR にコンテナイメージを push します。
docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/hello-lambda-world:latest
コンテナイメージの push に成功したことを確認するため、AWS コンソールで ECR リポジトリを参照してみます。 以下のように表示されていれば push 成功です。
Lambda 関数を登録する
以降、手順の都合で AWS コンソールでの作業となりますが、AWS CLI や AWS SAM CLI で行うことも可能です。
AWS コンソールから Lambda サービスを開き、以下のように関数の作成を行います。
- オプションから「コンテナイメージ」を選択する
- 関数名を任意に入力する
- 「画像を参照」(恐らく「画像」はコンテナイメージを指す)ボタンをクリックする
「コンテナイメージを選択」のダイアログが開くので以下のように ECR に push したコンテナイメージを選択し、「イメージを選択」をクリックします。
あとはそのまま「関数の作成」をクリックすれば関数が作成されます。
テスト実行してみる
ここまでで、作成された関数をテストしてみます。
右上のドロップダウンから「テストイベント設定」を選択します。
「テストイベントの設定」ダイアログが開くので、「イベント名」に任意の名前を入力し「作成」をクリックします。
作成されたイベントテンプレートをドロップダウンリストから選択し、「テスト」ボタンをクリックすると関数が呼び出されます。
関数の呼び出しに成功すると、以下のように実行結果が表示されます。
実行結果が「成功」となっていればテスト成功です。
Lambda 関数の入り口となる API Gateway を作成
これが最後の手順です。 Lambda 関数を HTTP 経由で呼び出せるように API Gateway を作成します。
前節で作成した Lambda 関数にトリガーを追加します。
トリガーの種類は API Gateway を選択し、その下に表示されるドロップダウンリストから「API を作成する」を選択します。
上記のように選択し「追加」をクリックします。
API Gateway のエンドポイント経由で Lambda 関数を実行する
前節で API Gateway を作成すると、その API エンドポイントが公開されインターネットから呼び出し可能な状態になります。
上記エンドポイントをブラウザでアクセスしてみます。
以下のような PDF がダウンロードされたら成功です 🎉 🥳
さいごに
AWS Lambda がコンテナイメージをサポートすることによって、既存のコードベースをほぼそのままで Lambda 実行環境で動作させることが可能になります。
これを使って、今からバッチサーバを重くする原因となっている「あんなジョブ」や「こんなジョブ」を Lambda 化して運用コストが削減できるんじゃないかと夢が広がっています。
以上、年末・年始は re:Invent 2020 の動画視聴で潰れそうな気がしてる snaka でした。