michimani.net

CloudWatch Alarm を SNS 経由で Slack に通知するときのメッセージ内容を良い感じにしてみた

2018-12-20

CloudWatch でアラームの設定をして、SNS 経由で Slack に通知する際のメッセージを、見やすい形に整形する Lambda を作ってみました。

経緯

CloudWatch Alarm を Slack に通知すること自体は非常に簡単で、下記の方法で通知させることが可能です。

  1. SNS トピックを作成
  2. 作成したトピックのサブスクリプションに、HTTPS のプロトコルで Slack の Incoming Webhooks のエンドポイントを指定
  3. CloudWatch Alarm の通知先に、作成した SNS トピックを指定

ただ、これだけでは CloudWatch Alarm から送られてくる JSON の文字列がそのままメッセージ本文として通知されるので、非常に見づらいです。
これを良い感じに整形します。

やったこと

方法としては、SNS のサブスクリプションで直接 Incoming Webhooks のエンドポイントを指定するのではなく、 Lambda 関数を指定します。そして、その Lambda 関数内でメッセージの整形・ Slack への通知を実行します。

で、作ったのは次のような Lambda 関数。

import boto3
import json
import logging
import os

from base64 import b64decode
from urllib.error import URLError, HTTPError
from urllib.request import Request, urlopen

SLACK_CHANNEL = os.environ['slackChannel']
ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
HOOK_URL = boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext'].decode('utf-8')

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
    """Lambda handler."""
    logger.info('Event: ' + str(event))
    message = json.loads(event['Records'][0]['Sns']['Message'])
    logger.info('Message: ' + str(message))

    alarm_name = message['Trigger']['MetricName']
    new_state = message['NewStateValue']
    reason = message['NewStateReason']
    state_color = '#00FF00'

    if new_state != 'OK':
        state_color = '#FF0000'

    slack_message = {
        'channel': SLACK_CHANNEL,
        'icon_emoji': ':cloudwatch-%s:' % (new_state.lower()),
        'attachments': [
            {
                'color': state_color,
                'fields': [
                    {
                        'value': "*%s* state is now *%s*\n```\n%s\n```" % (alarm_name, new_state, reason)
                    }
                ]
            }
        ]
    }

    req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
    try:
        response = urlopen(req)
        response.read()
        logger.info('Message posted to %s', slack_message['channel'])
    except HTTPError as e:
        logger.error('Request failed: %d %s', e.code, e.reason)
    except URLError as e:
        logger.error('Server connection failed: %s', e.reason)

'icon_emoji': ':cloudwatch-%s:' % (new_state.lower()) の部分では通知用のアイコンをカスタム絵文字で指定しています。
事前に :cloudwatch-ok::cloudwatch-alarm: という名前でカスタム絵文字を作成しておくと、より それっぽさが出ます。不要な場合はこの 1 行を削除すれば ok です。

実際は次のような形で通知が届きます。

Slack Notification

とりあえず OKALARM の場合のみを考慮したパターンになってますが、適宜 INSUFFICIENT_DATA の状態についても色とかメッセージを変えることもできそうです。


comments powered by Disqus