AWS
CloudWatch
lambda
Slack

CloudWatch AlarmをSlackへ通知する(グラフ 画像 付き)


はじめに

CloudWatchAlarm を Slack へ通知しているが、もっとわかりやすく出来ないかと思って探していると、

CloudWatchAlarmをグラフ付きでSlackに通知する という素晴らしい記事を見つけたので、試させてもらいました。

記事の内容と異なる点は、 S3 を使わず Slack file.upload API を利用して、画像をアップロードするようにした点です。

Python の例は何個かあったのですが、node.js で Slack file.upload API を利用してグラフ画像をアップする例はなかったので、参考になればと思います。

ソースは以下です。


SlackBot

Slack file.upload API を使う方法は何通りかあります。利用するトークンにも何種類かあります。

ここでは、 SlackApp を作り Bot とトークンを用意します。


SlackApp の作成とトークン

https://api.slack.com/apps

上記URLにアクセスし「Create New App」ボタンをクリックして、SlackApp を作ります。

次に、左メニュー「Bot Users」をクリックして、Bot を作ります。

最後に、左メニュー「OAuth & Permissions」をクリックし、「Install App to Workspace」ボタンをクリックし Slack Workspace へ SlackApp をインストールします。

すると「OAuth & Permissions」に「Bot User OAuth Access Token」が表示されます。このトークンが後ほど作成する Lambda に記述するトークンです。


Source

Lambda は SAM を使って、デプロイします。

各ファイルを説明します。


index.js

Lambdaで実行するファイルです。

getMetricWidgetImage メソッドで取得したグラフの画像は Buffer として返却されますが、 Buffer のままではアップロードすることができませんでした。

そのため、画像ファイルとして出力後(fs.writeFileSync)、読み込むこと(fs.createReadStream)で StreamReader へ変換しています。

これでアップロードすることができました。

'use strict'

const AWS = require('aws-sdk')
const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')
const uuidv4 = require('uuid/v4')

const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN
const SLACK_CHANNEL = process.env.SLACK_CHANNEL

async function getMetricsGraphFromCloudWatch(message) {
const props = {
width: 480,
height: 240,
start: '-PT3H',
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,
annotations: {
horizontal: [
{
color: '#ff6961',
label: 'Trouble threshold start',
value: 0
}
]
}
}
const widgetDefinition = {
MetricWidget: JSON.stringify(props)
}

const cloudwatch = new AWS.CloudWatch()
try {
const response = await cloudwatch
.getMetricWidgetImage(widgetDefinition)
.promise()
return response.MetricWidgetImage
} catch (err) {
console.error(err)
}
}

async function sendSlackBot(records) {
for (const record of records) {
const sns = record.Sns
const message = JSON.parse(sns.Message)

let emoji = ':kissing:'
if (message.NewStateValue == 'ALARM') {
emoji = ':scream:'
} else if (message.NewStateValue == 'OK') {
emoji = ':grinning:'
}

const image = await getMetricsGraphFromCloudWatch(message)

// to stream from buffer
const file = '/tmp/' + uuidv4() + '-alarm.png'
fs.writeFileSync(file, image)
const streamImage = fs.createReadStream(file)

const formData = new FormData()
formData.append('token', SLACK_BOT_TOKEN)
formData.append('filename', message.AlarmName + '.png')
formData.append('file', streamImage)
formData.append('filetype', 'png')
formData.append('initial_comment', emoji + ' ' + sns.Subject + ', ' + message.NewStateReason)
formData.append('channels', SLACK_CHANNEL)
formData.append('title', sns.Subject)

const response = await axios.create({
headers: form.getHeaders()
}).post('https://slack.com/api/files.upload', formData)
console.log(response.data)
}
}

exports.handler = async (event, context) => {
// output event
console.log(JSON.stringify(event))

const response = {
statusCode: 200,
body: JSON.stringify({
message: "OK"
})
}

try {
await sendSlackBot(event.Records)
} catch (err) {
console.log(err)
return err
}

return response
}


package.json

node.js で Lambda を使う場合、aws-sdk はデフォルトで利用できますが、バージョンが古く

CloudWatch.getMetricWidgetImage メソッドが利用できません。

そのため、別途インストールする必要があります。

{

"name": "aws-cloudwatchalarm-to-slack",
"version": "0.0.1",
"description": "Cloudwatch alarm to Slack.",
"main": "index.js",
"scripts": {},
"dependencies": {
"aws-sdk": "^2.334.0",
"axios": "^0.18.0",
"form-data": "^2.3.3",
"uuid": "^3.3.2"
},
"devDependencies": {}
}


template.yaml

SAM のテンプレートファイルです。

Paramters で指定することにより、deploy 時、コマンド引数で指定することができます。

Resources として、Lambda、SNS Topic、IAM Role を指定し作成します。

今回の IAM Role は、あらかじめ用意されている ManagedPolicy を利用しています。

ManagedPolicyArns に指定している内容は、AWS管理コンソール > IAM > ポシリーの一覧から該当ポリシーを選択し詳細を見ると確認することができます。

AWSTemplateFormatVersion: '2010-09-09'

Transform: 'AWS::Serverless-2016-10-31'
Description: 'Cloudwatch alarm to Slack.'
Parameters:
SnsTopicName:
Type: String
Default: 'xxx-alarm-topic'
SlackBotToken:
Type: String
Default: 'xxx-999-999-xxx'
SlackChannel:
Type: String
Default: '#notify_xxx_alarm'

Resources:
NotifyFunction:
Type: 'AWS::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs8.10
Role: !GetAtt NotifyRole.Arn
CodeUri: .
Description: 'Cloudwatch alarm to Slack.'
MemorySize: 128
Timeout: 300
Environment:
Variables:
SLACK_BOT_TOKEN: !Ref SlackBotToken
SLACK_CHANNEL: !Ref SlackChannel
Events:
SNS:
Type: SNS
Properties:
Topic: !Ref NotifyTopic
NotifyTopic:
Type: 'AWS::SNS::Topic'
Properties:
TopicName: !Ref SnsTopicName
NotifyRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
- 'arn:aws:iam::aws:policy/AmazonSNSReadOnlyAccess'
- 'arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess'


Install & Deploy

AWS CLI を使って deploy するので、事前に AWS CLI を インストール し、設定(aws configure) を行ってください。

また、 npm のインストールを Docker を利用して行うので、こちらもインストールしてください(Install Docker Desktop for Mac, Install Docker Desktop for Windows)。

コマンド実行時に変更が必要な引数のみを説明します。


install

docker run -it -v $(pwd):/home/app -w /home/app node:8.12 npm install


package

Argument
Example
Description

--s3-bucket
xxx-bucket-sam
ソース等(アーティファクト)をアップロードするバケット

aws cloudformation package \

--template-file template.yml \
--output-template-file serverless-output.yaml \
--s3-bucket xxx-bucket-sam


deploy

Argument
Example
Description

SnsTopicName
xxx-alarm-topic
アラームを通知する SNS トピック名

SlackBotToken
xxx-999-999-xxx
Slack Bot のトークン

SlackChannel
#notify_xxx_alarm
アラームを通知する Slack のチャンネル

aws cloudformation deploy \

--template-file serverless-output.yaml \
--stack-name cloudwatchalarm-to-slack \
--capabilities CAPABILITY_IAM \
--parameter-overrides "SnsTopicName=xxx-alarm-topic" "SlackBotToken=xxx-999-999-xxx" "SlackChannel=#notify_xxx_alarm"


CloudWatch Alarm

AWS管理コンソールから CloudWatch Alarm を作成し、作成した SNS Topic名を指定すれば、Slack へアラームが画像付きで通知されます。

CloudWatch Alarm の設定は、CloudWatch Alarmでメール通知する際に一緒にグラフ画像を添付してみたの「CloudWatch Alarmの作成」が参考になります。


さいごに

まだ、この設定を行ってアラームは発生していないので、まわりの反応を見ることはできませんが、テストでアラームを発生させた限りだと、画像があることでかなりわかりやすくなったと思います。

忘れたころに来るアラームにも慌てずに対処できるようになると良いな。