CloudWatch Alarm から通知される文章の UTC時刻を JST時刻に差し替えたい
はじめに
上図のように 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時間を付与できました。
少しでもどなたかのお役に立てば幸いです。