ITお絵かき修行

3歩歩いても忘れないために

CloudWatch Metricsのグラフ画像を取得し、Lambdaより定期的にSlack通知させたい

まえがき

この手の話は、(AWSの場合は)「CloudWatch Alarm+ CloudWatch Metrics + SNS + Chatbot (+ Slack)」や「CloudWatch Alarm+ CloudWatch Metrics + SNS + DataDog(+ Slack)」という構成を第一に検討するべきだと思います。

ただし下記のケースなど、マネージドサービス利用ではなく何らかの実装で対応せざるを得ない場合があると思います。本稿ではそういったニッチな場合のお話を取り扱っている次第です。

1.CloudWatch Alarmは1つのメトリクスしか選択できない
→1つのメトリクスしか選択できないため、複数Metrics要素のグラフを作ることができない
→APIGatewayの4XX5XXエラー、Lambdaのエラー率とエラー件数の相関、Glueにおける各Executorメモリ使用量の推移などは、複数Metricsを並べることでグラフとして有意になる場合があります。

2.定期的にMetricsの状態をSlack通知したい場合、CloudWatch Alarmだと定期実行できない
→これに関しては色々逃げ道がありそうですが、基本的にはCloudWatch Alermはイベント発火なので定期実行はできないです(※投稿日時点では)。

事前準備

・SlackAppにて投稿用のSlackアプリを作成&インストール済。
→IncomingWebhookでも可能です。リクエストの形式を変更すれば大丈夫です。ただSlackAPI的に画像をそのまま渡せないので、S3に置く→S3のURLをattachments属性のimage_urlに設定する、という1クッション置いた方式となります。
・requestsモジュールを使ってます。
→urllibで頑張ろうとしましたが、つらみが深かったのでやめました。ゆるして

構成

CloudWatch Events(Cron) → Lambda(Slack通知) ←(※boto3経由)→  CloudWatch Metrics

実装

・Lambda環境変数は下記。
url・・・"https://slack.com/api/files.upload"
oauth_token・・・Slackアプリに対応するOAuthトーク
channel・・・SlackチャンネルのURLの末尾のハッシュ
・本LambdaをCloudWatch Eventsより定期実行する。

ソース(Python3.8)
import os
import sys
import json
import boto3
import requests

s3_rw = json.dumps({
    "view": "timeSeries",
    "stacked": True,
    "metrics": [
        [ "Glue", "glue.ALL.s3.filesystem.read_bytes", "Type", "gauge", "JobRunId", "ALL", "JobName", "TestJob", { "label": "S3 Bytes Read" } ],
        [ ".", "glue.ALL.s3.filesystem.write_bytes", ".", ".", ".", ".", ".", ".", { "label": "S3 Bytes Written" } ]
    ],
    "title": "[Glue][TestJob][s3 read/write][SUM][1 minute]",
    "stat": "Sum",
    "period": 60,
    "width": 1600,
    "height": 600,
    "start": "-P1D",
    "end": "P0D",
    "timezone": "+0900"
})

cloud_watch = boto3.client('cloudwatch', region_name='ap-northeast-1')

# ハンドラ
def lambda_handler(event, context):

    # 処理対象たち
    cw_map = {
        "Glue S3書き込み/読み込み状況【1分毎・合計】": s3_rw,
        }

    for cw_title in cw_map:
        # CloudWatchからウィジェット画像取得
        response_cw = cloud_watch.get_metric_widget_image(MetricWidget = cw_map[cw_title])
        image = {'file': response_cw['MetricWidgetImage']}

        # Slack通知
        url = os.environ['url']

        params={
            "token": os.environ['oauth_token'],
            "channels": os.environ['channel'],
            "title": cw_title,
            "filetype": "png"
        }
        req = requests.post(url, params=params, files=image)
        try:
            req.raise_for_status()
        except requests.RequestException as e:
            print(e)
ハマりポイント

・CloudWatch MetricsのイメージAPIを出力すると「true/false」で出力されるので、「True/False」へ変更する。
・イメージAPIのtitle属性に全角文字を入れると文字化けするので注意しましょう(一敗)。
・req = requests.post(url, params=params, files=image) のfiles属性に(名前的に)複数画像が添付できそうですが、できなかったです。複数画像アップロードできた方いらっしゃれば教えていただきたいですm(_ _)m
・複数回SlackへURLを投げるので、タイムアウト値は30秒程度にしたほうが良いです(※対象のCloudWatch Metricsの数によりますが)

おわりに(※ポエム)

監視アラームを設定する際にはCloudWatchにせよDataDogにせよ、基本的には何らかの閾値を設定することが求められます。
既存サービスの場合はCloudWatch anomaly detectionなどのサービスを利用すれば大体事足りますが、新規サービス案件(再構築などの案件も含む)の場合は、「仮決めした値でしばらく運用して、時が来たら変える」という感じになりがちです(※個人の感想です)。

この場合問題となるのは「想定外事象に気づけない」「正常に動作している状態が知りたいが毎日確認しにいくのは面倒(≒なんとなく大丈夫だろうという意識で運用してしまう)」という点にあります。
なので本稿のような、適度な期間での正常性確認をお手軽にできる手段があればな~と思った次第です。
(※ダッシュボードってかっこよくても結局見なくなるんで、何かしら通知させたいですよね。。ダッシュボードが来い)
(※Slack Command1クリックからCloudWatch Metrics画像化&通知など夢は膨らみますね!)

本稿が何かの役に立てば幸いです(クラメソ様風)。以上です。