独自にビルドした Docker Image を使って VPC Lambda で Rails のタスクを実行する
モチベーション
- Rails アプリケーションのデプロイのパイプラインを構築する際、 いくつかの Rake タスクを実行する場面がある
- その際、ECS で単発の Task を立ち上げてタスクを実行するのが定石っぽいが、Task の起動はプロビジョニングを含めて 3 分程度かかってしまう
- Lambda であればもっと簡単に素早く Rake 実行できるのでは?
Serverless Framework で Docker Image を使って Lambda 関数を作成する
参考にした記事
- Container Image Support for AWS Lambda
- AWS Lambda の新機能でサーバーレス・シェルスクリプト! カスタムランタイムのチュートリアルを動かしてみた #reinvent | DevelopersIO
- チュートリアル - カスタムランタイムの公開
Lambda で動作する Docker Image を用意する
エントリーポイントのスクリプト 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 handler() {
EVENT_DATA=$1
echo "${EVENT_DATA}" 1>&2
RESPONSE="Echoing request: '${EVENT_DATA}'"
echo $RESPONSE
}
コンテナイメージの構成を記述した Dockerfile を用意する。関数本体のコードは /var/task
の下に配置しておく前提らしい。
FROM alpine:3.14
WORKDIR /
COPY bootstrap /
COPY function.sh /var/task/
RUN apk add curl
ENTRYPOINT ["/bootstrap"]
CMD ["function.handler"]
Docker Image をビルドする
docker build -t lambda-bash .
ローカルでテストする
Lambda Runtime Interface Emulator をインストールする
aws/aws-lambda-runtime-interface-emulator
mkdir -p ~/.aws-lambda-rie && \
curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie && \
chmod +x ~/.aws-lambda-rie/aws-lambda-rie
コンテナを起動する
- 自前でビルドしたコンテナイメージの中で Lambda Runtime Interface Emulator (lambda-rie) を起動する
docker run -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \
--entrypoint /aws-lambda/aws-lambda-rie \
-e LAMBDA_TASK_ROOT=/var/task \
-e _HANDLER=function.handler \
lambda-bash:latest /bootstrap function.handler
関数の実行リクエストを投げてみる
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{ "hoge": "fuga" }'
レスポンスとして以下のような文字列が返ってきたら呼び出し成功
echoing request: '{ "hoge": "fuga" }'
ECR に Docker Image を登録する
ECRにリポジトリを作成する
aws ecr create-repository \
--repository-name lambda-bash \
--image-scanning-configuration scanOnPush=true
リポジトリの URI を紐付ける
docker tag lambda-bash:latest xxxxxxxxx.dkr.ecr.us-west-2.amazonaws.com/lambda-bash:latest
ECRにログインする
aws ecr get-login-password | \
docker login --username AWS --password-stdin xxxxxxxxx.dkr.ecr.us-west-2.amazonaws.com
Docker Image を ECR に push する
docker push xxxxxxxxx.dkr.ecr.us-west-2.amazonaws.com/lambda-bash:latest
コンテナイメージを実行する Lambda 関数を作成する
以下を参考に Lambda 関数を作成する
re:Invent 2020 で発表された AWS Lambda でのコンテナイメージのサポートを試してみた - あしたのチーム Tech Blog
テストしてみる
テスト結果にパラメタでイベントデータとして渡した JSON が出力されてて良さそう
VPC で Lambda 動かす
上記を VPC でも動かしてみる
VPC 関連の設定を Lambda で行ったがエラーになった権限まわり
VPC 内のリソースにアクセスするための Lambda 関数の設定
- 実行ロールアクセス許可 (Lambda 実行時に必要)
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterfacce
- ユーザーアクセス許可 (Lambda で VPC の設定を行う際に IAM ユーザ側に必要?)
- ec2:DescribeSecurityGroups
- ec2:DescribeSubnets
- eec2:DescribeVpcs
IAM Role を編集して権限を追加する
Lambda の作成ができ、テストも実行できた ( Lambda の更新に 1〜2 分程度かかる)
既存のコンテナイメージを Lambda で動かす
ECRにログイン
aws ecr get-login-password | \
docker login --username AWS --password-stdin xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
既存の Rails アプリケーションの Docker Image をベースとして entrypoint となる bootstrap スクリプトと handler スクリプトを追加した Image を新たに作成する
FROM competency-cloud-app:latest
WORKDIR /
COPY bootstrap /
COPY function.sh /var/task/
ローカルで動くことが確認できた
AWS 上でも既存の Rails のコンテナイメージをベースに Lambda 用の entrypoint (bootstrap) と commmand を上書きしたら動いた
Lograge のファイル出力でエラーになってしまった
- 対応方法として以下が考えられる
- EFS でファイルシステムを作成しておいて Lambda にアタッチする
- Lograge を無効化する
- EFS を利用するためにいろいろ準備が必要となるのと、Lograge のファイル出力先を EFS をマウントしたディレクトリに変更する必要があり結局アプリケーション側の変更が必要になるので、EFS案は一旦却下
- 環境変数を見て lograge を止める方が簡単に対応可能なので、今回はその方向で
独自 entrypoint を仕込んだ Docker image を作る
ROM competency-cloud-app:latest
WORKDIR /
COPY bootstrap /
COPY function.sh /var/task/
ビルドする
docker build --tag lambda-rails-app .
ECR に push する
docker tag lambda-rails-app:latest xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-rails:latest && \
docker push xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-rails:latest
docker run --rm -it xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-rails:latest /bin/bash
実行に失敗した
Mysql2::Error::ConnectionError: Can't connect to MySQL server on 'xxxxxxxxxxxxxxxxxxxx.ap-northeast-1.rds.amaz' (110)
文字列が切れている気がするが...ローカルでも同様のエラー出るが環境変数自体は問題無く設定されているように見えるので表示上の問題だと思いたい...
irb(main):010:0> ActiveRecord::Base.connection
Traceback (most recent call last):
1: from (irb):10
Mysql2::Error::ConnectionError (Can't connect to MySQL server on 'xxxxxxxxxxxxxxxx.ap-northeast-1.rds.amaz' (115))
irb(main):011:0> ENV['DATABASE_URL']
=> "mysql2://root@xxxxxxxxxxxxxxxxx.c5vqq9goystj.ap-northeast-1.rds.amazonaws.com/compeleader"
RDSが停止していたので開始して試す
成功したっぽい
Docker Image で ENTRYPOINT を省略しても動作するか?
-
app
,sidekiq
コンテナはENTRYPOINT
の設定が無く、実行時に Command を与える形になっている - Lambda の場合でも同様の形式で Docker Image が実行できるか?
- Lambda 側で ENTRYPOINT と CMD の上書きができるようになっているの Docker Image 自体に ENTRYPOINT と CMD 無くても良さそう