Lambda(SAM) の外部モジュールインストールには Layer の requirements を使おう

mull

Lambda の各関数ディレクトリの中にある requirements.txt の中に依存モジュールを書くのではなく、空のレイヤーをつくってそこに requirements.txt だけ書いて置いておこうという話です。

なんで?

例えば Python 選択時の下記のようなsam init後のディレクトリ構成にて、

root
│  .gitignore
│  README.md
│  template.yaml
│
├─events
│  event.json
│
└─hello_world
    │  __init__.py
    │  app.py
    │  requirements.txt  # --> ここに依存モジュールを書きがち
    │
    └─tests
        │  __init__.py
        │  requirements.txt
        |
        ├─integration
        |  __init__.py
        |  test_api_gateway.py
        |  
        └─unit
            __init_.py
            test_handler.py

Lambda 関数のロジックとなるソースファイルが1つしかないのでつい hello_world 内の requirements.txt を使ってしまいがちです。

この場合に考えられる問題は下記のような感じでしょうか。

  1. 他の関数ディレクトリから共通して使用する外部モジュールがあった場合、それぞれがデプロイパッケージを作成して通信するため色々無駄が多い
  2. SAM 側で定義する1つの Function 単位でデプロイパッケージが大きくなりすぎる場合、Lambda のマネジメントコンソール上でコードを参照できなくなる

①に関してはただ無駄なだけならまだいいんですが、SAM の資材として置き場になる S3 の容量も専有すること、毎回のデプロイ所要時間が伸びてしまうこと、など色々困りポイントがあります。

そして②の問題がかなりやっかいで、気軽な print デバッグやコード参照がこれだけのために無効化されるのはストレスも溜まります。

too-large-deploy-package-lambda

こうなる。

これらのせいか、AWS 側でも外部モジュールの使用に際してのベストプラクティスとしてレイヤーの利用を推奨しているっぽいです(自分も今回はじめて気づいた)。

 

この辺りの仕組みが実際どうなっているのかは、sam buildしたあとにプロジェクト内に出来上がる.aws-samディレクトリを見ることで様子が伺えます。デプロイするときに使われるキャッシュのようなファイル群で、最後にビルドしたときの状態がそのままデプロイに利用されていると思って問題ないと思います。

.aws-sam/
├── build/
│   ├── function_1
│   ├── function_2
│   ├── function_3
│   └── template.yaml
└── build.toml

build ディレクトリ以下にビルドされた各関数ディレクトリが並んでおり、依存モジュールを必要としている場合はそれらの実体も含まれているのがわかります。

sam-build-directory

実際にImportModulesLayerというレイヤーに束ねて外部モジュール依存を記述してみると、ここに色々溜まっているのが見て取れますね。

テンプレート的には下記のようになると気持ちいいでしょう!

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: my_function
      CodeUri: function_1/
      Handler: app.lambda_handler
      Runtime: python3.9
      Layers:
        - !Ref ImportModulesLayer  # --> これ

Comments

タイトルとURLをコピーしました