はじめに
CloudWatchAlarm を Slack へ通知しているが、もっとわかりやすく出来ないかと思って探していると、
CloudWatchAlarmをグラフ付きでSlackに通知する という素晴らしい記事を見つけたので、試させてもらいました。
記事の内容と異なる点は、 S3 を使わず Slack file.upload API を利用して、画像をアップロードするようにした点です。
Python の例は何個かあったのですが、node.js で Slack file.upload API を利用してグラフ画像をアップする例はなかったので、参考になればと思います。
ソースは以下です。
SlackBot
Slack file.upload API を使う方法は何通りかあります。利用するトークンにも何種類かあります。
ここでは、 SlackApp を作り Bot とトークンを用意します。
SlackApp の作成とトークン
上記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の作成」が参考になります。
さいごに
まだ、この設定を行ってアラームは発生していないので、まわりの反応を見ることはできませんが、テストでアラームを発生させた限りだと、画像があることでかなりわかりやすくなったと思います。
忘れたころに来るアラームにも慌てずに対処できるようになると良いな。