2019/7/25 追記
AWS Chatbotというサービスで簡単にCloudWatchアラームをSlackに通知できるようになりました。
個別に通知内容をカスタマイズしたい場合は変わらずLambda関数を作る必要がありますので
そのような場合に本記事の内容が参考になれば幸いです。
はじめに
Amazon CloudWatch に、AWS コンソール以外でカスタムダッシュボードを作成できる機能が追加されました。
https://aws.amazon.com/jp/about-aws/whats-new/2018/09/amazon-cloudwatch-adds-ability-to-build-custom-dashboards-outside-the-aws-console/
実装例として、GetMetricWidgetImage APIとSESを使用してCloudWatchアラームと一緒に
グラフ画像をメール通知する方法が公式Blogで紹介されています。
Reduce Time to Resolution with Amazon CloudWatch Snapshot Graphs and Alerts | Amazon Web Services
https://aws.amazon.com/jp/blogs/devops/reduce-time-to-resolution-with-amazon-cloudwatch-snapshot-graphs-and-alerts/
Slackで画像も通知できたらな。。。と思い、上記を参考にして作ってみました。
ざっくり構成
Lambdaは python3.6 で実装していますが、基本的な構成はメール通知版とほとんど変わりません。
CloudWatchでアラームを検知したら、SNSに通知します。
Lambda関数がSNSトリガーで起動します。GetMetricWidgetImage APIから対象アラームの
グラフを取得し、Slack APIのfiles.uploadメソッドでアラーム情報とグラフをPostします。
結果イメージ
アラームが発生すると以下のように対象メトリクスのグラフがアップロードされます。

注釈を挿入できるので、しきい値を超えた状態が簡単に確認できると思います。
Slack Botを使用してるため、後述のLambda関数の設定にはBotユーザのTokenと
通知先のチャンネルIDが必要です。
プライベートチャンネルの場合は、Botユーザをinviteしてください。
OK状態のアラームを定義していれば、しきい値を下回った場合も通知されます。
以下が GetMetricWidgetImage API で取得した画像です。

Lambda関数
ひよコードかもしれませんが、ご容赦ください![]()
環境変数 CHANNEL にチャンネルIDを、TOKENにBotユーザのTokenを設定する必要があります。
import base64
import boto3
import json
import logging
import os
import requests
from botocore.exceptions import ClientError
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def get_params(title, message):
token = os.environ['TOKEN']
channel = os.environ['CHANNEL']
reason = message['NewStateReason']
slack_params = {
'token': token,
'channels': channel,
'initial_comment': reason,
'title': title
}
return slack_params
def get_properties(alarm_state):
if alarm_state == "ALARM":
properties = { 'color': '#ff6961', 'label': 'Trouble threshold start' }
elif alarm_state == "OK":
properties = { 'color': '#009933', 'label': 'Trouble threshold end' }
return properties
def get_image(title, message):
threshold = message['Trigger']['Threshold']
metrics = [ message['Trigger']['Namespace'], message['Trigger']['MetricName'],
message['Trigger']['Dimensions'][0]['name'], message['Trigger']['Dimensions'][0]['value'] ]
alarm_state = message['NewStateValue']
annotation_properties = get_properties(alarm_state)
widget_definition = json.dumps(
{
"width": 600,
"height": 400,
"start": '-PT2H',
"end": "PT0H",
"timezone": '+0900',
"view": "timeSeries",
"stacked": False,
"stat": "Average",
"title": title,
"metrics": [ metrics ],
"period": 300,
"annotations": {
"horizontal": [
{
"color": annotation_properties['color'],
"label": annotation_properties['label'],
"value": threshold
}]
}
}
)
cloudwatch = boto3.client('cloudwatch')
try:
response = cloudwatch.get_metric_widget_image(
MetricWidget = widget_definition
)
except ClientError as e:
print(e.response['Error']['Message'])
else:
image = {'file': response['MetricWidgetImage']}
print("Get Image succeeded:")
return image
def upload_slack(upload_url, params, files):
req = requests.post(upload_url, params=params, files=files)
try:
req.raise_for_status()
logger.info("Message posted.")
return req.text
except requests.RequestException as e:
logger.error("Request failed: %s", e)
def lambda_handler(event, context):
title = event['Records'][0]['Sns']['Subject']
message = json.loads(event['Records'][0]['Sns']['Message'])
upload_url = 'https://slack.com/api/files.upload'
params = get_params(title, message)
files = get_image(title, message)
response = upload_slack(upload_url, params, files)
return response
widget_definition でAPIの入力パラメータを定義しています。
全体の構造については以下のドキュメントを参照してください。
GetMetricWidgetImage: Metric Widget Structure and Syntax
https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/CloudWatch-Metric-Widget-Structure.html
上記を元に boto3 の get_metric_widget_image メソッドでグラフを取得します。
Boto 3 Documentation CloudWatch
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudwatch.html#CloudWatch.Client.get_metric_widget_image
2018/9/26 時点で、東京リージョンのLambda実行環境に含まれるboto3では
get_metric_widget_image を使用できませんでした。いずれアップグレードされるとは思いますが、
現時点ではデプロイパッケージ(zip)に最新版のboto3を含めるようにしてください。
また通信ライブラリにrequestsを使用しているのでこちらもデプロイパッケージに含めます。
設定補足
SNS
空のTopicを作成しておきます。
Lambda
トリガーにSNSを追加します。
トリガーの設定で対象のSNS Topicを指定します。
繰り返しになりますが、環境変数にCHANNEL(通知対象チャンネルID)と
TOKEN(BotユーザのToken)を指定します。
実行ロールはメトリクスデータを取得できるようポリシーを設定してください。
タイムアウトはデフォルトの3秒ではタイムアウトする可能性があるので5秒にしておきました。
CloudWatch
対象のCloudWatchアラームの通知アクションに作成したSNS Topic を指定します。

参考URL
https://github.com/aws-samples/aws-cloudwatch-snapshot-graphs-alert-context
https://dev.classmethod.jp/cloud/aws/lambda-python-tips-all-events-are-not-dict/
以上です。
参考になれば幸いです。




