Lambda Layerの基本的な仕組みを確認する #reinvent

re:Invent 2018で発表されたLambda Layerを、今更ながら触ってみました。 その基本的な仕組みについて、これから触る人向けに少し噛み砕いて書いてみます。

Lambda Layerとは

一言で言えば、複数のLambda関数でライブラリを共有できる仕組みです。

これまでは同じライブラリを利用する関数が複数あった場合、全ての関数にいちいちライブラリを含めてパッケージングしていましたが、ライブラリをLayerとしてアップロードしておくことで、個々の関数はLayerを使えばよくなります。

すでに速報記事、検証記事などアップされていますので、ご参照ください。

以下、実際に関数を実行しながら、Lambda Layerの動作の仕組みを確認していきます。

仕組み

詳しい仕組みは公式ドキュメントに記載されています(今のところ英語のみです)
AWS Lambda Layers

ポイントは以下の2つです。

  • Layerのコードは /opt 以下に展開される
  • /opt 以下の特定のパスはあらかじめライブラリ検索パスに設定されている

例としてPython 3.7の場合を見てみます。まず sys.path を表示するだけの簡単なLambda関数を実行してみると、以下のような結果が得られます。/opt/python, /opt/python/lib/python3.7/site-packages が含まれています。

Layerのコードは /opt 以下に展開されますので、PythonのライブラリをLayerにする場合は /opt/python または /opt/python/lib/python3.7/site-packages に展開されるように作ると自然にimportできるようになります。

これを踏まえて、最も簡単なLayerを作ってみます。 zipを展開した時 /opt/python に配置されるように、スクリプトを python/ ディレクトリの下に置き、zipでアーカイブします。

def hello():
    return 'Hello from layer'
$ zip layer.zip python/layer.py
  adding: python/layer.py (deflated 5%)

これをアップロードしてLayerを作ります。今回はAWS CLIでやってみましょう。

$ aws lambda publish-layer-version \
    --layer-name hello \
    --zip-file fileb://layer.zip \
    --compatible-runtimes python3.7
{
    "LayerVersionArn": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:layer:hello:2",
    "Description": "",
    "CreatedDate": "2018-12-10T03:09:15.238+0000",
    "LayerArn": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:layer:hello",
    "Content": {
        "CodeSize": 221,
        "CodeSha256": "HH/77BxLsFnARX1K0fmFRZsiYYJ1SQjsamwAPOawb20=",
        "Location": "...(省略)..."
    },
    "Version": 2,
    "CompatibleRuntimes": [
        "python3.7"
    ]
}

これでLayer "hello"ができました。このLayerを使う簡単な関数を作って実行してみます。 まずは関数のコードです。import layerで先ほど作成したLayerをimportしています。ついでに /opt の中身のリストも出力しています。

import layer
import subprocess

def lambda_handler(event, context):
    opt = subprocess.run(["find", "/opt"], capture_output=True).stdout.decode('utf-8').split('\n')
    return {
        'statusCode': 200,
        'body': layer.hello(),
        'opt': opt
    }

これをアップロードして関数を作ります。--layersで先ほど作成したLayerのARNを指定します。

$ zip sample-use-layer.zip sample-use-layer.py
  adding: sample-use-layer.py (deflated 30%)
$ aws lambda create-function \
    --function-name sample-use-layer \
    --runtime python3.7 \
    --role arn:aws:iam::xxxxxxxxxxxx:role/lambda_basic_execution \
    --handler sample-use-layer.lambda_handler \
    --zip-file fileb://sample-use-layer.zip \
    --layers arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:layer:hello:2

実行すると以下のように動作します。 /opt 以下にLayerのコードが展開されていることがわかります。

$ aws lambda invoke --function-name sample-use-layer output.txt
{
    "ExecutedVersion": "$LATEST",
    "StatusCode": 200
}
~/work/lambda-layer$ cat output.txt | jq
{
  "statusCode": 200,
  "body": "Hello from layer",
  "opt": [
    "/opt",
    "/opt/python",
    "/opt/python/layer.py",
    ""
  ]
}

Pythonを含め、各言語環境についての詳細は以下のドキュメントに記載されていますのでご参照ください。
Including Library Dependencies in a Layer

また、その他のCustom Runtimeで動かすような言語では、エントリポイントであるbootstrapファイルをLayerに分離するのが便利です。 具体例としてはCustom Runtimeのチュートリアルが分かりやすいと思います。チュートリアルではbootstrapを関数に全て含める方法とLayerに分離する方法の2通りを行っています。

まとめ

Lambda Layerの基本的な仕組みを確認しました。関数をたくさん作るような場合には特に有用な機能だと思います。 これからLambda Layerを触る人の参考になれば幸いです。