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/
以上です。
参考になれば幸いです。