CloudWatch アラームの通知メールを少しでも読みやすくしたい

CloudWatch アラームからの通知を読みやすく理解しやすい意味のある通知を目指します
2021.08.02

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。
ご機嫌いかがでしょうか。
"No human labor is no human error" が大好きな ネクストモード株式会社 の吉井です。

先日 Ops JAWS Meetup#19 で登壇する機会がありました。
運営メンバーで雑談をしていたときに「SNSでメール通知をしないほうがいいよね」といった話をしたことを思い出しました。

CloudWatch アラーム → SNS → メール、という仕組みは簡単に構築できるので採用されている企業も多いと思います。
しかし、正直言うとメール本文を見てもよくわからないというのが感想ではないでしょうか。

通知メールを読みやすくするには

アラームの内容が一見して理解しやすく保守作業を効率よくするにはどうしたらいいでしょうか?

Lambda で整形したメールを送信する

SNS でメール送信せずに Lambda で整形してからメールを送信する方法です。
通知設計を細かく実施、かつ、Lambda の実装を正確に行えるパワーが自社内にあるのであれば良い選択肢だと思います。

監視設計、通知設計は時間と共に変化していくので、それに対応するパワーも必要になります。

Chatbot

現在の選択肢としてはこれが一番有力ではないでしょうか。
Chatbot から Slack or Chime にアラーム内容を送信します。
Chatbot は対応するサービスが増えてきていますし、将来がとても楽しみです。

3rd party のソリューションを使う

もし、自社でモニター系の機能を持った SaaS を契約しているのであれば、これを使うのも有効です。
CloudWatch だけでは実現できない複雑な通知が設定できるかもしれません。
通知メールも自身でカスタマイズできるはずです。

しかしSNS

何らかの事情で SNS からのメール送信を採用せざるを得ない場合があるとします。
CloudWatch アラームを SNS 経由でメール送信する方法は難しくありません。

Using Amazon CloudWatch alarms

ツライ通知

CloudWatch アラームから SNS 経由でメール送信をすると
とてもツライ本文のメールが飛んできます。

以下は EC2 インスタンスの CPU 使用率を試してに送信してみた結果です。
これがメールで飛んできて理解できる人は少ないと思います。

{"version":"0","id":"b0936c1a-91e2-3347-2e02-6765efae6085","detail-type":"CloudWatch Alarm State Change","source":"aws.cloudwatch","account":"nnnnnnnnnnnn","time":"2021-07-30T23:50:54Z","region":"us-west-2","resources":["arn:aws:cloudwatch:us-west-2:nnnnnnnnnnnn:alarm:CPU_Utilization_YourHostName"],"detail":{"alarmName":"CPU_Utilization_YourHostName","state":{"value":"OK","reason":"Threshold Crossed: 1 out of the last 1 datapoints [0.0583313889537015 (30/07/21 23:49:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition).","reasonData":"{\"version\":\"1.0\",\"queryDate\":\"2021-07-30T23:50:54.451+0000\",\"startDate\":\"2021-07-30T23:49:00.000+0000\",\"statistic\":\"Maximum\",\"period\":60,\"recentDatapoints\":[0.0583313889537015],\"threshold\":50.0,\"evaluatedDatapoints\":[{\"timestamp\":\"2021-07-30T23:49:00.000+0000\",\"sampleCount\":1.0,\"value\":0.0583313889537015}]}","timestamp":"2021-07-30T23:50:54.472+0000"},"previousState":{"value":"ALARM","reason":"Threshold Crossed: 1 out of the last 1 datapoints [0.1083387502708468 (30/07/21 23:45:00)] was less than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition).","reasonData":"{\"version\":\"1.0\",\"queryDate\":\"2021-07-30T23:46:33.754+0000\",\"startDate\":\"2021-07-30T23:45:00.000+0000\",\"statistic\":\"Maximum\",\"period\":60,\"recentDatapoints\":[0.1083387502708468],\"threshold\":50.0,\"evaluatedDatapoints\":[{\"timestamp\":\"2021-07-30T23:45:00.000+0000\",\"sampleCount\":1.0,\"value\":0.1083387502708468}]}","timestamp":"2021-07-30T23:46:33.755+0000"},"configuration":{"metrics":[{"id":"b8bd7ce9-9b1f-c563-0c62-449b8a2ca9fc","metricStat":{"metric":{"namespace":"AWS/EC2","name":"CPUUtilization","dimensions":{}},"period":60,"stat":"Maximum"},"returnData":true}]}}}

読みやすくしてみよう

もう少しだけ読みやすくしましよう。
日本語も含めてみたいと思います。

AWS CLI v2 を使って構築していきます。
すべての手順は同じシェルで実行することを想定しています。
手元の PC に AWS CLI v2 をセットアップしてもいいですし、CloudShell を使ってもいいです。

SNS の作成

まずは SNS を作成します。
AWSACCOUNT 以外の bash 環境変数をご自身の環境に合わせて変更をお願いします。

YourDisplayName がメールの From に表示される名称になります。
メールフィルターをしたいときはこれを工夫ください。

export AWSACCOUNT=$(aws sts get-caller-identity | jq -r .Account)
export AWSREGION=us-west-2
export YourTopicName=TestTopic
export YourDisplayName=TestTest
export YourEmail="your mail address"

aws sns create-topic --name ${YourTopicName} \
  --attributes "DisplayName=${YourDisplayName}"

aws sns subscribe --topic-arn arn:aws:sns:${AWSREGION}:${AWSACCOUNT}:${YourTopicName} \
  --protocol "email" \
  --notification-endpoint ${YourEmail}

YourEmail で指定したメールアドレスに「AWS Notification - Subscription Confirmation」という件名のメールが来ていると思います。
内容を確認して Confirm します。

CloudWatch アラームの作成

CloudWatch アラームを作成します。
CLI のオプションをご自身の環境に合わせて変更してください。
説明を記載しておきます。
※指定する値の詳細は put-metric-alarm を参照ください。

設定項目 設定値
alarm-name アラーム名。メール本文に表示させるので一見して理解しやすい名称にします。
metric-name CloudWatch メトリクスのメトリクス名。
namespace CloudWatch メトリクスの名前空間。
statistic アラームに使用する統計です。Maximum、Sum、Average などから指定します。
period アラームに使用する期間です。秒数。
evaluation-periods アラームを実行するデータポイント。"M out of N" の N です。
datapoints-to-alarm アラームを実行するデータポイント。"M out of N" の M です。
threshold アラームのしきい値。
comparison-operator しきい値を比較するときの算術演算です。GreaterThanThreshold、LessThanThreshold などから指定します。
dimensions CloudWatch メトリクスのディメンション。
aws cloudwatch put-metric-alarm --alarm-name "CPU_Utilization_YourHostName" \
  --metric-name "CPUUtilization" \
  --namespace "AWS/EC2" \
  --statistic "Maximum" \
  --period 60 \
  --evaluation-periods 1 \
  --datapoints-to-alarm 1 \
  --threshold 50 \
  --comparison-operator "GreaterThanThreshold" \
  --dimensions "Name=InstanceId,Value=i-00fcd055daf502ffb"

CloudWatch アラームに直接 SNS を指定することが可能ですが、今回は指定しません。
後段の EventBridge に任せることにします。

EventBridge

EventBridge ルールを作成します。
source → CloudWatch がソースです。
detail-type → CloudWatch アラームの状態変化を受け取っています。
detail → 状態が ALARM へ遷移したものを受け取ります。
resources → CloudWatch アラームで「CPU_Utilization_」で始まるものを受け取っています。ここをうまく変えて様々な CloudWatch アラームに対応してみてください。

YourRuleName はご自身の環境に合わせて変更をお願いします。

export YourRuleName=Notification_CPUUtilization

aws events put-rule --name ${YourRuleName} \
  --event-pattern "{\"source\": [\"aws.cloudwatch\"],\"detail-type\": [\"CloudWatch Alarm State Change\"],\"resources\": [{\"prefix\": \"arn:aws:cloudwatch:${AWSREGION}:${AWSACCOUNT}:alarm:CPU_Utilization_\"}],\"detail\": {\"state\": {\"value\": [\"ALARM\"]}}}"

ルールができました。
続いてターゲットを登録します。
ターゲットには SNS トピックを指定します。
ここで入力トランスフォーマーを用いて日本語に整形しています。

コマンド一発だと長くなって難しいので JSON ファイルを事前に作成します。

cat << EOT > rule.json
{
  "Rule": "${YourRuleName}",
  "Targets": [
    {
      "Id": "1",
      "Arn": "arn:aws:sns:${AWSREGION}:${AWSACCOUNT}:${YourTopicName}",
      "InputTransformer": {
        "InputPathsMap": {
          "Account": "$.account",
          "AlarmName": "$.detail.alarmName",
          "MetricsInstance": "$.detail.configuration.metrics[0].metricStat.metric.dimensions.InstanceId",
          "MetricsName": "$.detail.configuration.metrics[0].metricStat.metric.name",
          "Reason": "$.detail.state.reason",
          "Time": "$.time"
        },
        "InputTemplate": "\"System Status Check Failed Alarmを発行しました\"\n\"AWS Account ID : <Account>\"\n\"発生時間(GMT) : <Time>\"\n\"アラーム名 : <AlarmName>\"\n\"発生インスタンス : <MetricsInstance>\"\n\"発生メトリクス : <MetricsName>\"\n\n\"理由 : <Reason>\""
      }
    }
  ]
}
EOT

JSON ファイルを読み込むコマンドを実行してターゲットを登録します。

aws events put-targets --cli-input-json file://rule.json

マネージメントコンソールでEventBridgeを見る

作成した EventBridge ルールを見てみます。
作成したルールの編集ボタンを押してみると以下のようになっています。

入力トランスフォーマーの簡単な説明

Input Path Map

冒頭で紹介したツライ通知で飛んでくる JSON を変数にマップします。
通知メールに記載したい情報をここで選択しています。

{"Account":"$.account","AlarmName":"$.detail.alarmName","MetricsInstance":"$.detail.configuration.metrics[0].metricStat.metric.dimensions.InstanceId","MetricsName":"$.detail.configuration.metrics[0].metricStat.metric.name","Reason":"$.detail.state.reason","Time":"$.time"}

スキーマレジストリ

マップ元の JSON の内容を事前に知っておきたいことがあると思います。
その場合はスキーマレジストリを参照します。
マネジメントコンソールだと こちら を開きます。

CloudWatch アラームですと aws.cloudwatch@CloudWatchAlarmStateChange という名前のスキーマが該当します。

Input Template

このテンプレートはメール本文に記載される内容になります。
日本語が使えます。
改行する際にはダブルクォテーションで囲んでください。

<> 山カッコに Input Path Map で定義した変数を記入します。

"CPU Utilization Alarmを発行しました"
"AWS Account ID : <Account> "
"発生時間(GMT) : <Time>"
"アラーム名  :  <AlarmName>"
"発生インスタンス :  <MetricsInstance>"
"発生メトリクス :  <MetricsName>"

"理由 : <Reason>"

結果

Map と Template を本エントリ通り設定すると以下のようなメールが届きます。

"CPU Utilization Alarmを発行しました"
"AWS Account ID : nnnnnnnnnnnn"
"発生時間(GMT) : 2021-07-31T01:44:30Z"
"アラーム名  :  CPU_Utilization_YourHostName"
"発生インスタンス :  i-0000000000000000"
"発生メトリクス :  CPUUtilization"

"理由 : Threshold Crossed: 1 out of the last 1 datapoints [0.0833333333333333 (31/07/21 01:43:00)] was less than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition)."

まとめ

Ops JAWS で通知と手順書をセットにしようという話をさせていただきました。
Ops JAWS Meetup#19 勉強会 でLT登壇しましたので資料を公開します #opsjaws #jawsug
別途ドキュメントを用意した例を示したのですが、今回の方法を使えば通知メール本文に対応手順を記載することができるかもしれません。

意味不明な内容で読まない通知を大量発生させることはシステム運用にとって不利益です。
意味のある通知を実現したいと考えます。

参考

Transforming Amazon EventBridge target input
Tutorial: Use Input Transformer to Customize What is Passed to the Event Target
[新機能] Amazon CloudWatch EventsのInput Transformerを試してみた
GuardDutyからのイベント通知をちょっと見やすくして通知する

以上、吉井 亮 がお届けしました。