Lambda Layers をnode.js(SAM)で試してみる

re:Invent2018で「Lambda Layers」なるものが発表されました。
これで、私が抱えていた「複数のLambda Functionで共有するものをどうやって同期・更新していくか問題」と「ちょっとしたFunctionで試したいときに外部ライブラリが使えない問題」を解決できるはずです。

# 参考文献

* [日本語ブログ記事](https://aws.amazon.com/jp/blogs/news/new-for-aws-lambda-use-any-programming-language-and-share-common-components/)
* [Lambda ドキュメント](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html)
* [Cloud Formation ドキュメント(英語)](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-layerversion.html)
* [SAM ドキュメント](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion)

# Lambda Layersとは

> Lambda Layers, 複数の関数で共用されるコードやデータをセンタライズし管理するものです
>
> https://aws.amazon.com/jp/blogs/news/new-for-aws-lambda-use-any-programming-language-and-share-common-components/

# まずはLayer

Layerに [moment.js](http://momentjs.com/) を入れて、Lambda Functionから参照するというのをやってみます。

LayerはFunctionと同じようにzipファイルをアップロードするという形式のようです。
そのzipファイルはドキュメントによると以下のような構成ということです。

> Node.js – nodejs/node_modules, nodejs/node8/node_modules (NODE_PATH)
> Example AWS X-Ray SDK for Node.js
> ```
> xray-sdk.zip
> └ nodejs/node_modules/aws-xray-sdk
> ```
> https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html

これに合わせて、ローカルで準備します。

``` bash
$ 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にアップロードします。

```bash
$ cd ..
$ aws s3 cp ./layer.zip s3://<BUCKET>/

```

これをデプロイ（Layerもデプロイっていうのかな？）するためのSAMテンプレートを作成します。

``` yaml:layer-sam.yaml
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`コマンドでデプロイします。

``` bash
aws cloudformation deploy \
  --stack-name shared-layer \
  --template-file layer-sam.yaml
```

作成されたLayerのArnは後で使うので確認しておきます。

``` bash
$ 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を作成します。

``` javascript:functions/Func1/index.js
const moment = require('moment');
exports.handler = async (event) => {
    return {
        moment: moment().format()
    };
}; 
```

Func1をdeployするためのtemplateファイルを`functions`ディレクトリに作成します。

``` yaml:functions/app-sam.yaml
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を指定します。

``` bash
$ aws cloudformation package \
  --template-file app-sam.yaml \
  --s3-bucket <BUCKET> \
  --output-template-file app-output_sam.yaml
```

app-output_sam.yamlが出来上がっているのでデプロイします。

``` bash
aws --region us-west-2 cloudformation deploy \
  --stack-name layer-functions \
  --template-file app-output_sam.yaml \
  --capabilities CAPABILITY_IAM
```

ではFunctionを実行してみましょう。

``` bash
$ 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を使えばそれもできるはず！と試してみました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117772/e931b79e-ae9f-35dd-3070-d783b6d9624b.png)

Layersを選択すると「レイヤーの追加」ができるのでクリックする。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117772/ec28ad4c-7b1b-b0dd-a817-5d037a375f88.png)

先ほど作成したLayerがあるので選択して接続する。

以下のコードを入力して実行してみる。

``` javascript
const moment = require('moment');
exports.handler = async (event) => {
    console.log(moment().format());
    return {};
};
```

きちんとログに出ていれば成功！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117772/90ac7928-9f5b-b1eb-aa8e-64c14b968910.png)



# 課題

CodeBuild（それ以外のCIでもいいけど）でFunctionをテストするときに、Layerに依存しているFunctionを確実に実行時と同じLayerを適用してテストするにはどうしたもんか。。。