top of page
検索
  • 執筆者の写真Naoya Yamashita

Lambda layerとの上手な付き合い方

はじめに

開発部の山下です。


Lambdaはそれ自身にデプロイしたコードだけでなく、Lambda layerのコードも利用できます。 1つのLambda layerは複数のLambdaにアタッチできるため、utilモジュールなどを入れておくと便利だと思うかもしれません。

が、それはやってはいけません。なぜならば「Lambda layerがLambdaのコンテナに展開される」というLambdaの仕組みに依存してしまうため、あなたのコードはローカルで動かなくなってしまいます。

ローカルで動かないコードはローカルでテストできず、修正のたびにAWSにデプロイして動作を確認する必要があります。 エディタによるプログラミング支援も受けられなくなります。具体的には関数の引数が分からなくなり、定義ジャンプ等々の機能が利用できなくなります。


簡単な方針があります。Lambda layerは外部依存コードのみ配置し、自作のコードは一切置かないことです。

この記事ではLambda + Lambda layerという構成のプロジェクトの作り方を提案します。

フォルダ構成

こちらのフォルダ構成を提案します。記事ではステップバイステップでこのプロジェクト構成になるように紹介します。

Lambdaのデプロイ

以下の内容で sample-repo/app/function/b1/src/main.py に保存します。

def handler(event, context) -> dict[str, Any]:
    return {
        "status": "ok",
    }

そして以下の内容を template.yaml に保存します。

AWAWSTemplateFormatVersion: "2010-09-09"

Transform: AWS::Serverless-2016-10-31

Parameters:
  Prefix: { Type: String }

Globals:
  Function:
    Runtime: python3.11

Resources:
  FunctionB1:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}-b1
      CodeUri: ./app/function/b1
      Handler: src.main.handler

このLambdaは以下のコマンドでデプロイできます

sam deploy --stack-name dev-lambda-layer-sample --capabilities CAPABILITY_NAMED_IAM --resolve-s3 --parameter-overrides Prefix=dev-lambda-layer-sample

デプロイ後はこの様にコンソールから実行できます。

外部パッケージの利用

pyyaml をLambdaで利用したくなったとします。 外部パッケージ依存のためLambda layerの出番です。

ローカルの依存解決にはpoetryを利用します。まずレポジトリルートで poetry init します。これで作る .venv がローカルで使う依存コードです。


$ poetry init

This command will guide you through creating your pyproject.toml config.

Package name [aws-lambda-layer-sample]:  
Version [0.1.0]:  
Description []:  
Author [yamashita-optarc <yamashita@optarc.net>, n to skip]:  
License []:  
Compatible Python versions [^3.11]:  ~3.11.0

基本的にデフォルトのままで問題ありませんが、Compatible Python versionsのところだけLambdaのPythonバージョンと合わせる必要があります。 ~3.11.0 で 3.11.x を指定したことになります。3.11系で固定したいのでこのように指定します。

そして pyyaml を普段と同じ様にインストールします。

poetry add pyyaml

main.py を次のように変更します

from typing import Any

import yaml


def yaml_loads(target: str) -> dict[str, Any]:
    return yaml.safe_load(target)


def handler(event: dict[str, Any], context: Any) -> dict[str, Any]:
    target_yaml = '''\
a: 1
b: 2
c: 3
'''
    print(yaml_loads(target_yaml))

    return {
        "status": "ok",
    }

通常のPythonプロジェクトと同じため、エディタによる支援が受けられています。

ローカルテスト

pytest によるローカルテストを行ってみます。 フォルダを作り、Pythonにモジュールと認識してもらうため、各フォルダに __init__.py を作成します。

mkdir -p app/function/test_b1
touch app/function/test_b1/__init__.py
touch app/function/b1/__init__.py
touch app/function/b1/src/__init__.py
touch app/function/__init__.py
touch app/__init__.py

そして以下の内容で app/function/test_b1/test_main.py に保存します。

from app.function.b1.src import main


def test_main1():
    assert main.yaml_loads('a: 1\nb: 2\nc: 3\n') == {'a': 1, 'b': 2, 'c': 3}


def test_main2():
    assert main.yaml_loads('a: "1"\nb: "2"\nc: "3"\n') == {'a': '1', 'b': '2', 'c': '3'}


pytest をインストールし、 pytest を実行するとテストが実行されます。

$ poetry add pytest -D
$ poetry run pytest
================================================== test session starts ==================================================
platform darwin -- Python 3.11.5, pytest-7.4.2, pluggy-1.3.0
rootdir: /Users/conao/dev/optarc/ojt-yamashita-code/prj/aws-lambda-layer-sample
collected 2 items                                                                                                       

app/function/test_b1/test_main.py ..                                                                              [100%]=================================================== 2 passed in 0.02s ===================================================

Lambda layerの作成

pyyamlが入ったLambda layerを作成します。

app/function-layerをLayer置き場ということにして、新しくLambda Layer用のpoetryプロジェクトを作成します。

$ mkdir -p app/function-layer/b1
$ cd app/function-layer/b1
$ poetry init
This command will guide you through creating your pyproject.toml config.

Package name [b1]:  
Version [0.1.0]:  
Description []:  
Author [yamashita-optarc <yamashita@optarc.net>, n to skip]:  
License []:  
Compatible Python versions [^3.11]:  ~3.11.0

そしてレポジトリルートと同じパッケージをインストールします。 Lambda layerとしてAWSにデプロイされるため、いわゆるdevパッケージは必要ありません。( pytest など)

$ poetry add pyyaml

実際のLambda layerを作る方法は少し複雑です(ドキュメント)。そのため以下の内容を Makefile として保存し、 make を使って簡単にパッケージングできるようにします。

all:


.PHONY: init
init: dist/python  ## create lambda layer


dist/python: requirements.txt
	rm -rf dist
	mkdir -p $@
	poetry run pip install -r requirements.txt --target $@
	rm -rf dist/python/boto*


.PHONY: init.docker
init.docker: requirements.txt  ## create lambda layer using x86 docker
	docker run --platform linux/amd64 --rm -v $(CURDIR):$(CURDIR) -w $(CURDIR) python:3.11 pip install -r requirements.txt --target dist/python
	rm -rf dist/python/boto*


requirements.txt: pyproject.toml
	poetry export -f requirements.txt --without-hashes > $@


.PHONY: clean
clean:  ## clean all
	rm -rf python requirements.txt .venv dist

保存できたら make init で dist フォルダができます。

もしM1 Macを使っている場合などでLambdaとCPUアーキテクチャが異なる場合はdockerを利用して作成することもできるようになっています。 その場合は make init.docker を実行します。

Lambda layerのデプロイ

template.yaml を以下に変更します。

AWSTemplateFormatVersion: "2010-09-09"

Transform: AWS::Serverless-2016-10-31

Parameters:
  Prefix: { Type: String }

Globals:
  Function:
    Runtime: python3.11

Resources:
  FunctionLayerB1:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: !Sub ${Prefix}-b1
      ContentUri: ./app/function-layer/b1/dist

  FunctionB1:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}-b1
      CodeUri: ./app/function/b1
      Handler: src.main.handler
      Layers:
        - !Ref FunctionLayerB1

デプロイは先程と同じく以下のコマンドです。

sam deploy --stack-name dev-lambda-layer-sample --capabilities CAPABILITY_NAMED_IAM --resolve-s3 --parameter-overrides Prefix=dev-lambda-layer-sample

コンソールから実行し、 pyyaml が動いていることが確認できました。


この方法を使うことでローカルでの開発体験を損なうことなく、自由にLambdaで外部依存パッケージを使えるようになります。poetryでlockファイルを利用しているためバージョン固定もできています。

ぜひ試してみてください。

閲覧数:764回0件のコメント

最新記事

すべて表示

データ状態により異なるSQLを実行させたい

はじめに 近頃担当する業務は夜間バッチでのデータ更新処理が多く、特にDWH的にテーブル再構築(TRUNCATE/INSERT)のパターンを多く使用しています。 その中でSQLで処理を組み上げる時、エラー処理などで条件分岐で異なるSQLを実行したくなる事は珍しくありません。 多くのシステムでは呼び出し側でSQLの実行結果を参照し、次に実行するSQLを選択/実行していると思います。 また、SQLだけで

bottom of page