Top View


Author k-masatany

CloudWatchAlarmをグラフ付きでSlackに通知する

2018/10/17


最近趣味でトランペットを始めた政谷です。

少し前に、CloudWatchのグラフをAPIで取得できるようになりましたね
これまでは、アラームが飛んで来ても、具体的なデータの推移状況はマネジメントコンソールにアクセスして、ダッシュボードを確認する必要がありました。

個人的に、この機能追加によって、AWSの運用監視がグッと楽になるのではないかと思い、
CloudWatchAlarmの内容をグラフ付きでSlack通知するためのサーバーレスアプリケーションを作りました。

動作フロー

  • CloudWatchAlarmが発火
  • SNSにTopicが追加される
  • Lambdaが発火
  • SNS Topicに記載されたTrigger情報を取得して、getMetricsGraphFromCloudWatchAPIで1時間前までのCloudWatchMetricsの画像を取得
  • 取得したデータをS3にPut
  • SNSの情報と、Putした画像のURLを使って、Slackのwebhookのペイロードを作成
  • Slack通知

CloudFormation

このアプリケーションのデプロイには、AWS SAMを使用します。

AWSTemplateFormatVersion: '2010-09-09'
 Transform: AWS::Serverless-2016-10-31
 Description: \>
 cloudwatch\_snapshot\_graph\_to\_slack
 Cloudwatch alarm notification to Slack with snapshot graph.
 Parameters:
 SNSTopic:
 Type: String
 Default: 'alarm-notice-topic'
 AssetsBucketName:
 Type: String
 Default: 'image-upload-bucket'
 WebHookURL:
 Type: String
 Default: 'https://example.com'
 Channel:
 Type: String
 Default: 'slack-channel'
 Username:
 Type: String
 Default: 'SlackBot'
 IconEmoji:
 Type: String
 Default: ':slack:'
 
 Resources:
 Function:
 Type: AWS::Serverless::Function
 Properties:
 Description: 'post sns messages to slack incoming webhooks'
 CodeUri: cloudwatch\_snapshot\_graph\_to\_slack/
 Handler: app.lambdaHandler
 Runtime: nodejs8.10
 Timeout: 30
 MemorySize: 512
 Events:
 SNS:
 Type: SNS
 Properties:
 Topic: !Ref Topic
 Environment:
 Variables:
 SLACK\_WEBHOOK\_URL: !Ref WebHookURL
 SLACK\_CHANNEL: !Ref Channel
 SLACK\_USERNAME: !Ref Username
 SLACK\_ICONEMOJI: !Ref IconEmoji
 BACKET\_NAME: !Ref AssetsBucketName
 TZ: Asia/Tokyo
 Role: !GetAtt IamRole.Arn
 
 AssetsBucket:
 Type: AWS::S3::Bucket
 Properties:
 BucketName: !Ref AssetsBucketName
 AccessControl: PublicRead
 LifecycleConfiguration:
 Rules:
 - Status: Enabled
 ExpirationInDays: 7
 WebsiteConfiguration:
 IndexDocument: index.html
 ErrorDocument: error.html
 BucketPolicy:
 Type: AWS::S3::BucketPolicy
 Properties:
 Bucket: !Ref AssetsBucket
 PolicyDocument:
 Statement:
 - Action:
 - 's3:GetObject'
 Effect: 'Allow'
 Resource: !Sub arn:aws:s3:::${AssetsBucket}/\*
 Principal: '\*'
 
 Topic:
 Type: 'AWS::SNS::Topic'
 Properties:
 TopicName: !Ref SNSTopic
 
 IamRole:
 Type: AWS::IAM::Role
 Properties:
 AssumeRolePolicyDocument:
 Version: '2012-10-17'
 Statement:
 - Effect: Allow
 Principal:
 Service: lambda.amazonaws.com
 Action: 'sts:AssumeRole'
 Policies:
 - PolicyName: 'cloudwatch\_snapshot\_graph\_to\_slack'
 PolicyDocument:
 Version: '2012-10-17'
 Statement:
 - Effect: 'Allow'
 Action:
 - 'autoscaling:Describe\*'
 - 'cloudwatch:Describe\*'
 - 'cloudwatch:Get\*'
 - 'cloudwatch:List\*'
 - 'logs:Get\*'
 - 'logs:List\*'
 - 'logs:Describe\*'
 - 'logs:TestMetricFilter'
 - 'logs:FilterLogEvents'
 - 'sns:List\*'
 - 'sns:Get\*'
 Resource: '\*'
 - Effect: 'Allow'
 Action:
 - 'logs:CreateLogGroup'
 - 'logs:CreateLogStream'
 - 'logs:PutLogEvents'
 Resource: 'arn:aws:logs:\*:\*:\*'
 - Effect: 'Allow'
 Action:
 - s3:PutObject
 Resource: !Sub arn:aws:s3:::${AssetsBucket}/\*

Parameterを自分の環境向けに編集して使用します。
Makefileを用意しているので、環境変数で上書きすることもできます。

SAMのデプロイには、先んじてS3バケットが必要になりますので、事前に作成しておいてください。
デプロイしたら、SNSとLambdaと公開用のS3バケットが作成されるので、
CloudWatchAlarmの通知先をSNSにしてLambdaが発火するようにします。

Lambdaコード

このサーバレスアプリケーションで動作するLambdaのコードは下記になります。
ランタイムはNode.js8.10です。

"use strict"
 
 const axios = require("axios")
 var AWS = require("aws-sdk")
 const BASE\_URL = process.env.SLACK\_WEBHOOK\_URL
 
 async function getMetricsGraphFromCloudWatch(MessageId, message) {
 const props = {
 width: 320,
 height: 240,
 start: "-PT1H",
 end: "PT0H",
 timezone: "+0900",
 view: "timeSeries",
 stacked: false,
 metrics: [
 [
 message.Trigger.Namespace,
 message.Trigger.MetricName,
 message.Trigger.Dimensions[0].name,
 message.Trigger.Dimensions[0].value
 ]
 ],
 stat:
 message.Trigger.Statistic.charAt(0).toUpperCase() +
 message.Trigger.Statistic.slice(1).toLowerCase(),
 period: message.Trigger.Period
 }
 const widgetDefinition = {
 MetricWidget: JSON.stringify(props)
 }
 
 const cloudwatch = new AWS.CloudWatch()
 const s3 = new AWS.S3()
 
 try {
 const cw\_res = await cloudwatch
 .getMetricWidgetImage(widgetDefinition)
 .promise()
 const res = await s3
 .putObject({
 Bucket: process.env.BACKET\_NAME,
 Key: `${MessageId}.png`,
 Body: cw\_res.MetricWidgetImage,
 ContentType: "image/png"
 })
 .promise()
 return res
 } catch (err) {
 console.error(err)
 }
 }
 
 async function createPayload(records) {
 let attachments = []
 for (const record of records) {
 const sns = record.Sns
 const message = JSON.parse(sns.Message)
 let color = "good"
 if (message.NewStateValue == "ALARM") {
 color = "danger"
 } else if (message.NewStateValue != "OK") {
 color = "warning"
 }
 await getMetricsGraphFromCloudWatch(sns.MessageId, message)
 let attachment = {
 fallback: sns.Subject,
 title: message.AlarmName,
 pretext: sns.Subject,
 color: color,
 text: message.NewStateReason,
 image\_url: `https://s3-ap-northeast-1.amazonaws.com/${
 process.env.BACKET_NAME
 }/${sns.MessageId}.png`
 }
 attachments.push(attachment)
 }
 
 return {
 channel: process.env.SLACK\_CHANNEL,
 username: process.env.SLACK\_USERNAME,
 icon\_emoji: process.env.SLACK\_ICONEMOJI,
 attachments: attachments
 }
 }
 
 function createOptions(payload) {
 let options = {
 method: "post",
 baseURL: BASE\_URL,
 headers: {
 "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
 },
 data: `payload=${JSON.stringify(payload)}`
 }
 return options
 }
 
 exports.lambdaHandler = async (event, context) =\> {
 const payload = await createPayload(event.Records)
 const options = createOptions(payload)
 
 try {
 const res = await axios.request(options)
 response = {
 statusCode: 200,
 body: JSON.stringify({
 message: "hello world",
 location: res.data.trim()
 })
 }
 } catch (err) {
 console.log(err)
 return err
 }
 
 return response
 }

結果

このサーバーレスアプリケーションを使うと、Slackにこんな感じで通知されます

これならアラートと一緒に時系列データを確認できるので、一々ダッシュボードを確認してデータの遷移状況を確認する必要がないのでとても楽ですね。

まとめ

今回はCloudWatchAlarmの内容を、画像付きでSlack通知してみました。
実際の通知内容を見て、これは非常に便利だと感じているので、業務でもバンバン使ってアプリケーションを洗練させていきたいと思います。

現場からは以上です。

k-masatany

k-masatany

Twitter X

インターネットの海で泳ぐときは、だいたいペンギンの姿をしています。