LambdaのLayerをServerless Frameworkで

# はじめに
本稿は[オープンストリーム Advent Calendar 2018](https://qiita.com/advent-calendar/2018/opst)の14日目です。なんとなくで選んだ日程ですが図らずも昨年と同じ日になりました。
先日のre:Inventにて発表・実装されたAWS LambdaのLayer機能ですが、[Serverless Framework](https://serverless.com/framework/)が既に対応していたので早速試してみました。実行した環境はVirtualBoxにCentOS7を載せたものですが、Node.js(npm)が使えれば環境による違いはないと思われます。
# 導入
Node.jsのバージョンが**6.5.0**以降であることを確認

```bash
node --version
```
古いバージョンの場合はnvmなりyumなりでインストール

Serverless Frameworkのバージョンが**1.34.0**以降であることを確認

```bash
sls --version
```
古いバージョンの場合(未インストールの場合も同じコマンド)

```bash
npm i -g serverless
```

``aws-nodejs``テンプレートを用いてサービスを作成

```bash
sls create --template aws-nodejs --path my-service
```
AWSのクレデンシャルが設定されていることを確認

```bash
aws configure list
```

# Layerの実装
上記の手順で単純なLambda関数をデプロイすることができる状態になりました。ここにLayerを追加していきます。
## モジュールのソースコード
作成するモジュールは単純に引数を二乗する関数で、powという名前にします。
ドキュメント[^layerconf]によると、この場合下記のような配置になります。

```
.
├── handler.js
├── my-layer  ← 任意の名称のディレクトリ(serverless.ymlに記述する)
│   └── nodejs
│       └── node_modules
│           └── pow
│               └── index.js
└── serverless.yml
```

[^layerconf]: https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html#configuration-layers-path

my-layer/nodejs/node_modules/pow/index.js は特筆することはない普通の関数です。

```js
module.exports = i => i * i;
```
## 設定ファイルの記述
serverless.ymlにLayerの定義を記述します。

```yaml
service:
provider:
  name: aws
  runtime: nodejs8.10

functions:
  hello:
    handler: handler.hello
# ここから↓を追加
layers:
  mylayer:
    path: my-layer
```
Layer名を「my-layer」ではなく「mylayer」としている理由は後述します。
## デプロイ
デプロイは従来通りの手順です。

```bash
sls deploy
```
Layerができていることを確認します。

```
aws lambda list-layers
```

```
{
    "Layers": [
        {
            "LayerName": "mylayer", 
            "LayerArn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:mylayer", 
            "LatestMatchingVersion": {
                "LayerVersionArn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:mylayer:1", 
                "Version": 1, 
                "CreatedDate": "2018-12-13T23:59:59.999+0000"
            }
        }
    ]
}
```
# Layerを使う
この状態ではLayerを作ったもののどこからも呼び出されない孤独な存在です。
これからLambda関数にLayerを呼び出す処理を追加します。
## 設定ファイルの記述
関数の方の定義に**layers**を追加します。

```yaml
service:
provider:
  name: aws
  runtime: nodejs8.10

functions:
  hello:
    handler: handler.hello
# ここから
    layers:
      - {Ref: MylayerLambdaLayer}
# ここまで追加
layers:
  mylayer:
    path: my-layer
```
ここで「mylayer」とした理由が現れます。ドキュメント[^slsusinglayer]には
> The name of your layer in the CloudFormation template will be your layer name TitleCased (without spaces) and have LambdaLayer appended to the end. 

とあるのですが、期待した変換が行われません。

- my-layer → MyDashlayerLambdaLayer
- my_layer → MyUnderscorelayerLambdaLayer
 
「MyLayer」とすれば「MyLayerLambdaLayer」となるので、好みやポリシーで命名規約を決めてよいと思います。

[^slsusinglayer]: https://serverless.com/framework/docs/providers/aws/guide/layers#using-your-layers

## Lambda関数の変更
テンプレートから生成されたままのLambda関数(handler.js)をLayerで定義したモジュール(pow)を利用するように書き換えます。

```js
'use strict';
const pow = require('pow');
module.exports.hello = async (event, context) => {
  return pow(event.value);
};
```
REST APIにするつもりはないので普通に値を返すだけの関数にしました。
## 再びデプロイ
変更は以上です。再度デプロイします。手順は変わりません。

```bash
sls deploy
```
デプロイが終えたら、Lambda関数の定義を確認してみます。

```bash
aws lambda get-function-configuration --function-name my-service-dev-hello
```
Layersに作成したLayerが登録されていることが分かります。

```json
{
    "Layers": [
        {
            "CodeSize": 206, 
            "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:mylayer:2"
        }
    ], 
    "FunctionName": "my-service-dev-hello", 
    "LastModified": "2018-12-13T23:59:59.999+0000", 
    "RevisionId": "00000000-1111-2222-3333-444444444444", 
    "MemorySize": 1024, 
    "Version": "$LATEST", 
    "Role": "arn:aws:iam::123456789012:role/my-service-dev-ap-northeast-1-lambdaRole", 
    "Timeout": 6, 
    "Runtime": "nodejs8.10", 
    "TracingConfig": {
        "Mode": "PassThrough"
    }, 
    "CodeSha256": "XXXXXXXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYY=", 
    "Description": "", 
    "VpcConfig": {
        "SubnetIds": [], 
        "VpcId": "", 
        "SecurityGroupIds": []
    }, 
    "CodeSize": 387, 
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:my-service-dev-hello", 
    "Handler": "handler.hello"
}
```
LayerのARNの末尾の数字はバージョン番号で、デプロイの度にインクリメントされていきます。
# 実行
環境は整いました。Layerを利用するLambda関数を実行してみましょう。

```bash
sls invoke --function hello --data '{"value": 2}'
```

```
4
```
無事二乗した値が返ってきました。ちゃんとpowが使える状態になっています。

# まとめ
最近はAWS SAMに押され気味の感がある[^1]Serverless Frameworkですが、Layerのアーカイブ作成の手間が省けるなど、使いどころはあると思いました。何事も適材適所ですね。

[^1]: 主観です

### おまけ
下記のようにpackage.jsonを配置したらdependenciesにあるモジュールを自動的にインストールした状態になってくれないかという淡い期待を持って試しましたが、ダメでした。

```
.
├── handler.js
├── my-layer
│   └── nodejs
│       ├── node_modules
│       │   └── pow
│       │       └── index.js
│       └── package.json
└── serverless.yml
```