Closed7

独自にビルドした Docker Image を使って VPC Lambda で Rails のタスクを実行する

snakasnaka

モチベーション

  • Rails アプリケーションのデプロイのパイプラインを構築する際、 いくつかの Rake タスクを実行する場面がある
  • その際、ECS で単発の Task を立ち上げてタスクを実行するのが定石っぽいが、Task の起動はプロビジョニングを含めて 3 分程度かかってしまう
  • Lambda であればもっと簡単に素早く Rake 実行できるのでは?

Serverless Framework で Docker Image を使って Lambda 関数を作成する

参考にした記事

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"]
snakasnaka

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" }'
snakasnaka

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
snakasnaka

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 分程度かかる)

snakasnaka

既存のコンテナイメージを 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が停止していたので開始して試す

成功したっぽい

snakasnaka

Docker Image で ENTRYPOINT を省略しても動作するか?

  • app , sidekiq コンテナは ENTRYPOINT の設定が無く、実行時に Command を与える形になっている
  • Lambda の場合でも同様の形式で Docker Image が実行できるか?
  • Lambda 側で ENTRYPOINT と CMD の上書きができるようになっているの Docker Image 自体に ENTRYPOINT と CMD 無くても良さそう

このスクラップは2022/07/15にクローズされました