- 公開日
- 最終更新日
Lambda Layer に入れたはずのパッケージが各関数にも入っていた話
この記事を共有する
目次
はじめに
こんにちは!パーソル&サーバーワークスの野間です。
以前のブログで、複数の AWS Lambda(以下、Lambda)関数で使う共通コードを Lambda Layer(以下、Layer)にまとめた話をご紹介しました。
その流れで外部ライブラリも Layer にまとめたのですが、しばらくして一部の関数だけ想定と違う動きをするようになりました。
デプロイは成功していて、ローカルのテストも通っていたので、原因の見当がなかなかつきませんでした。
同じところで詰まる方がいそうなので、後学のために記録を残します。
何が起きていたか
きっかけは、共通で使うパッケージを Layer にまとめたことでした。
自作の共通コードは各関数から消して Layer に移したのですが、そのパッケージを使うための requirements.txt の記述は、各関数に残したままでした。
コードの重複は消したのに、依存パッケージの宣言だけが各関数に残っていたことになります。
AWS Serverless Application Model(以下、SAM)で管理しているプロジェクトの構成は、以下のようになっています。
.
├── layers/
│ ├── common/
│ │ └── python/
│ │ └── db_client.py ← 自作共通コード
│ └── dependencies/
│ └── requirements.txt slack-sdk==3.43.0
├── src/
│ ├── example_function_a/
│ │ ├── app.py
│ │ └── requirements.txt slack-sdk==3.27.1
│ └── example_function_b/
│ ├── app.py
│ └── requirements.txt slack-sdk
└── template.yaml
Layer と各関数の両方に slack-sdk が書かれています。
しかもバージョンがそれぞれ違い、片方はバージョン指定すらありません。
この状態でも関数は動きます。
そのため問題として認識されないまま、関数が増えるたびに同じ書き方がコピーされていきました。
デプロイパッケージには同じパッケージが二重に含まれます。
Layer を使う目的のひとつはデプロイパッケージを小さくすることなので、これでは効果が出ません。
本当にまずいのはバージョンのずれ
サイズよりも困るのがこちらです。
Layer 側は 3.43.0、ある関数は 3.27.1 を指定していました。
バージョンを指定していない関数もあり、その関数はビルドのたびに最新版が入る状態でした。
つまり関数ごとに違うバージョンのライブラリで動いていたことになります。
共通コードを Layer にまとめたのに、ライブラリの挙動は関数ごとに違う、という状況です。
さらにやっかいなのは、この不整合がローカルでは見えないことです。
ローカルのテストは仮想環境の 1 セットのパッケージで動くため、関数ごとの差が出ません。
実際に気づいたのは、ある関数だけ通知の書式が変わったときでした。
同じパッケージが Layer と関数の両方にある場合、関数のデプロイパッケージ側(/var/task)が優先されます。
Python のランタイムは sys.path の先頭から順にモジュールを探し、/var/task は /opt/python より前に位置しています。
つまり Layer 側のバージョンは使われず、関数に同梱されたバージョンで動きます。
Layer でライブラリを統一したつもりでも、関数側に同じパッケージが残っていればそちらが読み込まれるため、Layer のバージョン指定は意味を持ちません。
Layer を唯一の定義元にする
直し方は単純で、Layer に入っているパッケージを各関数の requirements.txt から消すだけです。
.
├── layers/
│ ├── common/
│ │ └── python/
│ │ └── db_client.py ← 自作共通コード
│ └── dependencies/
│ └── requirements.txt slack-sdk==3.43.0
├── src/
│ ├── example_function_a/
│ │ ├── app.py
│ │ └── requirements.txt (空)
│ └── example_function_b/
│ ├── app.py
│ └── requirements.txt requests==2.32.3
└── template.yaml
各関数には、その関数だけが使うパッケージを残します。
これで共通パッケージのバージョンは Layer の 1 か所だけになります。
適用後、関数のデプロイパッケージは半分以下になりました。
ただし効果よりも、バージョンを直す場所が 1 か所になったことのほうが価値があると感じています。
同じことを繰り返さないために
このミスは、Layer を導入したときではなく関数を追加したときに生まれます。
既存の関数の requirements.txt をコピーして新しい関数を作ると、そのまま引き継がれるためです。
対策として、私の環境では以下を決めました。
- 共通パッケージは Layer にしか書かない
- 同じパッケージを Layer と関数の両方に書かない
バージョンを固定するか範囲で指定するかは、一概には決められないと感じています。
固定すると更新を自分で追う必要があり、範囲指定にするとビルドした時期で入るものが変わります。
今回のように同じパッケージが 2 か所にある状態で範囲指定を使うと、ずれに気づけないので、まずは重複をなくすことを優先しました。
Layer のディレクトリ構成やパッケージの置き場所は Lambda レイヤーのドキュメント にまとまっています。
まとめ
今回は Layer と各関数で依存パッケージが二重に定義されていた問題を解消しました。
学んだのは、共通化はコードを移すだけでは終わらないということでした。
自作コードの重複は消したつもりでも、それが使うパッケージの宣言は各関数に残っていて、しかもデプロイが通るぶん気づけませんでした。
目に見えるコードの重複より、requirements.txt のような宣言の重複のほうが残りやすいと感じています。
共通コードを Layer にまとめた方は、依存パッケージの宣言も 1 か所になっているか、あわせて確認してみてください。
この記事は私が書きました
野間 太一
記事一覧猫とCloudFormationが好きです。