0
0

More than 1 year has passed since last update.

【Serverless Framework】PythonランタイムのLambdaでrequirements.txtと独自モジュールをレイヤー化する

Last updated at Posted at 2022-12-10

はじめに

Lambda Layerを使うと、複数のLambdaで共通して使用するライブラリとその他の依存関係をパッケージ化することができます。

今回はServerless Frameworkを利用してLayerをデプロイし、開発環境、Lambda環境で利用する方法を解説します。

Layerの基礎知識

押さえておくべきポイントは以下2つです。

Lmabda環境においてLayerは/opt以下に展開される。

Lambda環境のPYTHONPATHには/opt/pythonが含まれている。
※ PYTHONPATHとは、Pythonが import 文で利用するモジュールを探す際のパスです。

以下のようなutils.pyを共通化する場合を想定します。

utils.py
def hello_func():
    return "Hello!"

上記のポイント2つを組み合わせ、Lambda内で/opt/python/utils.pyとなるようにレイヤーを配置することで、このようにLambda内部からLayerを利用することができます。

api.py
from util import hello_func

ディレクトリ構成

今回はLambdaごとにディレクトリを分けた構造を想定しています。

ルートに置いたrequirements.txtに記載したPythonパッケージとlayers/custom_packages/python以下の自前のPythonモジュールをLayer化し、hello_api/api.pyから呼び出します。

開発環境

.
├── hello_api
│   ├── api.py
│   └── serverless.yml
├── layers
│   ├── custom_packages ←ここの名前はなんでも良い
│   │   └── python  ← ここをpythonにすることで/opt/pythonに展開される
│   │       ├── __init__.py
│   │       └── utils.py
│   └── serverless.yml
└── requirements.txt

Lambda環境

.
└── api.py

デプロイ

自前のモジュールをLayer化する

Serverless frameworkにはLayerをデプロイする機能がデフォルトで備わっているため、これをそのまま利用します。

serverless.yml
service: myService
 
provider:
  name: aws
 
layers:
  hello:
    path: layer-dir # required, path to layer contents on disk
    name: ${sls:stage}-layerName # optional, Deployed Lambda layer name
    description: Description of what the lambda layer does # optional, Description to publish to AWS
    compatibleRuntimes: # optional, a list of runtimes this layer is compatible with
      - python3.8
    compatibleArchitectures: # optional, a list of architectures this layer is compatible with
      - x86_64
      - arm64
    licenseInfo: GPLv3 # optional, a string specifying license information
    # allowedAccounts: # optional, a list of AWS account IDs allowed to access this layer.
    #   - '*'
    # note: uncommenting this will give all AWS users access to this layer unconditionally.
    retain: false # optional, false by default. If true, layer versions are not deleted as new ones are created

今回の例では、このようになります。

layers/serverless.yml
service: layers

provider:
  name: aws
  region: ap-northeast-1
  runtime: python3.9

layers:
  utils:
    path: custom_packages
    name: utils
    compatibleRuntimes:
      - python3.9

requirements.txtをLayer化する

serverless-python-requirementsを使うことで、requirements.txtに記述したライブラリを全てLayer化することができます。

layers/serverless.yml
custom:
  pythonRequirements:
    fileName: "../requirements.txt"
    layer:
      name: python-requirements
    compatibleRuntimes:
      - python3.9
plugins:
  - serverless-python-requirements

Layerを利用する

開発環境でLayerを読み込む

pip等でインストールしたPythonパッケージは通常通り読み込むことができます。

hello/api.py
import flask

一方でutils.pyで定義するような自前のモジュールは、Pythonのインポートの制約を受けます。

今回のように全く別のディレクトリで定義したものを呼び出す際はPYTHONPATHを追加する必要があります。

追加方法は環境によりますが、例えばDocker、venvではそれぞれこのようになります。

# Dockerfile
ENV PYTHONPATH "${PYTHONPATH}:/app/layers/custom_packages/python"
# venv/bin/activate
export PYTHONPATH="../../layers/custom_packages/python"

これによりエラーを起こさず読みとれるようになりますが、IDEでは警告が出る場合もあります。
Pycharmでは、画像のように「プロジェクト構造」でpython/に「ソース」を設定することでうまく認識するようになりました。
image.png

LambdaでLayerを読み込む

LambdaからLayerを利用する際は、読み込むLayerを指定する必要があります。

functions:
  hello:
    handler: handler.hello
    layers:
      - arn:aws:lambda:region:XXXXXX:layer:LayerName:Y # arnで指定するパターン

同一のserverless.yml内部であれば、Serverless Frameworkの!Refを使って参照することができます。
この際、タイトルケース化したLayerの名前+LambdaLayerを使って指定します。

layers:
  test: # 参照時のキーはここに依存し、testであればTestになる
    path: layer

functions:
  hello:
    handler: handler.hello
    layers:
      - !Ref TestLambdaLayer # Test + LambdaLayer

しかし、今回はLayerとLambdaで別のserverless.ymlを使っており!Refでは参照できません。
そこで、CludFormationのOutputs機能を利用し、Layer作成時に、Layerのarnを別のCloudFormationのスタックから参照可能にします。

resources:
  Outputs:
    UtilsLayerExport:
      Value:
        Ref: UtilsLambdaLayer # Layerの名前に依存
      Export:
        Name: UtilsLambdaLayer # 自由に変えて良い
    PythonRequirementsLambdaLayerExport:
      Value:
        Ref: PythonRequirementsLambdaLayer # 変更不可
      Export:
        Name: PythonRequirementsLambdaLayer # 自由に変えて良い

Outputした値を読み込むserverless.ymlはこのようになります。

hello_api/serverless.yml
functions:
  api:
    handler: wsgi_handler.handler
    name: hello_api
    layers:
      - 'Fn::ImportValue': UtilsLambdaLayer # layers/serverless.ymlで指定したキー
      - 'Fn::ImportValue': PythonRequirementsLambdaLayer # layers/serverless.ymlで指定したキー

まとめ

これにより、開発環境、Lambda環境のどちらでも共通の処理、ライブラリをまとめることができるようになります。

ここまでの要素をまとめたserverless.ymlは以下の様になります。

layers/serverless.yml
service: layers

provider:
  name: aws
  region: ap-northeast-1
  runtime: python3.9

layers:
  utils:
    path: custom_packages
    name: utils
    compatibleRuntimes:
      - python3.9

custom:
  pythonRequirements:
    fileName: "../requirements.txt"
    layer:
      name: python-requirements
    compatibleRuntimes:
      - python3.9

resources:
  Outputs:
    UtilsLayerExport:
      Value:
        Ref: UtilsLambdaLayer
      Export:
        Name: UtilsLambdaLayer
    PythonRequirementsLambdaLayerExport:
      Value:
        Ref: PythonRequirementsLambdaLayer
      Export:
        Name: PythonRequirementsLambdaLayer

plugins:
  - serverless-python-requirements
hello_api/serverless.yml
functions:
  api:
    handler: wsgi_handler.handler
    name: hello_api
    layers:
      - 'Fn::ImportValue': UtilsLambdaLayer
      - 'Fn::ImportValue': PythonRequirementsLambdaLayer
0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0