AWS CDKでPerfomance AlarmのSlack通知セットアップする

最近仕事でCDKばっかり触っている竹下です。
Infrastructure as a codeを進めており、AWSのリソース管理をCDKに移行中です。
最近CFの取れたSlackChannelConfigurationクラスが追加になっており、Chatbotが少し使いやすくなっていました。
実際の運営ではリソースのパフォーマンス監視は必須なので、参考になると幸いです。

# 想定読者

前提として、CDKがどういうものか理解していて、CDKの実行環境が準備できている人を対象とした記事になっています。CDKが何か？などがわからない方は、先に他の記事などから概要を把握してもらったほうが良いです。また、コードの方はTypeScriptで記述しています。


# 設定の流れ

1. Amazon ChatBotのGUIにSlack workspace登録(残念ながらこちらはまだ手動です)
1. パフォーマンスAlarmの通知を受け取るSNS Topicを作成
1. ChatBotを登録し、通知先のSlackチャンネルとTopicを結びつける
1. RDSやEC2、FargateServiceなどからmetricを取得
1. metricを紐づけてAlarm作成
1. Alarmにtopicへの通知を登録


となります。通知の流れは

```
RDS,EC2など - Metric -> Alarm -> SNS Topic -> ChatBot -> Slack
```

となります。

# 1. Slack workspace登録

残念ながらこちらはまだCDK化されていないようです。なので、Consoleから手動で登録する必要があります。ただ、こちらはアカウントに一度登録するだけで良いので、2回目以降はこの手順は不要になります。

※2020/12/26時点で未対応ですがCDKはバージョンアップが早いので、現在は対応している可能性があります。

### 必要なもの

* App連携の権限を持ったSlackアカウント

### 手順

AWS ConsoleからChatBotへ移動し、Slack workspaceを作成します。GUIに従って連携許可するだけで登録完了します。

![chatbot2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12647/74b956c2-9371-e1a5-0813-5e8c8e4823a5.png)


![Chatbot1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12647/4e72110c-03b4-17bb-da38-e796bd324014.png)

# 2. パフォーマンスAlarmの通知を受け取るSNS Topicを作成

それではここからはCDKを実装していきます。
まずは、各種Alarmなどを受け取りChatBotへ通知するTopicを作成します。

依存

```
yarn add @aws-cdk/aws-sns
```

実装

```typescript
import * as cdk from "@aws-cdk/core"
import * as sns from "@aws-cdk/aws-sns"

export class PerfomanceAlarmStack extends cdk.Stack {
  constructor(app: cdk.App, id: string) {
    super(app, id)
    // Topicの作成
    const alarmTopic = new sns.Topic(this, "AlarmTopic", {
      displayName: "Performance alarm topic",
      topicName: "perfomance-alarm-topic"
    })
  }
}

```

以上です。さすがCDK、簡潔に書けます。



# 3. ChatBotを登録し、通知先のSlackチャンネルとTopicを結びつける

次に、Topicが通知を受け取ったときにそれを受けて通知を送るChatBotを作成します。

準備

* 1で作成したSlackWorkspaceID
* 通知先のSlackのChannelのID

SlackChannelのIDは、チャンネルを右クリックし「リンクをコピー」するとコピーしたURLの末尾がIDになっています。(もっと簡単に確認できる方法あれば教えて下さい。)
![SlackChannelID.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12647/d79650dc-02fa-c591-22bf-43db1ed4b560.png)



依存

```
yarn add @aws-cdk/aws-chatbot
```

実装

```typescript
import * as cdk from "@aws-cdk/core"
import * as sns from "@aws-cdk/aws-sns"
import * as chatbot from "@aws-cdk/aws-chatbot"

export class PerfomanceAlarmStack extends cdk.Stack {
  constructor(app: cdk.App, id: string) {
    super(app, id)
    // Topicの作成
    const alarmTopic = new sns.Topic(this, "AlarmTopic", {
      displayName: "Performance alarm topic",
      topicName: "perfomance-alarm-topic"
    })
    // ChatBotの作成
    const cb = new chatbot.SlackChannelConfiguration(scope, "SlackChatbot", {
      slackChannelConfigurationName: "PerfomanceAlarmSlackBot",
      slackWorkspaceId: "SlackWorkspaceId",
      slackChannelId: "SlackChannelId",
      notificationTopics: [alarmTopic] 
    })
  }
}

```

SlackWorkspaceIdとslackChannelIdは用意したIDに置き換えてください。

### 注意

SlackWorkspaceに同じSlackChannelIDを持つSlackChannelは一つしか作れません。
また、既存のSlackChannelに対して、連携するTopicを追加することもCDKではできません(AWS Consoleからは可能です)

# 4. RDSやEC2、FargateServiceなどからmetricを取得

次に、RDSやEC2インスタンスなどのパフォーマンスの監視を取得して行きたいと思います。
今回は、RDSで説明していきたいと思いますが、EC2やECSでも同様にしてパフォーマンス情報の取得が可能です。metricsが取得できる対象ならば、Interface,ClassどちらにもmetricsXXX()という関数が定義されており、そちらのメソッドを使用すると簡単に取得可能です。

依存

```
yarn add @aws-cdk/aws-rds
```

実装

```typescript

import * as cdk from "@aws-cdk/core"
import * as sns from "@aws-cdk/aws-sns"
import * as chatbot from "@aws-cdk/aws-chatbot"
import * as rds from "@aws-cdk/aws-rds"

export class PerfomanceAlarmStack extends cdk.Stack {
  constructor(app: cdk.App, id: string) {
    super(app, id)
    // Topicの作成
    略
    // ChatBotの作成
    略
    // RDSの作成とMetricの取得
    const vpc = new Vpc(this, "Vpc")
    const rds = new rds.DatabaseCluster(this, 'Database', {
      engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_2_08_1 }),
      instanceProps: {
        vpcSubnets: {
          subnetType: ec2.SubnetType.PRIVATE,
        },
        vpc
      }
    })

    const cpuMetric = rds.metricCPUUtilization()
    const memoryMetric = rds.metricFreeableMemory()
    // 他にもいろいろあります
  }
}

```

# 5. metricを紐づけてAlarm作成

次に、metricをAlarmに紐付けます。metricは単純にパフォーマンスを取得するだけなので、このAlarmに紐付ける際にしきい値やサンプル数などをコントロールします。

依存

```
yarn add @aws-cdk/aws-cloudwatch
```

実装

```typescript
import * as cdk from "@aws-cdk/core"
import * as sns from "@aws-cdk/aws-sns"
import * as chatbot from "@aws-cdk/aws-chatbot"
import * as rds from "@aws-cdk/aws-rds"
import * as cloudwatch from "@aws-cdk/aws-cloudwatch"

export class PerfomanceAlarmStack extends cdk.Stack {
  constructor(app: cdk.App, id: string) {
    super(app, id)
    // Topicの作成
    略
    // ChatBotの作成
    略
    // RDSの作成とMetricの取得
    略

    const cpuMetric = rds.metricCPUUtilization()
    const memoryMetric = rds.metricFreeableMemory()

    const cpuMetric= new Alarm(this, "CPUAlarm", {
      evaluationPeriods: 2, // 1だと一時的なスパイクでAlarmが飛ぶので、クリティカルでないなら2以上を推奨
      metric: cpuMetric,
      threshold: 80 // CPUはデフォルト使用率(100分率)
    })
    const memoryMetric= new Alarm(this, "FreeMemoryAlarm", {
      evaluationPeriods: 2,
      metric: memoryMetric,
      threshold: 400000000 // 400M, Memoryは容量指定
    })
  }
}
```

thresholdは、監視対象によって100分率だったり、実値だったりします。使用する際には、ドキュメントで確認しましょう。[RDSだとこのように](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-rds.IDatabaseCluster.html#metricwbrfreeablewbrmemoryprops)"in bytes"などの指定があります。

また、サンプル時間や集計関数(AVGやMAXなど)などは、metricXXXメソッドの引数で指定することも可能です。(だいたいはデフォルトで良いと思いますが)


# 6. Alarmにtopicへの通知を登録

Alarm作成までできたら、Alertが出た時にTopicへ通知を投げれるように関連付けます。AlarmにaddAlarmActionというメソッドがあるので、そのメソッドでTopicへの通知を追加すればOKです。


実装

```typescript
import * as cdk from "@aws-cdk/core"
import * as sns from "@aws-cdk/aws-sns"
import * as chatbot from "@aws-cdk/aws-chatbot"
import * as rds from "@aws-cdk/aws-rds"
import * as cloudwatch from "@aws-cdk/aws-cloudwatch"
import * as cloudwatchactions from "@aws-cdk/aws-cloudwatch-actions"

export class PerfomanceAlarmStack extends cdk.Stack {
  constructor(app: cdk.App, id: string) {
    super(app, id)
    // Topicの作成
    const alarmTopic = new sns.Topic(this, "AlarmTopic", {
      displayName: "Performance alarm topic",
      topicName: "perfomance-alarm-topic"
    })
    // ChatBotの作成
    略
    // RDSの作成とMetricの取得
    略
    // Alarmの作成
    const cpuMetric= ...
    const memoryMetric = ...
    // Alarmの登録
    const alarms = [cpuMetric, memoryMetric]
    alarms.forEach((alarm) => {
      alarm.addAlarmAction(new cloudwatchactions.SnsAction(alarmTopic))
    })
  }
}
```

# 確認

以上のCDKを実行すると、リソースの監視が設定できます。通知がちゃんとできているか試したい場合は、一時的にthresholdを低くしてみるか、aws cuiの[set-alarm-state](https://docs.aws.amazon.com/cli/latest/reference/cloudwatch/set-alarm-state.html)を使用してAlert状態に変更できるので、それを利用して確かめてください。成功すれば、Slackに通知されます。


以上になります。Alarmの設定は一度書いておけば使いまわしがしやすいので、CDKで設定できるようにしておくとプロダクション用のリソース作成がかなり楽になるのでおすすめです。

# コード全体

※今回はサンプルなのでコンストラクタ内でだらっと書いていますが、実際はメソッドにわけるなど、可読性を高めるコードにしています。

```typescript
import * as cdk from "@aws-cdk/core"
import * as sns from "@aws-cdk/aws-sns"
import * as chatbot from "@aws-cdk/aws-chatbot"
import * as rds from "@aws-cdk/aws-rds"
import * as cloudwatch from "@aws-cdk/aws-cloudwatch"
import * as cloudwatchactions from "@aws-cdk/aws-cloudwatch-actions"

export class PerfomanceAlarmStack extends cdk.Stack {
  constructor(app: cdk.App, id: string) {
    super(app, id)
    // Topicの作成
    const alarmTopic = new sns.Topic(this, "AlarmTopic", {
      displayName: "Performance alarm topic",
      topicName: "perfomance-alarm-topic"
    })
    // ChatBotの作成
    const cb = new chatbot.SlackChannelConfiguration(scope, "SlackChatbot", 
    {
      slackChannelConfigurationName: "PerfomanceAlarmSlackBot",
      slackWorkspaceId: "SlackWorkspaceId",
      slackChannelId: "SlackChannelId",
      notificationTopics: [alarmTopic] 
    })
    // RDSの作成とMetricの取得
    const vpc = new Vpc(this, "Vpc")
    const rds = new rds.DatabaseCluster(this, 'Database', {
      engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_2_08_1 }),
      instanceProps: {
        vpcSubnets: {
          subnetType: ec2.SubnetType.PRIVATE,
        },
        vpc
      }
    })
    // Alarmの作成
    const cpuMetric = rds.metricCPUUtilization()
    const memoryMetric = rds.metricFreeableMemory()

    const cpuMetric= new Alarm(this, "CPUAlarm", {
      evaluationPeriods: 2, // 1だと一時的なスパイクでAlarmが飛ぶので、クリティカルでないなら2以上を推奨
      metric: cpuMetric,
      threshold: 80 // CPUはデフォルト使用率(100分率)
    })
    const memoryMetric= new Alarm(this, "FreeMemoryAlarm", {
      evaluationPeriods: 2,
      metric: memoryMetric,
      threshold: 400000000 // 400M, Memoryは容量指定
    })
    // Alarmの登録
    const alarms = [cpuMetric, memoryMetric]
    alarms.forEach((alarm) => {
      alarm.addAlarmAction(new cloudwatchactions.SnsAction(alarmTopic))
    })
  }
}

```