Serverless FrameworkでLambdaのコールドスタート対策を行う

# 2020年1月26日 追記
[Serverless Frameworkで行うLambdaのコールドスタート対策](https://qiita.com/keitakn/items/fac324cd44c4374d82e6) という記事を書きました。

今は [Serverless Frameworkで行うLambdaのコールドスタート対策](https://qiita.com/keitakn/items/fac324cd44c4374d82e6) を参考にする事をオススメします。

# この記事で扱うテーマ

AWS Lambdaで発生するコールドスタートを軽減させる為の対策を実施します。

# 前提条件

[Serverless Framework](https://github.com/serverless/serverless) でAWS Lambdaの開発を行っている事。

# コールドスタート？

実業務でAWS Lambdaを使っている方なら分かると思いますが、AWS Lambdaにはコールドスタートという、Lambda関数のレイテンシが極端に悪化する現象が発生します。

コールドスタートは主に以下の原因で発生します。

- 実行可能なコンテナが1つも存在しない時
- 利用可能な数以上の同時リクエストが送信された時
- コードや設定変更を行った時（デプロイを行った時）

このあたりに関しては既に詳しくまとめてある記事がありますので、リンクを共有させて頂きます。

- [全部教えます！
サーバレスアプリのアンチパターン
とチューニング](https://d0.awsstatic.com/events/jp/2017/summit/devday/D4T7-2.pdf)
- [[AWS Summit2017]サーバレスアプリのアンチパターンとチューニングメモ](https://qiita.com/zwirky/items/c42d527a31f93e10cca3)

# 対策

実行可能なコンテナが消えないように定期的にAWS Lambdaを実行する事でコールドスタートの可能性を軽減します。

自分で0から作っても良いのですが、 [こちら](https://serverless.com/blog/keep-your-lambdas-warm/) で紹介されていた、[serverless-plugin-warmup](https://github.com/FidelLimited/serverless-plugin-warmup) というPluginを利用すると楽なので、こちらを利用していきます。

# 導入方法

[serverless-plugin-warmup](https://github.com/FidelLimited/serverless-plugin-warmup) の通りに進めていけば問題ありません。

はじめに `npm install serverless-plugin-warmup --save-dev` を実行してpackageのインストールを行います。

※ yarn を利用している場合は `yarn add serverless-plugin-warmup --dev` でインストールします。

# pluginを読み込ませる設定を追加

`serverless.yml` に以下の設定を追加します。

```yaml:serverless.yml
plugins:
  - serverless-plugin-warmup
```

# IAM Roleの追加

以下の記述を追加します。

既に lambda:InvokeFunction の権限がある場合はこの手順は省略出来ます。

```yaml:serverless.yml
iamRoleStatements:
  - Effect: 'Allow'
    Action:
      - 'lambda:InvokeFunction'
    Resource:
    - Fn::Join:
      - ':'
      - - arn:aws:lambda
        - Ref: AWS::Region
        - Ref: AWS::AccountId
        - function:${self:service}-${opt:stage, self:provider.stage}-*
```

# 定期実行の対象にしたい関数に `warmup: true` を指定

以下のように記述します。（helloを対象にする場合）

```yaml:serverless.yml
functions:
  hello:
    warmup: true
```

これが基本ですが、他にも特定のstageの時だけ定期実行の対象にしたり、定期実行の時間間隔を調整（デフォルトは5分間隔で実行）したり出来ます。

詳しくは [GitHub](https://github.com/FidelLimited/serverless-plugin-warmup) を見ると良いでしょう。

私の場合はデプロイ後に対象の関数を実行する `prewarm` を `true` に設定しました。

```yaml:serverless.yml
custom:
  warmup:
    prewarm: true
```

# デプロイ

設定が終わったら `serverless deploy` を実行します。

デプロイ完了後、マネジメントコンソールでLambda関数の一覧を見ると、 `[サービス名]-[stage]-warmup-plugin` という関数が生成されている事が確認出来ます。

```js:warmup-plugin
"use strict";

/** Generated by Serverless WarmUP Plugin at 2017-11-09T01:54:08.198Z */
const aws = require("aws-sdk");
aws.config.region = "ap-northeast-1";
const lambda = new aws.Lambda();
const functionNames = "[サービス名]-[stage]-[関数名]".split(",");
module.exports.warmUp = (event, context, callback) => {
  let invokes = [];
  let errors = 0;
  console.log("Warm Up Start");
  functionNames.forEach((functionName) => {
    const params = {
      FunctionName: functionName,
      InvocationType: "RequestResponse",
      LogType: "None",
      Qualifier: process.env.SERVERLESS_ALIAS || "$LATEST",
      Payload: JSON.stringify({ source: "serverless-plugin-warmup" })
    };
    invokes.push(lambda.invoke(params).promise().then((data) => {
      console.log("Warm Up Invoke Success: " + functionName, data);
    }, (error) => {
      errors++;
      console.log("Warm Up Invoke Error: " + functionName, error);
    }));
  });
  Promise.all(invokes).then(() => {
    console.log("Warm Up Finished with " + errors + " invoke errors");
    callback();
  });
}
```

`serverless logs` コマンド等で見てみると定期実行されている事が確認出来るかと思います。

# まとめ

以上が [serverless-plugin-warmup](https://github.com/FidelLimited/serverless-plugin-warmup) を利用したコールドスタート軽減対策でした。

これから正式に導入しようと考えているので、また知見が溜まったら何かで共有させて頂きます。

最後まで読んで頂きありがとうございました。