コンテナ利用者に捧げる AWS Lambda の新しい開発方式 !
Author : 下川 賢介
第一回 コンテナ Lambda の”いろは”、AWS CLI でのデプロイに挑戦 !
こんにちは、サーバーレス スペシャリストソリューションアーキテクトの下川 (@_kensh) です。
builder’s flashをご覧になっている皆様は AWS re:Invent 2020 で発表された「AWS Lambda の新機能 – コンテナイメージのサポート」についてももご存知の方が多いと思いますが、ご存知ですか ?
今回はこのコンテナイメージサポートの基本的な話について触れていきたいと思います。
この連載記事のその他の記事はこちら
- 選択
- 第 1 回 コンテナ Lambda の”いろは”、AWS CLI でのデプロイに挑戦 !
- 第 2 回 コンテナ Lambda を開発、まずは RIC と RIE を使ってみよう !
- 第 3 回 コンテナ Lambda をカスタマイズして、自分好みの PHP イメージを作ろう !
- 第 4 回 コンテナ Lambda を AWS SAM でデプロイしよう !
- 第 5 回 コンテナ Lambda の CI/CD パイプラインの考え方
- 第 6 回 コンテナ Lambda の CI/CD パイプラインを SAM Pipeline で作ろう !
- 第 7 回 コンテナLambdaでサイドカーパターンは実現可能なの ?
ご注意
本記事で紹介する AWS サービスを起動する際には、料金がかかります。builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。
なぜコンテナイメージサポートが必要だったか ?
AWS Lambda は 2014 年 11 月 13 日にサービス提供を開始 して以来、当初は想像もしなかったほどの多くのワークロードで利用されてるようになってきました。
その中で、開発や運用にかかわる要望が出てきました。たとえばこんな要望。
- プロセス・ガバナンスに対するよくあるご要望
- サーバーレスもコンテナも使っているが、CI/CD とかツールが個別なのをどうにかしたい。
- ローカルテストの方式を統一したい
- 依存関係の管理方法を統一したい
- イミュータブルイメージとしての配布や、ステージング環境や本番環境などの環境間を超えたデプロイをしたい
- 処理における制限へのよくあるご要望
- 機械学習関連ライブラリが大きくてサイズ上限にぶつかる。
AWS Lambda を利用しているシステムでは、システム内の別のエンドポイントが Amazon ECS や Amazon EKS で実装されていたり、コンテナとしてアプリケーションをデプロイしていることもよくあります。そのため Lambda は Zip デプロイ、コンテナ系サービスは Docker イメージを利用したデプロイと、CI/CD の体験が異なり、開発者は二種類のデプロイ方法を学習する必要がありました。
またコンテナベースでの開発に慣れている人や組織は、ローカルのテスト方式もコンテナベースで行っていたり、依存関係もイメージ内に梱包することが一般的だと思います。さらに、ビルド環境でビルドしたイメージをテスト環境、ステージング環境、本番環境とイミュータブルイメージとして持っていきたいという思いもあるでしょう。
そこで、そういったお客様の要望に応えるように取り入れられた機能が AWS Lambda のコンテナイメージサポートです。
ZIP 形式との違い
Zip 形式とコンテナ形式の AWS Lambda サービスのクオータの違いですが、アーティファクトサイズ上限が Zip 形式の 250 MB に比べて、コンテナ形式の場合は、10 GB のイメージもデプロイすることが可能になっています。このため大きな機械学習ライブラリのような場合でも、10 GB を超えない範囲であれば EFS などの利用なしに Lambda 関数から依存関係を参照することができるようになりました。
Zip | コンテナ | |
ストレージ場所 | S3 | ECR |
ストレージサイズ上限 (リージョン単位) | 75GB (上限緩和可能) | ECR のクォータに準拠 |
アーティファクトサイズ上限 | 250 MB (展開後) | 10 GB |
Layer 対応 | あり | なし |
コード署名 | あり | なし |
※この表は 2021/03/02 時点でのクォータになります。(最新の情報は AWS 公式ドキュメントを参照ください。)
もちろん、コンテナ形式のデプロイ方法が登場しても、これまでどおり Zip 形式のデプロイも可能です。
AWS Lambda はこれまで関数コードを Zip ファイルに圧縮してデプロイする形式のみがサポートされていました。Zip 形式の Lambda 関数のデプロイ最大サイズは 250 MB (展開後のサイズ、展開前は 50 MB が最大) ですので、機械学習のライブラリを梱包してデプロイしようとすると、この最大サイズ制限に引っかかることがよくありました。
もちろん、Amazon EFS に依存ライブラリを展開しても良いのですが、EFS を利用するためには VPC に接続した Lambda 関数として設定 する必要があり、EFS 以外の VPC リソースを利用する予定の無いユーザーにとっては依存ライブラリのためだけに、リージョナルサービス (Amazon S3, Amazon SQS など) にアクセスするためのネットワーク設定を行うのも多少手間となっていました。
ZIP 形式の Lambda 関数作成方法をまずおさらい
コンテナのデプロイ方式を見る前にまず、これまでどうやって Zip 形式の Lambda 関数をデプロイしていたか確認してみましょう。
クリックすると拡大します
まず、コマンドラインで必要な情報を環境変数に入れておきましょう。
ここでは、AWS のリージョン情報とアカウント ID を取得して環境変数に入れています。
$ REGION=$(aws configure get region)
$ ACCOUNTID=$(aws sts get-caller-identity --output text --query Account)
さて、今回デプロイする予定の Lambda 関数ですが、とくに何も処理せずに、message として “hello world” を返すだけの関数実装になっています。これを app.py というファイル名で保存しておきます。
app.py
import json
def handler(event, context):
return {
"statusCode": 200,
"body": json.dumps(
{
"message": "hello world",
}
),
}
app.py のみを Zip 形式に圧縮しておきます。
$ zip package.zip app.py
ローカルの package.zip を指定して、Lambda 関数の作成をします。(AWS CLI がローカル環境にインストールされていない場合はインストールしておいてください。)
ROLE_ARN は AWS Lambda の実行ロール です。実行ロールの ARN を環境変数に入れておきます。
$ aws iam create-role --role-name lambda-ex \
--assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'
$ ROLE_ARN=arn:aws:iam::${ACCOUNTID}:role/lambda-ex
$ aws lambda create-function --function-name func1 \
--runtime python3.8 \
--handler app.handler \
--zip-file fileb://./package.zip \
--role ${ROLE_ARN}
実行しデプロイが成功すると、関数のメタ情報が JSON 形式で出力されます。
$ aws lambda create-function --function-name func1 \
> --runtime python3.8 \
> --handler app.handler \
> --zip-file fileb://./package.zip \
> --role ${ROLE_ARN}
{
"FunctionName": "func1",
"FunctionArn": "arn:aws:lambda:${REGION}:${ACCOUNTID}:function:func1",
"Runtime": "python3.8",
"Role": "arn:aws:iam::${ACCOUNTID}:role/<Role Name>",
"Handler": "app.handler",
"CodeSize": 290,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-02-08T14:21:05.688+0000",
"CodeSha256": "SuXK7YhVmzzklYlNET8KKXFzzxpkgkJmzrxWbgPEmn0=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "34ca3994-31a5-4d7d-91a6-0d8152c5f0c1",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}
AWS CLI で作成したばかりの Lambda 関数を実行してみましょう。
aws lambda invoke --function-name func1 output ; cat output
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}
正常終了しているのが確認できましたね。
ローカルにあるファイルは、Lambda 関数本体の app.py と、それを梱包した package.zip になっていると思います。
├── app.py
└── package.zip
コンテナ形式の Lambda 関数作成方法をためしてみよう!
さて、つぎはコンテナを使ってビルド & デプロイしてみましょう。
(ここからの作業はローカル環境に Docker Desktop がインストールされていることを前提で進めます。インストールされていない場合は、インストールしてから進めてください。)
クリックすると拡大します
まず、Dockerfile を作っていきましょう。
$ touch Dockerfile
ローカルにあるファイルは、Lambda 関数本体の app.py と、それを梱包した Dockerfile になっていると思います。
app.py は Zip 形式で関数を作成したときに利用したものをそのまま利用します。
├── Dockerfile
└── app.py
Dockerfile の中身を書いていきましょう。
Dockerfile
FROM public.ecr.aws/lambda/python:3.8
COPY app.py ./
CMD ["app.handler"]
FROM 命令で public.ecr.aws/lambda/python:3.8 と ECR の AWS 公式の公開イメージを指定していますが、amazon/aws-lambda-python:3.8 のように docker hub のイメージを参照することも可能です。
COPY コマンドでローカルに配置されている Lambda 関数本体である app.py ファイルをイメージにコピーしています。そして CMD で Lambda 関数のハンドラーを渡しています。
この Dockerfile を元にビルドしてみましょう。
$ docker build -t func1 .
Sending build context to Docker daemon 7.168kB
Step 1/3 : FROM public.ecr.aws/lambda/python:3.8
---> 96d8701bf5d1
Step 2/3 : COPY app.py ./
---> a3121c365747
Step 3/3 : CMD ["app.handler"]
---> Running in 458d223e3f30
Removing intermediate container 458d223e3f30
---> 91a66a0bc1ed
Successfully built 91a66a0bc1ed
Successfully tagged func1:latest
なんなく、ビルド成功しましたね。
さて、ここから ECR に今回作成する Lambda 関数のイメージ用のリポジトリを作成します。
$ aws ecr create-repository --repository-name func1
{
"repository": {
"repositoryArn": "arn:aws:ecr:${REGION}:${ACCOUNTID}:repository/func1",
"registryId": "${ACCOUNTID}",
"repositoryName": "func1",
"repositoryUri": "${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/func1",
"createdAt": "2021-02-09T00:12:39+09:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
ECR 上にリポジトリが作成されたら、リポジトリの URI を含めたタグを付与します。
$ docker tag func1:latest \
${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/func1:latest
リポジトリに push する前に、ECR にログインしておきます。
$ aws ecr get-login-password | docker login --username AWS --password-stdin ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com
Login Succeeded
そして、リポジトリに push しましょう。
$ docker push ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/func1:latest
The push refers to repository [${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/func1]
29fe8a4ae381: Pushed
20b4eff3dd4d: Pushing 68.11MB/92.13MB
11284767d41d: Pushing 87.48MB/199.7MB
d6fa53d6caa6: Pushed
b09e76f63d5d: Pushed
0acabcf564c7: Pushed
f2342b1247df: Pushing 125.9MB/294.9MB
ECR への push が終了し、正常に作成されたことを確認します。
29fe8a4ae381: Pushed
20b4eff3dd4d: Pushed
11284767d41d: Pushed
d6fa53d6caa6: Pushed
b09e76f63d5d: Pushed
0acabcf564c7: Pushed
f2342b1247df: Pushed
latest: digest: sha256:ef29dcb71c314e61b390a48793c7291c44cd0712e9e3e16a88a5c851a9724223 size: 1788
push が完了すると、ダイジェストが発行されるので、これを DIGEST 環境変数に入れておきます。
ここから、Lambda 関数をコンテナイメージを利用して作成していきますが、AWS Lambda のコンテナサポートで、—package-type を指定できるようになりました。デフォルトは Zip ですので、ここでは Image としておきます。
さらに、 --code でイメージ URI を指定できるようになるのですが、ここで先ほど取得したダイジェストを含めた URI にしておきます。
DIGEST=$(aws ecr list-images --repository-name func1 --out text --query 'imageIds[?imageTag==`latest`].imageDigest')
aws lambda create-function \
--function-name func1-container \
--package-type Image \
--code ImageUri=${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/func1@${DIGEST} \
--role ${ROLE_ARN}
実際に、関数作成のコマンドを実行すると、Lambda 関数が新規に作成され、作成された関数のメタ情報が JSON 形式で返ってきます。
$ aws lambda create-function \
> --function-name func1-container \
> --package-type Image \
> --code ImageUri=${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/func1@${DIGEST} \
> --role ${ROLE_ARN}
{
"FunctionName": "func1-container",
"FunctionArn": "arn:aws:lambda:${REGION}:${ACCOUNTID}:function:func1-container",
"Role": "arn:aws:iam::${ACCOUNTID}:role/<Role Name>",
"CodeSize": 0,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-02-08T15:56:11.998+0000",
"CodeSha256": "ef29dcb71c314e61b390a48793c7291c44cd0712e9e3e16a88a5c851a9724223",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "81fc0fd1-3011-4a67-ba08-65d143343aed",
"State": "Pending",
"StateReason": "The function is being created.",
"StateReasonCode": "Creating",
"PackageType": "Image"
}
そしていよいよ実行することができます。作成された関数を実行してみましょう。
実行のコマンド構文は Zip 形式の時と変わりません。
$ aws lambda invoke --function-name func1-container output ; cat output
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}
正常終了できました。
まとめ
AWS Lambda の新たにサポートされた Docker コンテナ形式のデプロイを試してみました。この手順であるように既存の Lambda 関数のアプリケーションを再利用してコンテナ化した新規 Lambda 関数を作成することも可能です。今回実施した範囲では、手順についての理解は深められたと思いますが、まだコンテナ化した恩恵は感じられていないかなと思います。
次回以降ではさらに、コンテナサポート Lambda 関数の特徴や利点を追っていきたいと思います。
この連載記事のその他の記事はこちら
- 選択
- 第 1 回 コンテナ Lambda の”いろは”、AWS CLI でのデプロイに挑戦 !
- 第 2 回 コンテナ Lambda を開発、まずは RIC と RIE を使ってみよう !
- 第 3 回 コンテナ Lambda をカスタマイズして、自分好みの PHP イメージを作ろう !
- 第 4 回 コンテナ Lambda を AWS SAM でデプロイしよう !
- 第 5 回 コンテナ Lambda の CI/CD パイプラインの考え方
- 第 6 回 コンテナ Lambda の CI/CD パイプラインを SAM Pipeline で作ろう !
- 第 7 回 コンテナLambdaでサイドカーパターンは実現可能なの ?
筆者プロフィール
下川 賢介 (@_kensh)
アマゾン ウェブ サービス ジャパン合同会社
シニア サーバーレススペシャリスト ソリューションアーキテクト
Serverless Specialist Solutions Architect として AWS Japan に勤務。
Serverless の大好きな特徴は、ビジネスロジックに集中できるところ。
ビジネスオーナーにとってインフラの管理やサービスの冗長化などは、ビジネスのタイプに関わらず必ず必要になってくる事柄です。
でもどのサービス、どのビジネスにでも必要ということは、逆にビジネスの色はそこには乗って来ないということ。
フルマネージドなサービスを使って関数までそぎ落とされたロジックレベルの管理だけでオリジナルのサービスを構築できるという Serverless の特徴は技術者だけでなく、ビジネスに多大な影響を与えています。
このような Serverless の嬉しい特徴をデベロッパーやビジネスオーナーと一緒に体験し、面白いビジネスの実現を支えるために日々活動しています。
AWS を無料でお試しいただけます