CloudWatch ダッシュボードを定期的にSlack通知する(ソース付き)
はじめに
最近TypeScriptが楽しいので、実益兼ねて作ったものをご紹介します
どんなものを作るか
AWS上でサーバレスに構築したLPを個人で開発・保守・運用しています👷♂️
今まで、メトリクスの異常値(CloudFrontのリクエスト数増加、Lambdaでのエラー発生、請求金額など)については、CloudWatchアラーム -> SNS -> Chatbot -> Slack
という経路で適宜通知を行っていたのですが、
平常時の値についてはダッシュボードを作成したもののあまり見れておらず、Slack上でもう少し手軽&こまめに確認したい...と考えていました
AWS SDKにはGetMetricWidgetImageという、メトリクスを画像出力できるというそのものズバリなやつがありますが、
CloudWatchダッシュボードをそのまま出力する機能は提供されておらず、何かしら工夫が必要となります
ということで、TypeScriptとServerless Frameworkを用いて、CloudWatchダッシュボードを日次でSlackに通知する仕組みを開発することとしました🐔
どうやって作るか
この上なく簡単な構成図がこちらです
ダッシュボードはコンソールで手ポチで作成した上で、JSONエクスポートしたものをソースコードから読み込み利用します
Lambda上でダッシュボードの定義ファイルを読み込み、メトリクス毎に画像出力します
そのまま画像毎にSlackに投稿しても悪くはないですが、好みとしてはその時のメトリクスの断面を残しておきたかったので、merge-imagesというライブラリを用いて各メトリクス画像を1枚の画像にまとめることとします
本ライブラリの動作にはnode-canvasが必要になりますが、例によってLambda上では動かず困っていたところ、
渡りに船⛴とばかりにnode-canvas-lambdaなるLambda Layerを作っている方がいたので、こちらをありがたく読み込んで使います
作ったもの
コードは以下になります
構築方法
ダッシュボードを作成した上で、 アクション -> ソースの表示/編集
で表示されるJSON定義をコピーし、widgets/sample.json
を上書きしてください
{
"comment": "replace me!"
}
serverless.yml
の deploymentBucket
や schedule
、関数のenvironment
などを適宜変更してください
service:service:
name: cw-metrics-notifier
provider:
name: aws
runtime: nodejs12.x
region: ap-northeast-1
stage: prod
deploymentBucket: xxxxxxx
memorySize: 256
timeout: 60
environment:
SLACK_TOKEN: ${env:SLACK_TOKEN}
SLACK_METRICS_CHANNEL_ID: ${env:SLACK_METRICS_CHANNEL_ID}
iamRoleStatements:
- Effect: Allow
Action:
- cloudwatch:GetMetricWidgetImage
Resource: "*"
functions:
widgets:
handler: src/widgets.scheduledHandler
environment:
# widgets/ 配下のファイル名(カンマ区切り可)
WIDGETS_NAMES: sample
# マージ画像でx方向にいくつまでメトリクスを並べるか
METRICS_X_COUNT: 2
# 何日前までを表示対象にするか
METRICS_DAYS_AGO: 7
events:
- schedule:
rate: cron(0 0 * * ? *)
enabled: true
layers:
- { Ref: NodeCanvasLambdaLayer }
- { Ref: CanvasLib64LambdaLayer }
layers:
nodeCanvas:
package:
artifact: vendor/node-canvas-lambda/node12_canvas_layer.zip
canvasLib64:
package:
artifact: vendor/node-canvas-lambda/canvas-lib64-layer.zip
package:
include:
- widgets/
plugins:
- serverless-plugin-typescript
- serverless-plugin-optimize
custom:
optimize:
exclude:
- canvas
includePaths:
- widgets/
node-canvas-lambdaの取得については、npm scriptsに書いてあるので気にしなくてもokです
{
"scripts": {
"predeploy": "rm -rf .build; [[ -d vendor/node-canvas-lambda ]] || (cd vendor && git clone https://github.com/jwerre/node-canvas-lambda.git)",
"deploy": "dotenv -- sls deploy -v"
}
}
直してみてよさそうだったらデプロイ
# インストール
$ npm ci
# 環境変数情報を追加
$ cp -p .env.sample .env
$ cat .env
AWS_ACCESS_KEY_ID=xxxxx
AWS_SECRET_ACCESS_KEY=xxxxx
SLACK_TOKEN=xoxp-xxxxx
SLACK_METRICS_CHANNEL_ID=Cxxxxx
# デプロイ
$ npm run deploy
スケジュールした時間が来ると、Slackの所定のチャンネルにダッシュボードっぽい画像が投稿されます🍨
実際のコードはGitHubを見て頂くのがよいですが、肝となるメトリクスの画像出力とマージのコードを貼っておきます
MetricWidget
に何が渡せるかはこちらを見るといい感じに書いてあります
あと、これはちゃんと調査できてないのですが、timeSeries
タイプ以外のメトリクスはいい感じに描画されない感じがしたので、取得対象外としています
そしてグラフのキャプションに日本語などマルチバイト文字を入れると文字化けします...
// ウィジェット毎にpng画像を生成し、マージ用元データを作成する
const imageSources = await Promise.all(
widgets
.filter(widgetPart => widgetPart?.properties?.view === 'timeSeries')
.map(async (widgetPart, index) => {
const properties = widgetPart.properties;
const imagePath = path.join(imagesDir, `${index}.png`);
// +0000 形式
const timezone = moment()
.format('Z')
.replace(':', '');
const cw = new CloudWatch({
region: properties.region,
});
// AWSにリクエストし、結果をファイル保存
const output = await cw
.getMetricWidgetImage({
MetricWidget: JSON.stringify({
...properties,
start: `-PT${metricsDaysAgo * 24}H`,
end: 'PT0H',
timezone,
width: imageWidth,
height: imageHeight,
}),
})
.promise();
await fs.writeFile(imagePath, output.MetricWidgetImage);
// マージ用元データを作成し返却
// 描画位置の指定をおこない、画像を metricsXCount ずつ横に並べる
const source: ImageSource = {
src: imagePath,
x: imageWidth * (index % metricsXCount),
y: imageHeight * Math.floor(index / metricsXCount),
};
return source;
}),
);
// 画像のマージ
// ライブラリ都合で Canvas.Image が存在しないとエラーとなったため以下指定
const canvas: any = Canvas;
canvas.Image = Image;
const mergedImageBase64 = await mergeImages(imageSources, {
Canvas: canvas,
width: imageWidth * metricsXCount,
height: imageHeight * Math.ceil(imageSources.length / metricsXCount),
});
// Base64形式からファイルに変換
const metricsData = mergedImageBase64.replace(/^data:image\/png;base64,/, '');
const metricsPath = path.join(imagesDir, `${widgetsName}.png`);
await fs.writeFile(metricsPath, metricsData, 'base64');
今回は主旨から逸れるので触れませんが、.github/workflows/
配下にGitHub Actionsでデプロイする用のワークフローも入れていますので、必要あらばご利用ください
まとめ
TypeScriptは、静的型付けのメリットを手軽に享受しつつ、Node製の各種ライブラリを活用できつつ、Lambdaはじめサーバレスとの親和性も高く、フロントも書けるんだからだいぶ最高感ありますね...!
どんどん使っていきたいと思います🍚
Author And Source
この問題について(CloudWatch ダッシュボードを定期的にSlack通知する(ソース付き)), 我々は、より多くの情報をここで見つけました https://qiita.com/yktakaha4/items/3df6947c77599a722762著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .