Rakeタスクを雑にAWS Lambdaへ持っていく
DBレコードを参照・更新するような既存のRakeタスクを、シュッとLambdaに持っていった時のメモを残しておく。
前提
AWS Lambdaでは/tmp以外のディレクトリにファイルを出力することができず、容量も512MBしかない。Lambda起動時に都度bundle installする選択肢は、所要時間の点でも取るべきではない。
bundle install --path vendor/bundle
した後、vendorディレクトリごとzipで固めると動作させることはできるが、細かい点で特有の手順が必要だったので、メモに残しておく。
方針
- Gemごと固めてzipをアップロードする
- M1 macなのでDocker内に環境を作る
- ログを標準出力のみに変える
- 前述の通りファイル出力が不可なため
- Lambyは使用しない
- 定時実行のみが必要で、エンドポイントを公開したい訳ではないので、今回は導入しなかった
手順
Gemfileの用意
なるべくzipファイルのサイズを減らしたいので、タスク実行に不要なGemを取り除いておく。最終的に二つだけになった。
source "https://rubygems.org"
ruby "3.2.2"
gem "rails", "~> 7.1.3"
gem "mysql2", "~> 0.5"
ログ出力先の修正
Railsのログをファイル出力していると、Rakeタスクの実行時、出力先のパーミッションがチェックされ、エラーになるので修正する必要がある。
config.logger = ActiveSupport::Logger.new(STDOUT)
Lambaで実行するハンドラー関数の作成
Railsアプリのディレクトリ直下で、lambda_function.rbを作成する。Rakeタスクが実行できればなんでもよいが、今回はOpen3を使用した。
require "open3"
def lambda_handler(event:, context:)
o, e = Open3.capture3('bundle exec rails test:foo')
puts o unless o.empty?
unless e.empty?
puts e
raise e
end
'done'
end
Dockerfileの用意
公式で提供されているLambda用のイメージを使用する。
Lambdaでは、システムライブラリのパスがLD_LIBRARY_PATH
という環境変数で定義されている。
yum installしたMySQLのファイルは/lib64/mysql
に配置されるが、LD_LIBRARY_PATH
には含まれないので、エラーになってしまう。
パスが通っている場所にlibmysqlclient.so.18
のシンボリックリンクを作成する必要がある。
FROM public.ecr.aws/lambda/ruby:3.2 AS zip-stage
WORKDIR /var/task
COPY . /var/task
RUN yum -y install gcc make mysql-devel libyaml-devel zip
RUN ln -s /lib64/mysql/libmysqlclient.so.18 .
RUN gem update bundler
RUN bundle config set --local path 'vendor/bundle'
RUN bundle install
RUN zip -r lambda_package.zip .
FROM scratch
COPY /var/task/lambda_package.zip /
CMD ["/bin/bash"]
Dockerビルドの実行
ビルド時に生成されるzipファイルを、ゲストからホストへファイルコピーしている。詳しくはこちら。
DOCKER_BUILDKIT=1 docker build --output . .
Lambdaの設定
zipファイルをアップロードして完成。最低限、下記の環境変数が必要になる。
- DB_HOST
- DB_NAME
- DB_PASSWORD
- DB_USER
- RAILS_ENV
- SECRET_KEY_BASE
Discussion