SAM(Serverless Application Model) と Lambda Layers

以下のような内容を実現しようとしてみたメモ

  • Lambda の Layer も含めて同一の SAM file で管理したい
  • ステージごとに Layer も分離したい
    • 本番環境と開発環境の Layer は真面目に運用するなら分離したいところ
    • バージョンで分けるというのもあるけど、個人的にはそもそもリソースを分離したい

Directory 構造

以下のように Lambda 関数とレイヤを同一のリポジトリ、および SAM ファイルで管理します

.
├── config.dev
├── config.prod
├── config.template
├── deploy
├── src
│   └── python36
│       ├── hello_world
│       │   ├── __init__.py
│       │   ├── app.py
│       │   └── requirements.txt
│       └── layers
│           └── requests
│               ├── __init__.py
│               ├── python
│               └── requirements.txt
├── template.yaml
└── test
    └── ...(略)...

関数の実装コード

今回は単純に依存ライブラリ requests に依存する簡単な Lambda 関数を考えます

# -*- coding: utf-8 -*-

import requests


def lambda_handler(event, context):
    url = 'https://checkip.amazonaws.com'
    res = requests.get(url)
    return {
        'status_code': res.status_code,
        'ip_addr': res.text.strip(),
    }

Layer とデプロイ

当然 requests への依存解決が必要です。SAM を用いると requirements.txt に反応して依存ライブラリを解決してデプロイパッケージを作成してくれます。しかしながらこれをレイヤーに分離したい場合どのようにすればよいでしょうか。

基本的に SAM は LayerVersion に関しても CodeUri で指定したディレクトリ配下のものを単純に zip で固めてデプロイします。したがって以下のようなデプロイ用の簡単なシェルスクリプトがあると便利です。

S3Bucket=     # deploy target s3 bucket
S3KeyPrefix=  # deploy target key prefix

layers=("requests")
for layer in $layers; do
  pushd src/python36/layers/$layer/
  docker run --rm -v "$PWD":/var/task -w /var/task lambci/lambda:build-python3.6 pip install -r requirements.txt -t python
  popd
done

sam build
sam package --output-template-file packaged.yaml --s3-bucket $S3Bucket --s3-prefix "$S3KeyPrefix"
sam deploy --template-file packaged.yaml --stack-name ${Stage}SamLambdaLayer --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides \
    "STAGE=$stage"

もちろん依存ライブラリ自体は SCM の対象から外したいので、以下のような .gitignore を書いておけばよいでしょう。

.aws-sam

src/*/layers/*/*

!src/*/layers/*/requirements.txt
!src/*/layers/*/__init__.py

SAM ファイル

SAM ファイルは以下のようになります

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
    sam-lambda-layers

Globals:
  Function:
    Timeout: 10
    Runtime: python3.6
    MemorySize: 128

Resources:
  # IAM Role for Lambda Function
  LambdaFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub LambdaFunctionRole
      AssumeRolePolicyDocument:
        Statement:
        - Action:
          - sts:AssumeRole
          Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
        Version: '2012-10-17'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  # Function Layers
  RequestsLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: !Sub Requests
      Description: Requests(python 3.x)
      ContentUri: src/python36/layers/requests
      RetentionPolicy: Retain
      CompatibleRuntimes:
        - python3.6
        - python3.7


  # Lambda Functions and Log Groups
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: HelloWorld
      CodeUri: src/python36/hello_world
      Handler: app.lambda_handler
      Role: !GetAtt LambdaFunctionRole.Arn
      Layers:
        - !Ref RequestsLayer
  HelloWorldLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /aws/lambda/HelloWorld
      RetentionInDays: 7

ステージ管理

本番、開発環境を分離するためには以上の内容について関数名に Prod/Dev などといったプレフィックスをつければ OK。これに伴いデプロイスクリプトを少し変更する必要があります。

いい感じに動くサンプルを 53ningen/sam-lambda-layers にあげておきましたので必要な方はどうぞ。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください