k-masatany
最近趣味でトランペットを始めた政谷です。
少し前に、CloudWatchのグラフをAPIで取得できるようになりましたね。
これまでは、アラームが飛んで来ても、具体的なデータの推移状況はマネジメントコンソールにアクセスして、ダッシュボードを確認する必要がありました。
個人的に、この機能追加によって、AWSの運用監視がグッと楽になるのではないかと思い、
CloudWatchAlarmの内容をグラフ付きでSlack通知するためのサーバーレスアプリケーションを作りました。
GitHub - k-masatany/cloudwatch_snapshot_graph_to_slack: https://aws.amazon.com/about-aws/whats-new/2018/09/amazon-cloudwatch-adds-ability-to-build-custom-dashboards-outside-the-aws-console/
https://aws.amazon.com/about-aws/whats-new/2018/09/amazon-cloudwatch-adds-ability-to-build-custom-dashboards-outside-the-aws-console/ - k-masatany/cloudwatch_snapshot_graph_to_slack
動作フロー
- CloudWatchAlarmが発火
- SNSにTopicが追加される
- Lambdaが発火
- SNS Topicに記載されたTrigger情報を取得して、
getMetricsGraphFromCloudWatch
APIで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通知してみました。
実際の通知内容を見て、これは非常に便利だと感じているので、業務でもバンバン使ってアプリケーションを洗練させていきたいと思います。
現場からは以上です。