Chalice で Lambda Layer を利用する


問題点

Chalice で1つの app.py 内に以下のようなコードを書いた場合、API Gateway用の Lambda関数 と 定期実行用の2つの Lambda 関数が作成される。 デフォルトの設定だと requirements.txt で指定するすべてのライブラリを各 Lambda 関数内に展開してアップロードするので、2つの Lambda のコードサイズが大きくなる。

app.py
@app.route('/')
def index():
    pass


@app.schedule('rate(5 minutes)')
def cron(event):
    pass

こういった場合には共通ライブラリの配置場所として Lambda Layer を利用できるのだが、昔ドキュメントを読んだときに読み落としていたのか chalice ではまだサポートされていないと思い込んでいた。 改めて調べると、ちゃんと Layer を使うことができたので、そのやり方を示す。

対象コードとバージョン

今回は以下のような cryptography を使って暗号化・復号化を行うだけのサンプルを利用した。

app.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

from chalice import Chalice
from cryptography.fernet import Fernet

from typing import Union, Any

app = Chalice(app_name='layer-sample')


def _encrypt(key: str, text: str, else_value: Any = None) -> Union[str, Any]:
    try:
        fernet = Fernet(key.encode())
        return fernet.encrypt(text.encode()).decode()
    except Exception:
        return else_value


def _decrypt(key: str, encrypted_text: str,
             else_value: Any = None) -> Union[str, Any]:
    try:
        fernet = Fernet(key.encode())
        return fernet.decrypt(encrypted_text.encode()).decode()
    except Exception:
        return else_value


def _encrypt_and_decrypt() -> dict:
    key = Fernet.generate_key().decode()
    message = 'EXAMPLE'
    enctext = _encrypt(key, message)
    return {
        'key': key,
        'message': message,
        'encrypt': enctext,
        'decrypt': _decrypt(key, enctext),
    }


@app.route('/')
def index():
    return _encrypt_and_decrypt()


@app.schedule('rate(5 minutes)')
def cron(event):
    print(_encrypt_and_decrypt())
requirements.txt
cryptography
バージョンなど
$ pipenv --version
pipenv, version 2020.11.15
$ pipenv run chalice --version
chalice 1.22.3, python 3.8.5, linux 5.8.0-45-generic

Lambda Layer なしでデプロイした場合

$ pipenv run chalice deploy
Creating deployment package.
Creating IAM role: layer-sample-dev
Creating lambda function: layer-sample-dev-cron
Creating lambda function: layer-sample-dev
Creating Rest API
Resources deployed:
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:************:function:layer-sample-dev-cron
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:************:function:layer-sample-dev
  - Rest API URL: https://**********.execute-api.ap-northeast-1.amazonaws.com/api/

両方のLambda関数に cryptography ライブラリが含まれており、関数サイズがそれなりに大きいことが分かる。

Lambda Layer ありでデプロイした場合

Lambda Layer にまとめるには .chalice/config.json に以下の通り automatic_layer: true を記載する。

.chalice/config.json
{
  "version": "2.0",
  "app_name": "layer-sample",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "automatic_layer": true
    }
  }
}

これでデプロイすると以下のようになる。

$ pipenv run chalice deploy![SnapCrab_NoName_2021-3-28_17-30-39_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/d71bf63c-f291-c004-683d-db8adb0a9c75.png)

Creating shared layer deployment package.
Creating app deployment package.
Creating lambda layer: layer-sample-dev-managed-layer
Updating policy for IAM role: layer-sample-dev
Updating lambda function: layer-sample-dev-cron
Updating lambda function: layer-sample-dev
Updating rest API
Resources deployed:
  - Lambda Layer ARN: arn:aws:lambda:ap-northeast-1:************:layer:layer-sample-dev-managed-layer:1
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:************:function:layer-sample-dev-cron
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:************:function:layer-sample-dev
  - Rest API URL: https://**********.execute-api.ap-northeast-1.amazonaws.com/api/

# 動くかテスト
$ curl -s https://**********.execute-api.ap-northeast-1.amazonaws.com/api/ | jq .
{
  "key": "WPBwgDQ9dJSgXzhZ4cKLcklGtR6glH70gHY2fcE9PwY=",
  "message": "EXAMPLE",
  "encrypt": "gAAAAABgYD7DFXS2oJuwmBtvwFcz6vEW0bBq-qi970IW9MUxs5L8XFsV7RgUO0NBQDFfQxSuQGf8qt4D9LMbqYrLEp4jBYs6tQ==",
  "decrypt": "EXAMPLE"
}

ライブラリ分は全部新しいレイヤーである arn:aws:lambda:ap-northeast-1:************:layer:layer-sample-dev-managed-layer:1 にデプロイされ、今回作成した Lambda 関数はこのレイヤーを使うようになっている。

別で作成したレイヤーを利用する場合

Lambda Layer は chalice とは関係ないところで作成して使うこともできる。 例えば、過去にSVGデータのラスタライズをうまいこと実施するための方法として検討したことがある。

こういった Lambda Layer を利用するには .chalice/config.jsonlayers オプションを指定すればよい。 例えば、上記記事で作成したレイヤー arn:aws:lambda:ap-northeast-1:************:layer:rsvg-convert:3 を使おうとすれば、以下のように設定を記載する。

.chalice/config.json
{
  "version": "2.0",
  "app_name": "layer-sample",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "automatic_layer": true,
      "layers": [
        "arn:aws:lambda:ap-northeast-1:************:layer:rsvg-convert:3"
      ]
    }
  }
}

これで、"chaliceが自動生成するレイヤー" と "日本語ラスタライズ用のレイヤー" の2つを利用することができる。 なお、Lambda Layer は1関数あたり5個までなので、上限個数には注意のこと。

削除

pipenv run chalice delete で全体を削除すると、ちゃんとLayerも削除される。

まとめ

どうも最近 Cognito を調べていると、うーーーーん…、となることが多いので、改めてどうなんだろうと Firebase Auth を再検討した結果、Layerの利用が可能ということが分かったので、改めてこの記事を書いた。

前に書いた以下の記事では chalice が Lambda Layer をサポートしていないと思い込んでるのでこんなことを書いている。

単純な作業として完結させる場合は Build-in Authorier を使うので良いのですが、firebase-admin のライブラリだけで10MB近くの容量(バイナリビルドが走った場合20MBぐらい)になっています。
認証の1か所にしか使わないライブラリに対してLambdaの容量を喰いたくないので、この部分を CustomAuthorizer として別のLambda関数として切り出すことを考えてみます。

https://qiita.com/t-kigi/items/7fc50f9639c6ece4b62e

が、Layer で共通化できるんならわざわざ分ける必要性はない。 そっちのがシンプルなので。