re:Invent2018で「Lambda Layers」なるものが発表されました。
これで、私が抱えていた「複数のLambda Functionで共有するものをどうやって同期・更新していくか問題」と「ちょっとしたFunctionで試したいときに外部ライブラリが使えない問題」を解決できるはずです。
参考文献
Lambda Layersとは
Lambda Layers, 複数の関数で共用されるコードやデータをセンタライズし管理するものです
まずはLayer
Layerに moment.js を入れて、Lambda Functionから参照するというのをやってみます。
LayerはFunctionと同じようにzipファイルをアップロードするという形式のようです。
そのzipファイルはドキュメントによると以下のような構成ということです。
Node.js – nodejs/node_modules, nodejs/node8/node_modules (NODE_PATH)
Example AWS X-Ray SDK for Node.jsxray-sdk.zip └ nodejs/node_modules/aws-xray-sdkhttps://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html
これに合わせて、ローカルで準備します。
$ mkdir -p layer/nodejs
$ cd layer/nodejs
$ npm init -y
$ npm install moment
$ cd ../..
$ zip -r ../layer.zip ./
  adding: nodejs/ (stored 0%)
  adding: nodejs/node_modules/ (stored 0%)
  adding: nodejs/node_modules/moment/ (stored 0%)
  adding: nodejs/node_modules/moment/CHANGELOG.md (deflated 71%)
  adding: nodejs/node_modules/moment/ender.js (deflated 5%)
(以下略)
出来上がったzipファイルを適当なS3Bucketにアップロードします。
$ cd ..
$ aws s3 cp ./layer.zip s3://<BUCKET>/
これをデプロイ(Layerもデプロイっていうのかな?)するためのSAMテンプレートを作成します。
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Resources:
  SharedLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: SharedLayer
      Description: include moment
      ContentUri: 's3://<BUCKET>/layer.zip'
      CompatibleRuntimes:
        - nodejs6.10
        - nodejs8.10
      LicenseInfo: 'Available under the MIT-0 license.'
      RetentionPolicy: Retain
Outputs:
  LayerVersionArn:
    Value: !Ref SharedLayer
aws cloudformation deployコマンドでデプロイします。
aws cloudformation deploy \
  --stack-name shared-layer \
  --template-file layer-sam.yaml
作成されたLayerのArnは後で使うので確認しておきます。
$ aws cloudformation describe-stacks --stack-name shared-layer --query "Stacks[].Outputs"
[
    [
        {
            "OutputKey": "LayerVersionArn",
            "OutputValue": "arn:aws:lambda:us-west-2:xxxxxxxxxxxx:layer:SharedLayer:1"
        }
    ]
]
Layerを参照するFunction
Layerにあるmomentを利用するFunctionを作成します。
const moment = require('moment');
exports.handler = async (event) => {
    return {
        moment: moment().format()
    };
}; 
Func1をdeployするためのtemplateファイルをfunctionsディレクトリに作成します。
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
  Func1:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: Func1
      Handler: index.handler
      MemorySize: 128
      Runtime: nodejs8.10
      Timeout: 300
      Layers:
        - "arn:aws:lambda:us-west-2:xxxxxxxxxxxx:layer:SharedLayer:1"
Layersプロパティに先程のCloudFormationで作成したLayerのArnを指定します。
$ aws cloudformation package \
  --template-file app-sam.yaml \
  --s3-bucket <BUCKET> \
  --output-template-file app-output_sam.yaml
app-output_sam.yamlが出来上がっているのでデプロイします。
aws --region us-west-2 cloudformation deploy \
  --stack-name layer-functions \
  --template-file app-output_sam.yaml \
  --capabilities CAPABILITY_IAM
ではFunctionを実行してみましょう。
$ aws lambda invoke --function-name Func1 output.txt
$ cat output.txt
{"moment":"2018-12-02T14:49:29+00:00"}
これで、Lambda Functionのアップロードファイルに含まれていないライブラリが呼べることが確認できました。
ちょっとしたLambda Functionで外部ライブラリ使えない問題
ちょっとしたお試しでLambda FunctionをManagementConsoleから作ったときに、
これまでは外部ライブラリを呼ぶことができませんでした。
Layersを使えばそれもできるはず!と試してみました。
Layersを選択すると「レイヤーの追加」ができるのでクリックする。
先ほど作成したLayerがあるので選択して接続する。
以下のコードを入力して実行してみる。
const moment = require('moment');
exports.handler = async (event) => {
    console.log(moment().format());
    return {};
};
きちんとログに出ていれば成功!
課題
CodeBuild(それ以外のCIでもいいけど)でFunctionをテストするときに、Layerに依存しているFunctionを確実に実行時と同じLayerを適用してテストするにはどうしたもんか。。。


