y-ohgi's blog

TODO: ここになにかかく

Lambda でコンテナを実行する

TL;DR

概要

re:Invent の発表でコンテナの実行ができるようになったので、ざっくり試してみるだけの記事

ためす

失敗例

単純なAlpine イメージだと動かないらしいので失敗してみる

ECR へコンテナを上げる

env コマンドを実行するだけのイメージを作成

$ export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
$ cat <<EOL | docker build -t ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container:latest -
FROM alpine
CMD ["env"]
EOL

ECR へリポジトリを作ってpush

$ aws ecr create-repository --repository-name lambda-container
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
$ docker push ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container:latest

Lambda の作成

Lambda のコンソールに「コンテナイメージ」が追加されているのでそこから設定。
ECR のパスはタグもしくはハッシュの指定までする必要あり。

自動生成してくれるIAM Role だと権限がたりないので AmazonEC2ContainerRegistryReadOnlyAWSLambdaBasicExecutionRole ポリシーを追加でアタッチする

実行結果

失敗した。

成功例

ドキュメントいわくRuntime interface clients (RIE) に対応する必要がある模様(カスタムランタイムの拡張なイメージ?)

Runtime support for Lambda container images - AWS Lambda

RIE に対応させるには公式で配布しているベースイメージを使用する他、主要な言語に対してはライブラリを提供している模様。

Node.js でRIE に対応させる例

$ npm install aws-lambda-ric

lambda 用イメージをみてみる

OS としてはAmazonLinux2 を使用していている。

$ docker run -it --entrypoint=bash docker.io/amazon/aws-lambda-provided:latest
bash-4.2# cat /etc/system-release
Amazon Linux release 2 (Karoo)

ENTRYPOINT で /lambda-entrypoint.sh を実行するようになっており、みてみると aws-lambda-rie を叩いている。

$ docker inspect amazon/aws-lambda-provided | jq '.[].Config.Entrypoint'
[
  "/lambda-entrypoint.sh"
]
$ docker run -it --entrypoint=bash docker.io/amazon/aws-lambda-provided:latest
bash-4.2# 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

AWS_LAMBDA_* 変数は aws-lambda-rie が挿入してくれる。

HOSTNAME=34c12827a892
AWS_LAMBDA_FUNCTION_VERSION=$LATEST
AWS_SESSION_TOKEN=
AWS_LAMBDA_LOG_GROUP_NAME=/aws/lambda/Functions
LD_LIBRARY_PATH=/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib
LAMBDA_TASK_ROOT=/var/task
AWS_LAMBDA_RUNTIME_API=127.0.0.1:9001
AWS_LAMBDA_LOG_STREAM_NAME=$LATEST
AWS_LAMBDA_FUNCTION_NAME=test_function
PATH=/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin
PWD=/var/task
AWS_SECRET_ACCESS_KEY=
LAMBDA_RUNTIME_DIR=/var/runtime
LANG=en_US.UTF-8
TZ=:/etc/localtime
AWS_ACCESS_KEY_ID=
HOME=/root
SHLVL=1
_HANDLER=function.sh.handler
AWS_LAMBDA_FUNCTION_MEMORY_SIZE=3008
_=/usr/bin/env

カスタムランタイム と同じノリでいけそう

シェルスクリプトを実行してみる

カスタムランタイムと同様にランタイムAPI を実行するようなコードを書いて実行

参考
- https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html - https://aws.amazon.com/jp/blogs/aws/new-for-aws-lambda-container-image-support/ - https://aripalo.com/blog/2020/aws-lambda-container-image-support/

まずは以下の3ファイルを用意する - bootstrap - 名前の通りブートストラップ用コード - function.sh - 実行したい処理をこのファイルへ記述する - 今回は公式リファレンスにあるコードをコピペ - 基本的に(シェルスクを実行する場合)このファイルを変更して任意の処理を行うことになる - Dockerfile - AWS が提供している docker.io/amazon/aws-lambda-provided:latest イメージをベースとして使用して、 bootstrap function.sh の2ファイルを流し込むだけのDockerfile

bootstrap

#!/bin/sh

set -euo pipefail

# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"

# Processing
while true
do
  HEADERS="$(mktemp)"
  # Get an event. The HTTP request will block until one is received
  EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")

  # Extract request ID by scraping response headers received above
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

  # Run the handler function from the script
  RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")

  # Send the response
  curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
done

function.sh

function handler () {
  EVENT_DATA=$1
  echo "$EVENT_DATA" 1>&2;
  RESPONSE="Echoing request: '$EVENT_DATA'"

  echo $RESPONSE
}

Dockerfile

FROM amazon/aws-lambda-provided:latest

COPY bootstrap /var/runtime/bootstrap
COPY function.sh /var/task/function.sh

RUN chmod 755 /var/runtime/bootstrap /var/task/function.sh

EXPOSE 8080

CMD [ "function.sh.handler" ]

動作確認
ビルドして起動してhttp リクエストを受け付け、JSON をPOST してそのまま返ってきたら成功

$ export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
$ docker build -t ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container:2
docker run -d -p 9000:8080 ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container:2
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"name":"hoge"}'
Echoing request: '{"name":"hoge"}'

作成したイメージをLambda で実行してみる

ビルドしたイメージをpush

$ export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
$ docker push ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container:2

先程作成したLambda が参照しているイメージのタグを :latest から :2 へ変更

実行

動いた

片付け

消すもの - Lambda - CloudWatch Group - ECR

感想

  • Lambda でコンテナを動かすには素のDocker イメージだと使えないのでRIE に対応する必要がある
    • 公式で配布しているDocker イメージを使うか、ライブラリのインストールをする
  • マネージドシェルスク実行サービスがほしかったので良いかなーと思ったけど、CodeBuild なりLambda のカスタムランタイムでよさそう。
    • 雑なシェルスクを動かしたいとき、何も考えずにalpine イメージに乗せて動かしたかった
    • 使うとしたらbootstrap ファイルを用意済みのLambda をイメージをECR に用意して、それをベースイメージとして使用するようにするかなという気持ち
  • 他のLambda オプション同様、一度起動すると一定時間動いてくれる
    • コールドスタートは発生するものの、Lambda の実行毎にpull をしなくてよい。よかった。
  • 「VPC やタスク定義やECS クラスター動かすほどじゃないけど、AWS でコンテナ動かしたい」ときに使います。