AWS CDKでLambdaデプロイしてみた【AWS CDK】

# TL; DR
社内でInfrastructure as Codeを推進しているのですが、元々はTerraformでやっていました。（実際既に運用ベースに乗っているものもあります。）
しかしある日、こんなワードを聞いてしまったのです。。
「インフラ強い会社は今だいたいCDKっすよ。」…ドキッとしてしまいました。
彼は勉強熱心で社外の色々な勉強会にも参加しており、雷に打たれた思いでした。
その日のうちにCDKに移行することを決め、翌日からちまちま書き始めた経緯です。

### Advent Calendarの担当者通知
前職でAdvent Calenderに取り組んだときの記事です。
始まってからリマインダーなどの通知を仕込むことを失念していることに気付きさくっとやってみました。

通知してくれるLambdaをCDKでデプロイするまでやってみようと思います。

アーキ図的なもの
![cdk-lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/1de8cc5e-c701-0b0d-6abd-f15258dcc7cc.png)

# CDKの基本
生成された`README.md`には下記記載があります。

 * `npm run build`   compile typescript to js
 * `npm run watch`   watch for changes and compile
 * `npm run test`    perform the jest unit tests
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template

まあ見ての通りですが、これ以外にも後述する`cdk bootstrap`というデプロイ時に生成されるCloudFormation定義を格納しておくS3バケットなどを作ってくれるコマンドは必須なので覚えておきましょう。


### おまじない
最初のあれですね。
CDKは割とTypeScriptが公式感あるのでTypeScriptで書いていきます。
ちなみにエディタはVSCodeだと特にプラグインなくてもめちゃくちゃ恩恵受けられるのでオススメです。（公式も力入れてるっぽい）

```bash
$ cdk --version
1.17.1 (build fa4cb1f)
$ 
$ cdk init app --language=typescript
`cdk init` cannot be run in a non-empty directory!
```

いきなりエラーw
はい、空じゃないと怒られてしまうのできれいきれいなディレクトリでやりましょう。

```bash
$ cdk init app --language typescript
Applying project template app for typescript
Initializing a new git repository...
Executing npm install...
npm WARN deprecated fsevents@1.2.9: One of your dependencies needs to upgrade to fsevents v2: 1) Proper nodejs v10+ support 2) No more fetching binaries from AWS, smaller package size
npm WARN deprecated left-pad@1.3.0: use String.prototype.padStart()
node-pre-gyp WARN Using request for node-pre-gyp https download 
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN sub@0.1.0 No repository field.
npm WARN sub@0.1.0 No license field.

# Useful commands

 * `npm run build`   compile typescript to js
 * `npm run watch`   watch for changes and compile
 * `npm run test`    perform the jest unit tests
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template
```

今度は問題なく初期化が実施されました。
色々なファイルやらなんやらが生成されています。

```bash:files
$ pwd
/Users/user/Documents/GitHub/cdk-lambda
$
$ ls -l
total 504
drwxr-xr-x   15 user  staff     480 12  1 22:51 .
drwx------+  33 user  staff    1056 12  1 22:50 ..
drwxr-xr-x   12 user  staff     384 12  1 22:51 .git
-rw-r--r--    1 user  staff      93 12  1 22:51 .gitignore
-rw-r--r--    1 user  staff      65 12  1 22:51 .npmignore
-rw-r--r--    1 user  staff     369 12  1 22:51 README.md
drwxr-xr-x    3 user  staff      96 12  1 22:51 bin
-rw-r--r--    1 user  staff     112 12  1 22:51 cdk.json
-rw-r--r--    1 user  staff     159 12  1 22:51 jest.config.js
drwxr-xr-x    3 user  staff      96 12  1 22:51 lib
drwxr-xr-x  467 user  staff   14944 12  1 22:51 node_modules
-rw-r--r--    1 user  staff  227570 12  1 22:51 package-lock.json
-rw-r--r--    1 user  staff     525 12  1 22:51 package.json
drwxr-xr-x    3 user  staff      96 12  1 22:51 test
-rw-r--r--    1 user  staff     596 12  1 22:51 tsconfig.json
$
$ tree -I node_modules
.
├── README.md
├── bin
│   └── cdk-lambda.ts
├── cdk.json
├── jest.config.js
├── lib
│   └── cdk-lambda-stack.ts
├── package-lock.json
├── package.json
├── test
│   └── cdk-lambda.test.ts
└── tsconfig.json

```

`init`コマンドで生成されるいくつかのファイルにはデフォルトでディレクトリ名が振られます。

<details><summary>ファイルの中身はこんな感じ。</summary><div>

```ts:cdk-lambda-stack.ts
// CloudFormationのStackと同様にインフラを定義したファイル

import cdk = require('@aws-cdk/core');

export class CdkLambdaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
  }
}
```

```ts:cdk-lambda.ts
// cdk build 実施時に呼び出されるファイル
#!/usr/bin/env node
import 'source-map-support/register';
import cdk = require('@aws-cdk/core');
import { CdkLambdaStack } from '../lib/cdk-lambda-stack';

const app = new cdk.App();
new CdkLambdaStack(app, 'CdkLambdaStack');
```

```json:tsconfig.json
// TypeScriptの設定ファイル
{
  "compilerOptions": {
    "target":"ES2018",
    "module": "commonjs",
    "lib": ["es2018"],
    "declaration": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": false,
    "inlineSourceMap": true,
    "inlineSources": true,
    "experimentalDecorators": true,
    "strictPropertyInitialization":false,
    "typeRoots": ["./node_modules/@types"]
  },
  "exclude": ["cdk.out"]
}
```

```json:cdk.json
// CDKの設定ファイル(環境変数などで使えるが、今回は使わない)
{
  "app": "npx ts-node bin/sub.ts",
  "context": {
    "@aws-cdk/core:enableStackNameDuplicates": "true"
  }
}
```

</div></details>

# Stackを書いていく
さきほど`init`コマンドで生成された`lib/index.ts`がリソース定義に当たるので編集していきます。
が、その前に`init`打ったばっかりだと`@aws-cdk/core`しかないのでエディタに怒られ続けるので先にインストールしておきましょう。

今回はLambda関数を作成するのとSSMを利用、IAMロールの作成、Cloudwatch eventでのトリガーを作成するため以下が必要になります。

```bash:npm&nbsp;install
$ npm install @aws-cdk/aws-lambda @aws-cdk/aws-ssm @aws-cdk/aws-iam @aws-cdk/aws-events @aws-cdk/aws-events-targets
npm WARN cdk-lambda@0.1.0 No repository field.
npm WARN cdk-lambda@0.1.0 No license field.

+ @aws-cdk/aws-events-targets@1.18.0
+ @aws-cdk/aws-lambda@1.18.0
+ @aws-cdk/aws-ssm@1.18.0
+ @aws-cdk/aws-events@1.18.0
+ @aws-cdk/aws-iam@1.18.0
added 40 packages from 4 contributors and audited 1764654 packages in 13.232s

8 packages are looking for funding.
Run "npm fund" to find out more.
```


Nodeのv8系はサポート切れてしまうのでv10系以上が推奨なので、Runtimeも`NODEJS_10_X`に設定します。
またLambdaのソースを参照する部分では`AssetCode`クラスを使ってあげると、ローカルファイルをよきに計らって色々してくれるのでこっちにしましょう。ZIPにするのはめんどくさい。

```ts:lambda.Function
    const adventarNotifyFunction = new Function(this, 'SlackAdventar', {
      code: AssetCode.fromAsset('src'),  //ここで指定するとZIP化してUploadまでしてくれる
```

書きあがったコードはこちら

```ts:cdk-lambda-stack.ts
import cdk = require('@aws-cdk/core');
import { Function, AssetCode, Runtime } from '@aws-cdk/aws-lambda';
import { Role, ServicePrincipal, ManagedPolicy } from '@aws-cdk/aws-iam';
import { Rule, Schedule } from '@aws-cdk/aws-events';
import { LambdaFunction } from '@aws-cdk/aws-events-targets';
import { StringParameter } from '@aws-cdk/aws-ssm';

export class CdkLambdaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // IAM Role for Lambda with SSM policy.
    const executionLambdaRole = new Role(this, 'secureLambdaRole', {
      roleName: 'lambdaSecureExecutionRole',
      assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
      managedPolicies: [
        ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
        ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMReadOnlyAccess'),
      ]
    });

    // Call Slack channel by plain text.
    const slackChannel = StringParameter.fromStringParameterAttributes(this, 'slackChannel', {
      parameterName: '/Lambda/production/slack-channel',
    });

    // Configure for Lambda function.
    const adventarNotifyFunction = new Function(this, 'SlackAdventar', {
      functionName: 'slack-adventar-notify',
      runtime: Runtime.NODEJS_10_X,
      code: AssetCode.fromAsset('src'),
      handler: 'index.handler',
      timeout: cdk.Duration.seconds(300),
      role: executionLambdaRole,
      environment: {
        TZ: "Asia/Tokyo",
        SLACK_CHANNEL: slackChannel.stringValue,
      }
    });

    // Cloudwatch event for run notification at 1PM JST in Advent Calendar term.
    const rule = new Rule(this, 'Rule', {
      schedule: Schedule.expression('cron(0 4 1-25 12 ? 2019)')
    });

    rule.addTarget(new LambdaFunction(adventarNotifyFunction));
  }
}

const app = new cdk.App();
new CdkLambdaStack(app, 'adventarNotification');
app.synth();
```

ざっとこんな感じです。
今回はリソース量が大した量ではないので、1ファイルにまとめてしまいました。

# SSMを登録する
SlackチャンネルとWebhookはSSMのパラメータストアから呼び出すようにしたいので、下記コマンドで個別に登録していきます。

|環境変数|属性|
|---|---|
|SLACK_CHANNEL|String|
|SLACK_MEMBERS|StringList|
|SLACK_WEBHOOK|SecureString|

```bash
$ aws ssm put-parameter \
>   --type 'String' \
>   --name '/Lambda/production/slack-channel' \
>   --value '$CHANNEL_NAME' #Slackのチャンネル名
{
    "Version": 1,
    "Tier": "Standard"
}
$ 
$ 
$ # https://github.com/aws/aws-cli/issues/3076
$ aws ssm put-parameter \
>   --cli-input-json '{"Name": "/Lambda/production/slack-webhook", "Type": "SecureString",  "Value": "$WEBHOOK_URL"}'  #SlackのWebhook URL
{
    "Version": 1,
    "Tier": "Standard"
}
$ 
$ 
$ aws ssm put-parameter \
>   --type 'StringList' \
>   --name '/Lambda/production/slack-members' \
>   --value "dummy,hogehoge,fugafuga,hogefuga,..."  #SlackのユーザーID
{
    "Version": 1,
    "Tier": "Standard"
}
```

上記のように設定し、`Webhook`などのクレデンシャルはお漏らししないように気をつけます。

# Lambdaを書いていく
単純なSlack通知なので、特にこれといった面白みはないのですが…一応書いていきます。
ちょいハマりポイントとしては以下でした。

 - Slackのメンションの指定方法が`<@username>` -> `<@user_id>`に変わってた。

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -> [A lingering farewell to the username
](https://api.slack.com/changelog/2017-09-the-one-about-usernames)

 - SSMで設定したWebhookとチャンネル名の呼び出し方

`src/index.js`を作成して、中に処理を記述していきます。

```js:src/lambda/index.js
const request = require('request');
const AWS = require('aws-sdk');
const ssm = new AWS.SSM();

exports.handler = async function(event, context) {

  // Get WebHook URL from SSM parameter store.
  const slackWebhook = await ssm.getParameter({
    Name: '/Lambda/production/slack-webhook',
    WithDecryption: true,
  }).promise();
  let SLACK_WEBHOOK = slackWebhook.Parameter.Value;

  // Get user_id from SSM parameter store.
  const slackMember = await ssm.getParameter({
      Name: '/Lambda/production/slack-members',
      WithDecryption: true,
  }).promise();
  let member = slackMember.Parameter.Value.split(',');

  // Inject SLACK_CHANNEL value.
  const SLACK_CHANNEL = process.env.SLACK_CHANNEL;

  // create message
  const now = new Date();
  const Today =  now.getDate();
  const mention = "<@" + member[Today] + ">";
  let message = mention + "\n";
  message += '*' + "Advent Calendar " + Today + "日目 よろしくお願いします！" + '*' + "\n";
  message += 'https://adventar.org/calendars/4548';

  // Request configure
  const options = {
    url: SLACK_WEBHOOK,
    headers: {
      'Content-type': 'application/json'
    },
    body: {
      "text": message,
      "channel": SLACK_CHANNEL,
      "username": "uluru",
      "icon_emoji": "uluru",
    },
    json: true
  };

  // Sending message
  request.post(options, function(error, response, body) {
    if (!error && response.statusCode == 200) {
      context.done(null, body);
    } else {
      console.log('error: ' + response.statusCode);
      context.done(null, 'error');
    }
  });
}
```

ざっとこんな感じです。
特に面白みもなく。笑

注意すべき点は`src`配下に必要なパッケージをインストールあげないといけないところですね。
今回は`request`が必要なのでrootディレクトリで下記を実施しています。

```bash:npm&nbsp;install&nbsp;--prefix
$ npm install --prefix ./src request
npm WARN saveError ENOENT: no such file or directory, open '/Users/user/Documents/GitHub/cdk-lambda/src/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/Users/user/Documents/GitHub/cdk-lambda/src/package.json'
npm WARN src No description
npm WARN src No repository field.
npm WARN src No README data
npm WARN src No license field.

+ request@2.88.0
added 48 packages from 59 contributors and audited 63 packages in 2.997s
```

# デプロイ
コードが書き終わったのでデプロイしていきたいと思います。
まずは`cdk bootstrap`でS3バケットを作成していきます。
その後`npm run build`でコンパイルを実施して、`cdk deploy`でリソースを作成していきます。

### cdk bootstrap
対象ディレクトリで`cdk bootstrap`と打つだけです。

```bash:cdk&nbsp;bootstrap
$ pwd
/Users/user/Documents/GitHub/cdk-lambda

# failed
$ cdk bootstrap
⨯ Unable to compile TypeScript:
lib/cdk-lambda-stack.ts:40:16 - error TS2304: Cannot find name 'lambda'.

40       runtime: lambda.Runtime.NODEJS_10_X,
                  ~~~~~~

Subprocess exited with error 1

$ cdk bootstrap
ENOENT: no such file or directory, open '../src/index.js'
Subprocess exited with error 1

# Success
$ cdk bootstrap
 ⏳  Bootstrapping environment aws://608092561803/us-east-1...
CDKToolkit: creating CloudFormation changeset...
 0/2 | 22:41:38 | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket 
 0/2 | 22:41:39 | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket Resource creation Initiated
 1/2 | 22:42:01 | CREATE_COMPLETE      | AWS::S3::Bucket | StagingBucket 
 2/2 | 22:42:03 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | CDKToolkit 
 ✅  Environment aws://608092561803/us-east-1 bootstrapped.
```

この時点でエラーがあると教えてくれます。
問題なければS3バケットとCloudFormationのスタックが作成されます。
<img width="1380" alt="sc_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/1d42ae7d-c10f-b982-b953-7b7a8fe38c8f.png">

### npm run build
では続いてコンパイルするために`npm run build`を実施します。
コンパイラはTypeScriptなので`tsc`が走ります。

```json:cdk.json
{
  "app": "npx ts-node bin/cdk-lambda.ts"
}
```

```bash
$ npm run build

> cdk-lambda@0.1.0 build /Users/user/Documents/GitHub/cdk-lambda
> tsc
```

特に何か出力される訳ではないのですが、jsファイルに変換されたmonoga`lib` / `bin`配下にできます。
また`cdk.out`というディレクトリが作成され、CloudFormationの定義のようなものが作成されます。

<details><summary>ファイルの中身はこんな感じ。</summary><div>

```js:cdk-lambda.js
#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("source-map-support/register");
const cdk = require("@aws-cdk/core");
const cdk_lambda_stack_1 = require("../lib/cdk-lambda-stack");
const app = new cdk.App();
new cdk_lambda_stack_1.CdkLambdaStack(app, 'CdkLambdaStack');
//# sourceMappingURL=data:application/json;base64,hogehogehogefugafugafugafugafuga
```

```js:cdk-lambda-stack.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const cdk = require("@aws-cdk/core");
const aws_lambda_1 = require("@aws-cdk/aws-lambda");
const aws_iam_1 = require("@aws-cdk/aws-iam");
const aws_events_1 = require("@aws-cdk/aws-events");
const aws_events_targets_1 = require("@aws-cdk/aws-events-targets");
const aws_ssm_1 = require("@aws-cdk/aws-ssm");


const fs = require("fs");
class CdkLambdaStack extends cdk.Stack {
    constructor(scope, id, props) {
        super(scope, id, props);
        // The code that defines your stack goes here
        // IAM Role for Lambda with SSM policy.
        const executionLambdaRole = new aws_iam_1.Role(this, 'secureLambdaRole', {
            roleName: 'lambdaSecureExecutionRole',
            assumedBy: new aws_iam_1.ServicePrincipal('lambda.amazonaws.com'),
            managedPolicies: [
                aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
                aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMReadOnlyAccess'),
            ]
        });
        // Call Slack channel by plain text.
        const slackChannel = aws_ssm_1.StringParameter.fromStringParameterAttributes(this, 'slackChannel', {
            parameterName: '/Lambda/production/slack-channel',
        });
        // Configure for Lambda function.
        const adventarNotifyFunction = new aws_lambda_1.Function(this, 'SlackAdventar', {
            functionName: 'slack-adventar-notify',
            runtime: aws_lambda_1.Runtime.NODEJS_10_X,
            code: new aws_lambda_1.InlineCode(fs.readFileSync('./src/index.js', { encoding: 'utf-8' })),
            handler: 'index.handler',
            timeout: cdk.Duration.seconds(300),
            role: executionLambdaRole,
            environment: {
                TZ: "Asia/Tokyo",
                SLACK_CHANNEL: slackChannel.stringValue,
            }
        });
        // Cloudwatch event for run notification at 1PM JST in Advent Calendar term.
        const rule = new aws_events_1.Rule(this, 'Rule', {
            schedule: aws_events_1.Schedule.expression('cron(0 4 1-25 12 * 2019)')
        });
        rule.addTarget(new aws_events_targets_1.LambdaFunction(adventarNotifyFunction));
    }
}
exports.CdkLambdaStack = CdkLambdaStack;
const app = new cdk.App();
new CdkLambdaStack(app, 'adventarNotification');
app.synth();
//# sourceMappingURL=data:application/json;base64,hogehogehogefugafugafuga
```

```ts:cdk-lambda-stack.d.ts
import cdk = require('@aws-cdk/core');
export declare class CdkLambdaStack extends cdk.Stack {
    constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps);
}
```

```bash:cdk.out
 ls -l cdk.out
total 80
-rw-r--r--  1 user  staff   4902 12  1 22:41 CdkLambdaStack.template.json
-rw-r--r--  1 user  staff   4938 12  1 22:41 adventarNotification.template.json
-rw-r--r--  1 user  staff     20 12  1 22:41 cdk.out
-rw-r--r--  1 user  staff  11674 12  1 22:41 manifest.json
-rw-r--r--  1 user  staff   7352 12  1 22:41 tree.json
```

```json:CdkLambdaStack.template.json
{
  "Resources": {
    "secureLambdaRole067C996A": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Effect": "Allow",
              "Principal": {
                "Service": "lambda.amazonaws.com"
              }
            }
          ],
          "Version": "2012-10-17"
        },
        "ManagedPolicyArns": [
          {
            "Fn::Join": [
              "",
              [
                "arn:",
                {
                  "Ref": "AWS::Partition"
                },
                ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
              ]
            ]
          },
          {
            "Fn::Join": [
              "",
              [
                "arn:",
                {
                  "Ref": "AWS::Partition"
                },
                ":iam::aws:policy/AmazonSSMReadOnlyAccess"
              ]
            ]
          }
        ],
        "RoleName": "lambdaSecureExecutionRole"
      },
      "Metadata": {
        "aws:cdk:path": "CdkLambdaStack/secureLambdaRole/Resource"
      }
    },
    "SlackAdventar6AF5ACBE": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "ZipFile": "const request = require('request');\nconst AWS = require('aws-sdk');\nconst ssm = new AWS.SSM();\n\nexports.handler = async function(event, context) {\n\n  // Get WebHook URL from SSM parameter store.\n  const slackWebhook = await ssm.getParameter({\n    Name: '/Lambda/production/slack-webhook',\n    WithDecryption: true,\n  }).promise();\n  let SLACK_WEBHOOK = slackWebhook.Parameter.Value;\n\n  // Get user_id from SSM parameter store.\n  const slackMember = await ssm.getParameter({\n      Name: '/Lambda/production/slack-members',\n      WithDecryption: true,\n  }).promise();\n  let member = slackMember.Parameter.Value.split(',');\n\n  // Inject SLACK_CHANNEL value.\n  const SLACK_CHANNEL = process.env.SLACK_CHANNEL;\n\n  // create message\n  const now = new Date();\n  const Today =  now.getDate();\n  const mention = \"<@\" + member[Today] + \">\";\n  let message = mention + \"\\n\";\n  message += '*' + \"Advent Calendar \" + Today + \"日目 よろしくお願いします！\" + '*' + \"\\n\";\n  message += 'https://adventar.org/calendars/4548';\n\n  // Request configure\n  const options = {\n    url: SLACK_WEBHOOK,\n    headers: {\n      'Content-type': 'application/json'\n    },\n    body: {\n      \"text\": message,\n      \"channel\": SLACK_CHANNEL,\n      \"username\": \"uluru\",\n      \"icon_emoji\": \"uluru\",\n    },\n    json: true\n  };\n\n  // Sending message\n  request.post(options, function(error, response, body) {\n    if (!error && response.statusCode == 200) {\n      context.done(null, body);\n    } else {\n      console.log('error: ' + response.statusCode);\n      context.done(null, 'error');\n    }\n  });\n}\n"
        },
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "secureLambdaRole067C996A",
            "Arn"
          ]
        },
        "Runtime": "nodejs10.x",
        "Environment": {
          "Variables": {
            "TZ": "Asia/Tokyo",
            "SLACK_CHANNEL": {
              "Ref": "slackChannelParameter"
            }
          }
        },
        "FunctionName": "slack-adventar-notify",
        "Timeout": 300
      },
      "DependsOn": [
        "secureLambdaRole067C996A"
      ],
      "Metadata": {
        "aws:cdk:path": "CdkLambdaStack/SlackAdventar/Resource"
      }
    },
    "SlackAdventarAllowEventRuleCdkLambdaStackRule69AC60575315E536": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "Action": "lambda:InvokeFunction",
        "FunctionName": {
          "Fn::GetAtt": [
            "SlackAdventar6AF5ACBE",
            "Arn"
          ]
        },
        "Principal": "events.amazonaws.com",
        "SourceArn": {
          "Fn::GetAtt": [
            "Rule4C995B7F",
            "Arn"
          ]
        }
      },
      "Metadata": {
        "aws:cdk:path": "CdkLambdaStack/SlackAdventar/AllowEventRuleCdkLambdaStackRule69AC6057"
      }
    },
    "Rule4C995B7F": {
      "Type": "AWS::Events::Rule",
      "Properties": {
        "ScheduleExpression": "cron(0 4 1-25 12 * 2019)",
        "State": "ENABLED",
        "Targets": [
          {
            "Arn": {
              "Fn::GetAtt": [
                "SlackAdventar6AF5ACBE",
                "Arn"
              ]
            },
            "Id": "Target0"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "CdkLambdaStack/Rule/Resource"
      }
    }
  },
  "Parameters": {
    "slackChannelParameter": {
      "Type": "AWS::SSM::Parameter::Value<String>",
      "Default": "/Lambda/production/slack-channel"
    }
  }
}
```

</div></details>

### cdk deploy
コンパイルまで完了したのでいよいよ`cdk deploy`でリソースを作成していきます。
初回の場合いきなり作成されることはないのですが、2回目以降は普通に無視して作りにいくので注意してしてください。
![sc_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/5b8a63f1-876a-4b4e-0ebf-1c56cfb6e827.png)

![sc_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/23eaebca-068b-bc45-562f-b4db4335dd59.png)

CloudFormationも確認できます。
<img width="1386" alt="sc_5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/e66a2758-9653-446f-3e2d-aaed95833e6e.png">

ちなみに変更がかかるとこんな感じで`Update`がかかります。
![sc_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/b13b0097-e60a-0a0d-f548-d0330a342377.png)

無事メンション付きで通知がきました！
<img width="553" alt="sc_7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423088/eb4364e8-eeb2-0339-60e6-85c902366acf.png">

# まとめ
Lambdaも楽にデプロイできて幸せになれますね！
ZIP化して…S3にあげて…パスコピーして…みたいなのがないので楽でいいですね！
弊社だと割と`Serverless Framework`が主流ですが、SAMとうまく掛け合わせてローカルテストまでできるようになれば完全に駆逐できそう…！！！
嫌いな訳じゃないんですが、ちょっと多機能すぎるかな…というのと、AWS環境しか利用していないという背景があり、私個人としてはAWS CDKに統合していきたいのが本音です。
今後はユーザー作成やPersonalizeなどもCDKでガンガン書いていくつもりなので、気が向けばまた記事書くかもしれません。