AWS Lambdaの定期実行をSAMを使って実装する

# 概要
VPCアクセスを利用するLambda Functionでは、ENI生成に伴うレイテンシが発生することがあるため、[定期的にポーリングを行うのがオススメ](http://qiita.com/Keisuke69/items/1d84684f0511a062e968)とされている。
今回はLambdaのScheduled Eventと、SAM(Serverless Application Model)を利用して、定期実行の処理を実装してみる。

# ポーリング部分の実装
自身が保有しているLambda Functionを取得してきて、それぞれInvokeするような処理を実装する。

```js:index.js
import aws from 'aws-sdk';

export const handler = (event, context, callback) => {
    const lambda = new aws.Lambda();

    // 保有しているすべてのLambda Functionを取得してくる
    lambda.listFunctions({}, (error, data) => {
        let invokes = [];
        data.Functions.forEach((lambdaFunction) => {
            // 自分自身は実行しない
            if (lambdaFunction.FunctionName === 'MyFunctionName') {
                return true;
            }

            let invoke = new Promise((resolve, reject) => {
                lambda.invoke({
                    'FunctionName': lambdaFunction.FunctionName,
                    'InvocationType': 'Event',
                    'Qualifier': 'v1'
                }, (error, data) => {
                    if (error !== null) {
                        reject(error);
                        return;
                    }

                    resolve(data);
                });
            });

            invokes.push(invoke);
        });

        // 実行時間がもったいないのでPromise.allで並列処理させる
        Promise.all(invokes).then((data) => {
            context.succeed(data);

        }).catch((error) => {
            context.fail(error);
        });
    });
};
```
定期実行のためのコードはこれだけ。
aws-sdkはLambda実行環境に用意されているため、パッケージを含めなくても利用できる。

# SAMのテンプレートを実装する
```yaml:template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Wake up functions triggered on a timer.
Resources:
  ScheduledFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs4.3
      CodeUri: ./zip/index.zip
      MemorySize: 128
      Timeout: 60
      Role: arn:aws:iam::XXXXXXXXXXXX:role/AmazonAPIGatewayLambdaExecRole
      VpcConfig:
        SecurityGroupIds:
          - sg-xxxxxxxx
        SubnetIds:
          - subnet-XXXXXXXX
          - subnet-XXXXXXXX
      Events:
        Timer:
          Type: Schedule
          Properties:
            Schedule: rate(10 minutes)

```
10分間隔でindex.handlerを実行するテンプレートを作成。
CodeUriの値は、後述するGulpを利用した際にzipが生成されるパスを記載。

# Gulpを利用してcloudformationコマンドを実行する
```
% aws cloudformation package --template-file ./template.yaml --output-template-file ./serverless-output.yaml --s3-bucket s3-backet-name
% aws cloudformation deploy --template-file ./serverless-output.yaml --stack-name wakeup --capabilities CAPABILITY_IAM
```
上記コマンドでS3へのアップロードから、実行環境への反映まで完了するが、Gulpを利用することで毎回の反映プロセスを効率化することができる。
[AWS LambdaファンクションをGulpでデプロイ](http://dev.classmethod.jp/cloud/aws/how-to-deploy-a-lambda-function-with-gulp/)を参考に、cloudformationを利用したgulpfileを作成する。

```js:gulpfile.babel.js
import gulp from 'gulp';
import babel from 'gulp-babel';
import zip from 'gulp-zip';
import del from 'del';
import child from 'child_process'
import runSequence from 'run-sequence';

gulp.task('clean', (callback) => {
    return del(['./dist', './index.zip'], callback);
});

gulp.task('main', () => {
    return gulp.src(['./index.js'])
        .pipe(babel())
        .pipe(gulp.dest('./dist/'));
});

gulp.task('zip', () => {
    return gulp.src(['./dist/**/*', '!./dist/package.json'])
        .pipe(zip('index.zip'))
        .pipe(gulp.dest('./zip/'));
});

gulp.task('cfn-package', (callback) => {
    const command = 'aws cloudformation package --template-file ./template.yaml --output-template-file ./serverless-output.yaml --s3-bucket s3-backet-name';
    child.exec(command, (error, stdout, stderr) => {
        callback(error);
    });
});

gulp.task('cfn-deploy', (callback) => {
    const command = 'aws cloudformation deploy --template-file ./serverless-output.yaml --stack-name wakeup --capabilities CAPABILITY_IAM';
    child.exec(command, (error, stdout, stderr) => {
        callback(error);
    });
});

gulp.task('deploy', (callback) => {
    return runSequence(
        ['clean'],
        ['main'],
        ['zip'],
        ['cfn-package'],
        ['cfn-deploy'],
        callback
    );
});

```
今回テスト関連については省いているのと、deployできるcloudformationのnpmパッケージがエントリー公開時点で提供されていないので、child_processで直接コマンドを実行。
あとはgulpを動かせば、Lambda Functionを定期実行するSchedule Eventが動き出してくれる。

```
% gulp deploy
[19:12:39] Requiring external module babel-core/register
[19:12:40] Using gulpfile ~/Src/lambda/wakeup/gulpfile.babel.js
[19:12:40] Starting 'deploy'...
[19:12:40] Starting 'clean'...
[19:12:40] Finished 'clean' after 9.47 ms
[19:12:40] Starting 'main'...
[19:12:40] Finished 'main' after 121 ms
[19:12:41] Starting 'zip'...
[19:12:41] Finished 'zip' after 21 ms
[19:12:41] Starting 'cfn-package'...
[19:12:42] Finished 'cfn-package' after 1.04 s
[19:12:42] Starting 'cfn-deploy'...
[19:13:24] Finished 'cfn-deploy' after 43 s
[19:13:24] Finished 'deploy' after 44 s
```
cloudformationのdeployは結構時間かかるのが玉に瑕。