AWS Lambda に Ruby 関数をデプロイするために zip ファイルをつくる
ふとしです。
AWS Lambda が Ruby をサポートしてからもうだいぶん経ってしまいましたが、最近やっと試してみました。そこで、デプロイに少し手間取ったので日記にします。
問題: ネイティブエクステンション起動できない
EC2 などでの実行とは異なり、デプロイ先で bundle install するわけではないので Gem を同梱しなければなりません。そこで環境にあわせてビルドを行うネイティブエクステンションが問題になります。簡単に言うとローカルでビルドしてもデプロイ先で動きません。
例えば Nokogiri では以下のようなエラーになります。
{
"errorMessage": "liblzma.so.5: cannot open shared object file: No such file or directory - /var/task/vendor/bundle/ruby/2.7.0/gems/nokogiri-1.10.10/lib/nokogiri/nokogiri.so",
...
対応: AWS Lambda と同等の環境で用意する
初期化からデプロイまでの包括的な仕組みを提供するフレームワークはありますが、それを使うためにディレクトリ構造をあわせたりしたくありません。
そこで、その中で使われている Docker イメージだけを流用して、AWS Lambda の環境に合わせて zip 化します。
以下のような docker-compose.yml を用意してプロジェクトトップディレクトリに配置して docker-compose up
すると ruby.zip
が雑にできあがります。
version: "3"
services:
app:
image: amazon/aws-sam-cli-build-image-ruby2.7
volumes:
- .:/var/task
command:
- /bin/bash
- -c
- |
# パスで ! フラグを使用出来るようにする
shopt -s extglob
mkdir -p ./.deploy
cd .deploy
ls | grep -v -e vendor | xargs -r rm -fr
cp -r /var/task/!(vendor) /var/task/.deploy
bundle install --path="vendor/bundle"
zip -qr ruby.zip * .bundle
mv ruby.zip /var/task/ruby.zip
ローカルでテストをするために配置した vendor/bundle
をコピーに含めると、すでにインストール済みあると判断してコンテナ環境にあわせたビルドは行われないので注意しましょう。
(.bundle
は同梱しなくても動くはずですが、気持ち悪いので入れています)
おまけ Terraform
デプロイは Terraform で行っています。
data "aws_iam_role" "user" {
name = "common-lambda-executor"
}
resource "aws_lambda_function" "ruby" {
filename = "./ruby.zip"
function_name = "foo"
role = data.aws_iam_role.user.arn
handler = "main.lambda_handler"
source_code_hash = base64sha256(filesha256("./ruby.zip"))
runtime = "ruby2.7"
publish = false
timeout = 600
}
resource "aws_cloudwatch_log_group" "ruby" {
name = "/aws/lambda/${aws_lambda_function.ruby.function_name}"
retention_in_days = 14
}
ロールをいじれるのはなんかこわいという宗教上の理由から、ロールは Terraform でいじれるようにはしていません。
おわりに
これで Gem を同梱しなければならない Ruby 関数も気軽に使うことができるようになりました。
余談ですが、少し気になったのが Gem を使うタイプの関数のコールドスタート時の Init Duration がバイナリをデプロイするタイプの言語よりも大分長いことです。
この現象はなにも require
しない関数では観測できません。
REPORT
RequestId: 4833df14-1864-43ed-b994-09c6063f3072
Duration: 5878.56 ms
Billed Duration: 5879 ms
Memory Size: 1024 MB
Max Memory Used: 774 MB
Init Duration: 448.42 ms
Gem の性質にもよるのでしょうが require による Init Duration の延長現象は Nodejs にもあったので、動的言語を使用する場合は少し気をつけたほうが良いのかもしれません。