CloudWatchアラーム通知をSlackにする。SAMで実装版

以前の記事の、[CloudWatchアラーム通知をSlackにする](https://qiita.com/hf7777hi/items/e0f43f0fb7e2effa0af8) のSAM(Serverless Application Model)で実装した版になります。
SAMを使うことでローカル環境でもテスト実施ができる点とアラームの設定をGit管理できる点がメリットと思います。

#0.環境
* Docker
* Python3.7
* [aws-cli](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html#install-tool-pip)
* [aws-sam-cli](https://pypi.org/project/aws-sam-cli/)

aws configureでcredentialが設定してある前提で進めます。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration

#1. 実装
サンプルは以下に作ってしまいました。
https://github.com/hf7777hi/aws-cloudwatch-alarm-to-slack

ディレクトリ構成は以下の通り。

```
.
├── README.md
├── event.json                  <-- SNS event. Lamdaのテストイベントに使うものと同等です。
├── src
│   ├── __init__.py
│   ├── app.py                  <-- Lambdaのコードはここに書きます。
└── template.yaml               <-- SAM Template
```

ソースは、Lambdaの「設計図の使用」の「cloudwatch-alarm-to-slack-python」のサンプルコードから、slsckの絵文字を使ったメッセージを返すように改造しました。
app.pyの中身を良しなに改造していただくとOKです。もちろん、このままでも動作します。

##template.yaml 基本設定
テスト実行する前に環境に合わせて以下を設定してください(yamlでのCloudwatchアラームの設定は後述します)。

```yaml
SLACK_CHANNEL: cloudwatch-alarm   ←通知したいSlackのチャンネル
ENCRYPTED_INCOMING_URL: hogehoge  ←暗号化したIncoming Webhook URL
OK: ":ok:"                        ←OKアラートのSlack絵文字
ALARM: ":ng:"                     ←NGアラートのSlack絵文字
INSUFFICIENT_DATA: ":question:"   ←不足アラートのSlack絵文字
EXCEPTION: ":rotating_light:"     ←コードの例外時に通知するSlack絵文字
```

まず、通知先のSlackの準備と「Incoming Webhooks」の「Webhook URL」の暗号化を行います。
以前投稿した、[CloudWatchアラーム通知をSlackにする](https://qiita.com/hf7777hi/items/e0f43f0fb7e2effa0af8) の [Slackの準備](https://qiita.com/hf7777hi/items/e0f43f0fb7e2effa0af8#slack%E3%81%AE%E6%BA%96%E5%82%99) と [IAMのKMSで暗号キーでWebhookURLを暗号化] (https://qiita.com/hf7777hi/items/e0f43f0fb7e2effa0af8#iam%E3%81%AEkms%E3%81%A7%E6%9A%97%E5%8F%B7%E3%82%AD%E3%83%BC%E3%81%A7webhookurl%E3%82%92%E6%9A%97%E5%8F%B7%E5%8C%96) を参考に行ってください。
お金かけたくないので暗号化しないよって方は、暗号キーの作成を飛ばして、src/app.py l.51を以下のように変更して、ENCRYPTED_INCOMING_URLにhttps://以下の平文を設定してください。

```python
token = boto3.client('kms').decrypt(CiphertextBlob=b64decode(os.getenv("ENCRYPTED_INCOMING_URL")))['Plaintext'].decode('utf-8')
```
↓

```python
  token = os.getenv("ENCRYPTED_INCOMING_URL")
```


#2. Lambdaコードのテスト
以下コマンドを実行することで、lambci/lambda:python3.7 イメージのDockerを起動し、コードを実行してくれます。

```
$ sam local invoke -e event.json
```
## イベント

```
'Message': '{"AlarmName":"[アラーム名]","AlarmDescription":null,"AWSAccountId":"0000000","NewStateValue":"ALARM","NewStateReason":"Threshold Crossed: 1 datapoint [0.4667407613208132 (16/05/19 13:19:00)] was greater than or equal to the threshold (0.0).",
```

NewStateValueの値がアラート状態によって以下のように変わります。
* アラート時 : ALARM
* OK時 : OK
* 不足時 : INSUFFICIENT_DATA

これらをevent.jsonのMessageにすればそれぞれのアラートの状態のテストが可能です。

実際にやってみました。(OK, ALARM, INSUFFICIENT_DATA, EXCEPTIONの順です)
<img width="376" alt="slack.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86605/7fb14849-e70b-94f5-6de6-b2f61b8efc8b.png">
こんな感じでSlackに通知されます。

ここまでローカルでLambdaのテスト実行ができたので、src/app.pyの内容をそのまま、LambdaFunctionにコピペして、Lambdaを設定すれば出来上がりにすることも可能です。

#3. yamlによるアラームの設定
ここからがメインです。

各設定項目の詳細は公式を確認してください。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-cw-alarm.html

サンプルとして、ApiGatewayのアラームを設定してみました。

```yaml
  ApiCountAlarm:
    Type: AWS::CloudWatch::Alarmproperties-cw-alarm.html
    Properties:
      Namespace: AWS/Api­Gateway
      MetricName: Count
      Dimensions:
        - Name: ApiName
          Value: SampleApi
      Period: 300
      Statistic: Average
      AlarmName: Sample
      AlarmDescription: Sample
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 0
      EvaluationPeriods: 1
      TreatMissingData: notBreaching
      OKActions:
        - !Ref CloudwatchAlarmTopic
      AlarmActions:
        - !Ref CloudwatchAlarmTopic
      InsufficientDataActions:
        - !Ref CloudwatchAlarmTopic
```

設定は以下のように反映されます。
<img width="421" alt="cloudwatch.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86605/31c858e6-c139-8922-466f-a57ac823d05d.png">

適宜設定したいアラームがあればtemplete.yamlに追記してください。

ちなみに、AWSコンソール上からアラームを設定後、アラームの履歴をみるとAWS CloudFormation テンプレートの設定内容がJSONで確認できるのでyaml記載の参考になります。
参考：[CloudWatchの再起動アクションをCloudFormationから作成してみた](https://dev.classmethod.jp/cloud/aws/cloudwatch_action_reboot_cloudformation/)

#4. デプロイ
```bash
$ aws s3 mb s3://BUCKET_NAME
$ sam package \
    --output-template-file packaged.yaml \
    --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME
$ sam deploy \
    --template-file packaged.yaml \
    --stack-name cloudwatch-alarm-to-slack \
    --capabilities CAPABILITY_IAM
```

これでCloudFormationが立ち上がり、Lambda、Cloudwatch、SNSが作成されていると思います。

念のため、Lambdaのテストをしておくといいと思います。
テストイベントは、ローカルのテストに使用したものでOKです。

**SlackのURLを暗号化した場合は、KMSのキーユーザーにCloudFormationで作成されたロールを許可してください。**


以上です。