cloudwatchのalarmをslackに飛ばす(BluePrint)

Cloudwatchのalarmをメールで確認したくない！
slackで確認したい！


やること
SlackのWebhookの準備
KMSの準備(暗号化キーの作成)
Lambda Functionの作成
IAM RoleのPolicy追加
Webhook URLの暗号化
Webhook URLの復号化設定(IAM Role)
SNSの設定
CloudWatchアラームの作成
動作確認


* こんな感じのがやりたい
![blueprint-cloudwatch-slack-diagram.png](https://qiita-image-store.s3.amazonaws.com/0/97564/c695fab8-526f-d436-29bb-824cced02ba4.png)




# SlackWebHookUrl取得
まず、Slackのアカウントがなければ作成して下さい。
作成できたらhttps://[team名].slack.com/services/newにアクセスします。
ページ上部にある検索窓に「webhook」と入力して着信Webフックを選択
設定を追加

![page.png](https://qiita-image-store.s3.amazonaws.com/0/97564/1d9b13fe-3504-f5a0-ea11-e74a49a26bea.png)

通知をPOSTしたいChannelを指定します。
既存のものから選択するか新規に作成して下さい。
今回は「cloudwatch」というChannelにしました。

![page2.png](https://qiita-image-store.s3.amazonaws.com/0/97564/ca8f9204-c3d4-81c1-f7b9-9789eb82596e.png)


最後のページで「Webhook URL」が表示されるので、
コピーして控えておきましょう。Lambda Functionの作成時に必要になります。

![page (1).png](https://qiita-image-store.s3.amazonaws.com/0/97564/018649e8-3782-fdae-ad4b-b6ecca9a5105.png)


後は好みでアイコンなどを設定して「Save Settings」をクリックして保存します。



# KMSの準備(暗号化キーの作成)
次にWebhook URLを暗号化するキーを作成します。
環境に合わせて対象のリージョンを選択して、キーを作成して下さい。
後は全てデフォルトで作成しました。
![page (2).png](https://qiita-image-store.s3.amazonaws.com/0/97564/d69e0278-4317-018a-9b67-475c48af48d6.png)

![page (3).png](https://qiita-image-store.s3.amazonaws.com/0/97564/2d512fcd-5c8f-f549-8d0b-24647edf861a.png)

* 暗号化、復号化をlambdaがする必要がある為ロールを追加する
![page (4).png](https://qiita-image-store.s3.amazonaws.com/0/97564/fb507417-169a-93e1-b48c-e7cf661a0895.png)





# Lambda Functionの作成
ようやくLambda Functionの作成です。
Blueprintの選択時に「slack」で検索すると
「Cloudwatch-alarm-to-slack」が3種類出てきます。
利用言語が異なるだけなので使いやすいものを選択して下さい。
![page (5).png](https://qiita-image-store.s3.amazonaws.com/0/97564/03084848-35ef-d92c-c61a-becf1ebc3db3.png)

![page (6).png](https://qiita-image-store.s3.amazonaws.com/0/97564/0e77b6ac-a597-9773-0bbd-00d28b6aa781.png)

![page (7).png](https://qiita-image-store.s3.amazonaws.com/0/97564/e9c2dcf7-d51f-ce30-d3ad-c0920f6d244e.png)


``` index.js
'use strict';
/**
 * Follow these steps to configure the webhook in Slack:
 *
 *   1. Navigate to https://<your-team-domain>.slack.com/services/new
 *
 *   2. Search for and select "Incoming WebHooks".
 *
 *   3. Choose the default channel where messages will be sent and click "Add Incoming WebHooks Integration".
 *
 *   4. Copy the webhook URL from the setup instructions and use it in the next section.
 *
 *
 * To encrypt your secrets use the following steps:
 *
 *  1. Create or use an existing KMS Key - http://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html
 *
 *  2. Click the "Enable Encryption Helpers" checkbox
 *
 *  3. Paste <SLACK_HOOK_URL> into the kmsEncryptedHookUrl environment variable and click encrypt
 *
 *  Note: You must exclude the protocol from the URL (e.g. "hooks.slack.com/services/abc123").
 *
 *  4. Give your function's role permission for the kms:Decrypt action.
 *      Example:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1443036478000",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "<your KMS key ARN>"
            ]
        }
    ]
}

 */

const AWS = require('aws-sdk');
const url = require('url');
const https = require('https');

// The base-64 encoded, encrypted key (CiphertextBlob) stored in the kmsEncryptedHookUrl environment variable
const kmsEncryptedHookUrl = process.env.kmsEncryptedHookUrl;
// The Slack channel to send a message to stored in the slackChannel environment variable
const slackChannel = process.env.slackChannel;
let hookUrl;


function postMessage(message, callback) {
    const body = JSON.stringify(message);
    const options = url.parse(hookUrl);
    options.method = 'POST';
    options.headers = {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
    };

    const postReq = https.request(options, (res) => {
        const chunks = [];
        res.setEncoding('utf8');
        res.on('data', (chunk) => chunks.push(chunk));
        res.on('end', () => {
            if (callback) {
                callback({
                    body: chunks.join(''),
                    statusCode: res.statusCode,
                    statusMessage: res.statusMessage,
                });
            }
        });
        return res;
    });

    postReq.write(body);
    postReq.end();
}

function processEvent(event, callback) {
    const message = JSON.parse(event.Records[0].Sns.Message);

    const alarmName = message.AlarmName;
    //var oldState = message.OldStateValue;
    const newState = message.NewStateValue;
    const reason = message.NewStateReason;

    const slackMessage = {
        channel: slackChannel,
        text: `${alarmName} state is now ${newState}: ${reason}`,
    };

    postMessage(slackMessage, (response) => {
        if (response.statusCode < 400) {
            console.info('Message posted successfully');
            callback(null);
        } else if (response.statusCode < 500) {
            console.error(`Error posting message to Slack API: ${response.statusCode} - ${response.statusMessage}`);
            callback(null);  // Don't retry because the error is due to a problem with the request
        } else {
            // Let Lambda retry
            callback(`Server error when processing message: ${response.statusCode} - ${response.statusMessage}`);
        }
    });
}
exports.handler = (event, context, callback) => {
    if (hookUrl) {
        // Container reuse, simply process the event with the key in memory
        processEvent(event, callback);
    } else if (kmsEncryptedHookUrl && kmsEncryptedHookUrl !== '<kmsEncryptedHookUrl>') {
        const encryptedBuf = new Buffer(kmsEncryptedHookUrl, 'base64');
        const cipherText = { CiphertextBlob: encryptedBuf };

        const kms = new AWS.KMS();
        kms.decrypt(cipherText, (err, data) => {
            if (err) {
                console.log('Decrypt error:', err);
                return callback(err);
            }
            hookUrl = `https://${data.Plaintext.toString('ascii')}`;
            processEvent(event, callback);
        });
    } else {
        callback('Hook URL has not been set.');
    }
};
```




* lambdaが暗号化、復号化できるようにkmsロールを追加

![page (8).png](https://qiita-image-store.s3.amazonaws.com/0/97564/ac44b069-c932-0f65-efae-1d0d3700fa65.png)





# 動作確認
これで準備ができたので、実際にCloudWatchでアラームを飛ばしてみます。
CloudWatchのアラーム通知はLambda関数で指定したSNSトピックに飛ぶようにします。

# SNSの設定

![page (9).png](https://qiita-image-store.s3.amazonaws.com/0/97564/3e57f0e4-dcfa-1f86-e18c-26c5e55abeed.png)
![page (10).png](https://qiita-image-store.s3.amazonaws.com/0/97564/8e97a6eb-8d9a-7610-f2b6-404bd2cc1b05.png)
![page (11).png](https://qiita-image-store.s3.amazonaws.com/0/97564/2c88f074-7b7e-4736-f10a-026c412b53f4.png)
![page (12).png](https://qiita-image-store.s3.amazonaws.com/0/97564/a5719b32-bf39-c213-8347-6279f58488fa.png)









# Cloudwatchの設定
CPU使用率で監視しているので、閾値を「0以上」等にしてわざとアラーム状態にします。

![page (13).png](https://qiita-image-store.s3.amazonaws.com/0/97564/20dfb69d-bcdf-2edd-3ca8-1c3783fe9aa5.png)


![page (14).png](https://qiita-image-store.s3.amazonaws.com/0/97564/00825c5c-0086-bc3b-fb71-51ef1e22bcf1.png)
![page (15).png](https://qiita-image-store.s3.amazonaws.com/0/97564/df2e4b65-ab1f-04ad-9178-c903ae3a0534.png)




# 飛んでるか確認
![page (16).png](https://qiita-image-store.s3.amazonaws.com/0/97564/d97c80fd-fa52-d1fd-f189-34845dc92e1b.png)


こんな感じで「cloudwatch」のChannelにアラームがpostされました。
監視間隔のタイミングで通知されるのは嬉しいですね。
あとはalarm別に表示内容変えないとログが見にくいw


* 参考元
https://dev.classmethod.jp/cloud/aws/lambda-blueprint-slack-cloudwatch/