この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
上図のように CloudWatch Alarmから 通知先にSNSトピックを選択したときに送られるメールは以下のような文章です。
You are receiving this email because your Amazon CloudWatch Alarm "alarm-test-ec2-cpu" in the Asia Pacific (Tokyo) region has entered the ALARM state, because "Threshold Crossed: 1 out of the last 1 datapoints [50.0 (15/09/20 06:51:00)] was greater than the threshold (70.0) (minimum 1 datapoint for OK -> ALARM transition)." at "Tuesday 15 September, 2020 06:56:39 UTC".
View this alarm in the AWS Management Console:
(CloudWatch Alarmへのリンク)
Alarm Details:
- Name: alarm-test-ec2-cpu
- Description: XXX
- State Change: OK -> ALARM
- Reason for State Change: Threshold Crossed: 1 out of the last 1 datapoints [50.0 (15/09/20 06:51:00)] was greater than the threshold (70.0) (minimum 1 datapoint for OK -> ALARM transition).
- **Timestamp:** Tuesday 15 September, 2020 06:56:39 UTC
- AWS Account: XXXXXXXXXXXX
- Alarm Arn: arn:aws:cloudwatch:ap-northeast-1:XXXXXXXXXXXX:alarm:alarm-test-ec2-cpu
(以下略)
Timestamp
のタイムゾーンが UTCなので、 これをローカル(JST)にしたい です。
背景として、CloudWatch Alarm の通知を利用した運用をされている方は多いと思います。 メールが届いたときの、何かしらの初動・調査を行う際に 毎回 時刻の部分が UTCだと、 変換に手間がかかりますよね。
CloudWatchマネジメントコンソール上では ローカルタイムゾーンとして、 様々なグラフ・メトリクスを見ることができます。 しかし、CloudWatch Alarmからの通知文のタイムスタンプを JSTにするオプションはありません。
そこで、今回は Lambdaを介して JSTの時刻を付与したメッセージを送信する構成を作ってみます。 以下のような構成です。
構成
作成するリソースは以下の通り。先程の構成図通りです。
- SNSトピック(via CW Alarm): CloudWatch Alarm の通知用。Lambda関数のトリガー
- SNSトピック(via Lambda): Lambda関数からの通知用。メール送信
- Lambda関数: CloudWatch Alarmからのメッセージの処理用
SNSトピックは2つ作成します。CloudWatch Alarm からの通知先と、Lambda関数からの通知先です。
SNSトピック(via Lambda)
を購読(subscribe)しておきます。
Lambda関数の実行ロールの権限は以下の通り。
Lambdaの基本的な実行ポリシー ( AWSLambdaBasicExecutionRole
)に sns:Publish
を追加したものです。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"sns:Publish"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
Lambda Function
Python3.7 で Lambda Functionを作成します。以下のようなコードを書いてみました。 タイムゾーンの変換に pytz を使用しています。
import json
import os
import boto3
from pytz import timezone
from dateutil import parser
SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN']
client = boto3.client("sns")
def lambda_handler(event, context):
subject = event["Records"][0]["Sns"]["Subject"]
message = json.loads(event["Records"][0]["Sns"]["Message"])
client.publish(
TopicArn=SNS_TOPIC_ARN,
Subject=subject,
Message=parse(message)
)
def parse(message):
texts = []
# parse UTC to JST
jst_time = parser.parse(message["StateChangeTime"]
).astimezone(timezone('Asia/Tokyo'))
# Summary
texts.append("{} state is now {}: {}".format(
message["AlarmName"],
message["NewStateValue"],
message["NewStateReason"]
))
texts.append("StateChangeTime(JST): {}".format(jst_time))
# Details
texts.append("### Details")
for k, v in message.items():
texts.append("- {}: {}".format(k, v))
return "\n".join(texts)
環境変数に SNS_TOPIC_ARN
を作成して、 SNSトピック(via Lambda)のARNを指定します。
※SNS通知が Lambdaに渡されるときのイベントフォーマットは以下参考になります。
SAMで構築
上記構成をAWS サーバーレスアプリケーションモデル (AWS SAM)を使って構築しました。 SAMのプロジェクト構成内容を記します。
プロジェクト
sam init
で新規プロジェクトを作成します。
sam init --runtime python3.7 --name cw-alarm-notification-with-jst
template.yaml
必要なリソースを記述した template.yaml
は以下です。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: change the UTC to JST, for SNS topics via CW Alarm
Parameters:
EmailAddress:
Type: String
Resources:
# SNS topic (used by CW Alarm)
SnsTopicFromCWAlarm:
Type: AWS::SNS::Topic
Properties:
TopicName: cw-alarm-topic
# SNS topic (used by Lambda)
SnsTopicFromLambda:
Type: AWS::SNS::Topic
Properties:
TopicName: cw-alarm-topic-jst-processed
SnsSubscription:
Type: AWS::SNS::Subscription
Properties:
Protocol: email
Endpoint: !Ref EmailAddress
TopicArn: !Ref SnsTopicFromLambda
# IAM Role
IamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
Policies:
- PolicyName: "LambdaPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "sns:Publish"
Resource: "*"
# Function
Function:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.lambda_handler
Runtime: python3.7
Environment:
Variables:
SNS_TOPIC_ARN: !Ref SnsTopicFromLambda
Role: !GetAtt IamRole.Arn
Events:
EventBridgeRule:
Type: SNS
Properties:
Topic: !Ref SnsTopicFromCWAlarm
src/app.py
(Lambda関数)
前述のLambda関数のコードを src/app.py
に格納します。
src/requirements.txt
必要なパッケージを記します。タイムゾーン指定に pytz
を使用するため、記載します。
pytz
ビルド、デプロイ
sam build
sam deploy --guided
デプロイの設定は下記のとおりです。パラメータに指定した EmailAddress
に送信先メールアドレス を記入します。
> sam deploy --guided
Configuring SAM deploy
======================
Setting default arguments for 'sam deploy'
=========================================
Stack Name [cw-alarm-notification-with-jst]: cw-alarm-notification-with-jst
AWS Region [ap-northeast-1]: ap-northeast-1
Parameter EmailAddress []: sample@example.co.jp
確認
適当に作成した CloudWatch Alarm の通知先を作成した SNSトピック(via CloudWatch)
に指定して、
アラーム状態にしてみました。
以下のような メールが届きます。
ec2-cpu-alarm-test state is now ALARM:
Threshold Crossed: 1 out of the last 1 datapoints [50.0 (15/09/20 09:18:00)] was greater than the threshold (70.0) (minimum 1 datapoint for OK -> ALARM transition).
**StateChangeTime(JST): 2020-09-15 18:33:54.193000+09:00**
### Details
- AlarmName: ec2-cpu-alarm-test
- AlarmDescription: ec2 cpu alarm test
- AWSAccountId: XXXXXXXXXXXX
- NewStateValue: ALARM
- NewStateReason: Threshold Crossed: 1 out of the last 1 datapoints [50.0 (15/09/20 09:18:00)] was greater than the threshold (70.0) (minimum 1 datapoint for OK -> ALARM transition).
- StateChangeTime: 2020-09-15T09:33:54.193+0000
- Region: Asia Pacific (Tokyo)
- AlarmArn: arn:aws:cloudwatch:ap-northeast-1:XXXXXXXXXXXX:alarm:ec2-cpu-alarm-test
- OldStateValue: OK
- Trigger: {'MetricName': 'CPUUtilization', 'Namespace': 'AWS/EC2', 'StatisticType': 'Statistic', 'Statistic': 'AVERAGE', 'Unit': None, 'Dimensions': [{'value': 'i-01xxxx', 'name': 'InstanceId'}], 'Period': 300, 'EvaluationPeriods': 1, 'ComparisonOperator': 'GreaterThanThreshold', 'Threshold': 70.0, 'TreatMissingData': '- TreatMissingData: missing', 'EvaluateLowSampleCountPercentile': ''}
通知に JST時刻 StateChangeTime(JST): 2020-09-15 18:33:54.193000+09:00 を付与できました。 (それ以外は、Alarmの情報をそのまま出力しているだけです)
※ちなみにアラームのテストは AWS CLIから強制的に行うことが可能です。以下参考ください。
おわりに
以上、CloudWatch Alarm の情報に JST時刻を付与してメール通知してみました。
CloudWatch Alarm → SNS → メール
の構成の場合、受信するメールの文は
良い感じに加工してくれるみたいですね。
( You are receiving this email because your Amazon CloudWatch Alarm ...
の部分)
Lambdaを介した今回の構成では、この加工は適用されません。 なので 「オリジナルの文章を 時刻だけJSTにして他はそのままに」みたいなことはできませんでした。
オリジナルの文章ほどリッチじゃありませんが、JST時間を付与できました。
少しでもどなたかのお役に立てば幸いです。