AWS LambdaのRuby .zipパッケージでgitから取得したgemを使う
AWS LambdaでRubyランタイムを使っててzipアーカイブで関数コードをアップロードしてる人向け。
基本的にはGemfileに依存関係書いてbundle config set --local path 'vendor/bundle'
してbundle install
すればいい。以下のドキュメントを読もう。
んだけど、Gemfileにrubygems.org以外から取得したgem、特にgit
を指定したものを使っている場合にこれだけだとうまくいかないので、原因と対処を書いておく。
そもそもLambdaは何をやってるか
上述ドキュメントを読むとわかるが、zipファイルにはGemfile.lockもGemfileも入れない。それで動作する。これはどういうことかというと、AWS Lambdaのランタイム側ではBundlerを使わず(?)、vendor/bundle
以下にLambdaが想定しているパスで置かれているファイルを直接探索するように$LOAD_PATH
が指定されてるんじゃないかと思う。*1
で、通常rubygems.orgから取得したgemについては vendor/bundle/ruby/3.2.0/gems/
以下にmyclient-1.1.0
みたいなディレクトリでコード等が展開される。またvendor/bundle/ruby/3.2.0/specifications/
ディレクトリにgemspecファイルがmyclient-1.1.0.gemspec
として置かれる。試してみていた範囲では、gemspecを検出したらそれに対応するgemのディレクトリを$LOAD_PATH
に追加してるんじゃないかと思う。
gitから取得したgemの扱い
gitから取得したgemについてはBundlerはrubygems.orgからのものとは異なり、vendor/bundle/ruby/3.2.0/bundler/gems/
以下にmyclient-xxxxxxx
のようなディレクトリで展開される。xxxxxの部分はcommit hash。
これはLambdaランタイムが読み込むディレクトリと異なっているため、zipファイルに含まれていても実行時にロードパスが通らず、requireしてもエラーになる。なんということでしょう。
無理矢理パスを通す
しょうがないので、以下のようにしてzipアーカイブを作れば動くようになる。
1. ruby/3.2.0/gems
以下にシンボリックリンクを作成する
bundle install
してgitから取得したgemが展開されたら、そのディレクトリに対して以下のようにシンボリックリンクを作る。
$ ln -s vendor/bundle/ruby/3.2.0/bundler/gems/myclient-xxxxxx vendor/bundle/ruby/3.2.0/gems/myclient-1.1.0
こうすれば手元で実行するときにも壊れない。zipアーカイブを作るときは*2シンボリックリンクだった部分はそこにコピーされたようになるはず。つまり、Lambda環境ではruby/3.2.0/gems
以下に該当gemのコピーが展開される。
2. specifications
以下にgemspecをコピーする
gemspecがないと動かないのでこれもオリジナルの場所からコピーする。コピー時にバージョン番号をつけたファイルにするのに注意。
$ cp vendor/bundle/ruby/3.2.0/bundler/gems/myclient-xxxxx/myclient.gemspec vendor/bundle/ruby/3.2.0/specifications/myclient-1.1.0.gemspec
(余談) pathで指定したgemはどうなる?
Gemfileではgitで指定する他にもpathでローカルパスにあるgemを使うよう指定できる。開発中のgemを直接使うときに便利。
が、この指定だとBundlerは該当パスを直接見に行ってライブラリをロードし、vendor/bundle
以下にはコピーを作らない。このため、zipアーカイブにしようとしても入ってくれず、Lambdaランタイム上では使いようがないということになる。
ローカルストレージ上のものをコピーしてzipアーカイブに入れてもいいけど、何が起きるかちょっとわからないのが怖い気もするので、開発中とはいえgitリポジトリ経由で入れるようにしたほうがいいかなと思います。