【AWS】EventBridgeを使って毎日の課金額をSlackに通知する
概要
今回はAmazon EventBridge
で時間指定で「CostExplorer
からAWS
の利用料金を取得しSlack
に通知するLambda
」を起動する方法についてまとめます。
下記にある通り、同様の試みをされている例はいくつかあります。
それらの内容も参考にして、自分なりに解釈した方法の紹介になります。
途中Lambda
が登場しますが、今回メインとなるのはあくまでAmazon EventBridge
なので、そちらを中心として紹介になります。ご了承ください。
使用技術の確認
まずは、今回コアとなるAWS
の機能について紹介します。
AWS CostExplorerとは
AWS Cost Explorer の使いやすいインターフェイスでは、AWS のコストと使用量の経時的変化を可視化し、理解しやすい状態で管理できます。
その名の通りAWS
のコストを参照できます。
今回はCost Explorer API
を叩きますが、リクエストごとに0.01ドルかかるため闇雲に叩くのは禁物です。
参考
AWS Lambda
AWS Lambda は、サーバーレスでイベント駆動型のコンピューティングサービスであり、サーバーのプロビジョニングや管理をすることなく、事実上あらゆるタイプのアプリケーションやバックエンドサービスのコードを実行することができます。200 以上の AWS のサービスやサービス型ソフトウェア (SaaS) アプリケーションから Lambda をトリガーすることができ、使用した分だけお支払いいただきます。
言わずと知れたコンピューティングサービスで、各種コードをサーバレスで実行できます。
今回はNode
で動作するコードを使用します。
Amazon EventBridge
Amazon EventBridge はサーバーレスイベントバスであり、アプリケーション、統合された Software-as-a-Service (SaaS) アプリケーション、および AWS のサービスから生成されたイベントを使用して、イベント駆動型アプリケーションを大規模に構築することを容易にします。EventBridge は、Zendesk や Shopify などのイベントソースから AWS Lambda やその他の SaaS アプリケーションなどのターゲットにリアルタイムデータのストリームを配信します。ルーティングルールを設定して、データの送信先を決定し、イベントパブリッシャーとコンシューマーが完全に疎結合化された状態でデータソースにリアルタイムで反応するアプリケーションアーキテクチャを構築できます。
昔からAWS
に触れられている方にはCloud Watch Events
といった方が馴染みがいいかと思います。
バッチ処理の完了などの各種イベントを起点として、Lambda
などを起動することができます。
今回はcron
を用いて、Lambda
を毎日特定日時に起動するバッチのように使いました。
前準備
AWS
で作業に取り掛かる前に準備することがあります。
Slack Bot
の作成です。
人によってはSlack Webhook
を使っていますが、今回は「特定のチャンネルにメッセージを通知するBot
」を作成しました。
これについては以下の記事の通りになります。
あまり本筋ではないので割愛しますが、作成したBot
のToken
を控えておきましょう。
Lambdaの作成
Lambda
を作成していきます。
コードは以下の通りです。
なお、リージョンはus-east-1
とします。
環境変数にSlack Bot
のToken
と対象チャンネルのIDを設定しましょう。
const AWS = require('aws-sdk');
const axios = require('axios');
const ce = new AWS.CostExplorer({region: 'us-east-1'});
exports.handler = async (event) => {
// 昨日と今日のDateインスタンスを作成
const today = new Date();
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
// YYYY-MM-DD形式でstartとendを作成
const start = `${yesterday.getFullYear()}-${('0' + (yesterday.getMonth() + 1)).slice(-2)}-${('0' + yesterday.getDate()).slice(-2)}`;
const end = `${today.getFullYear()}-${('0' + (today.getMonth() + 1)).slice(-2)}-${('0' + today.getDate()).slice(-2)}`;
// CEに投げるパラメータ
const params = {
Granularity: 'DAILY',
TimePeriod: {
Start: start,
End: end,
},
Metrics: ['UnblendedCost'],
GroupBy: [{
Type: 'DIMENSION',
Key: 'SERVICE',
}],
};
// コストデータの結果格納用
let costResult = [];
// CEからコストデータを取得
const cost = await ce.getCostAndUsage(params).promise();
// 取得した結果をresultに格納していく
cost?.ResultsByTime?.forEach((c) => {
//console.log(c);
c?.Groups?.forEach((g) => {
if(g?.Metrics?.UnblendedCost?.Amount !== '0') {
costResult.push({
key: g?.Keys?.[0] ?? 'Any Service',
amount: g?.Metrics?.UnblendedCost?.Amount
})
}
})
});
// Slack投稿用の本文を作成
let markdown = `*_AWS Billing_* \r\n`;
// resultには各リソースごとの名称と金額が入っているのでリスト形式で出力
costResult.forEach((r) => {
markdown += `* ${r.key} : $${r.amount} \r\n`
});
// Slackに投稿
const res = await axios.post("https://slack.com/api/chat.postMessage", {
channel: process.env.SLACK_CHANNEL_ID,
mrkdwn: true,
text: markdown
}, {
headers: {
"Content-Type": "application/json; charset=utf-8",
"Authorization": "Bearer " + process.env.SLACK_TOKEN
}
});
const response = {
statusCode: 200,
body: JSON.stringify('Success!'),
};
return response;
};
また、ライブラリとしてaxios
などを利用しています。従ってLayers
を用いることをお勧めします。
Lambda
のデプロイやLayers
の作り方については自分の過去記事(下記)で解説しています。
注意点ですが、Lambda
の実行ロールに以下のポリシーを追加する必要があります。
用途はCostExplorer
からの値の読み取りです。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage"
],
"Resource": "*"
}
]
}
EventBridge側の設定
やることは大きく分けて3つあります。
- ルールを追加
- Lambdaにリソースポリシーを追加し、1で作成したルールからLambdaを実行できるように
- 1のルールにターゲット(Lambda)を追加する
以下、順番にCLI
から操作していきます。
なお、Lambda
の名称は$LAMBDA_FUNC_NAME
に格納されているものとします。
①ルールの追加
まずはルールの作成です。
今回は毎朝9時(UTC
だと0時)に実行させるようにします。
指定方法はcron
です。
aws events put-rule \
--schedule-expression "cron(0 0 * * ? *)" \
--name costAlert
レスポンスとしてルールのARN
が返ります。
この値も$RULE_ARN
の値に格納されたものとします。
②リソースポリシーの追加
Lambda
のリソースポリシーを追加します。
リソースポリシーはざっくり説明すると、通常の管理ポリシーとは真逆で「リソースに対して◯◯がアクセス可能」といった感じで記載します。
ここではLambada
関数をEventBridge
のルールが実行できるようにポリシーを定義しています。
aws lambda add-permission \
--function-name $LAMBDA_FUNC_NAME \
--statement-id daily-cost-alert \
--action 'lambda:InvokeFunction' \
--principal events.amazonaws.com \
--source-arn $RULE_ARN
③ターゲットの設定
最後にターゲット設定です。
指定の時間になったら動作するルールが、「何を」するのかを決めます。
以下のようなtargets.json
を作成しましょう。
[
{
"Id": "1",
"Arn": "【Lambda関数のARN】"
}
]
このJSON
の値を先程作成したルールに紐付けます。
aws events put-targets \
--rule costAlert \
--targets file://targets.json
以下のようなレスポンスが返ります。
"FailedEntryCount": 0,
"FailedEntries": []
}
あとはcron
で指定した時刻になったらSlack
の対象チャンネルに通知が届いていれば成功です。
手堅くやるなら、Lambda
を作った段階で手動実行してみてSlack
に通知が届くかをチェックしておくと、権限の設定漏れなどがないのでいいかなと思います。
うまくいくと以下のような通知が届きます。
まとめ
今回はEventBridge
から定期的にLambda
関数を呼び出し、Lambda
関数からCostExplorer
で日次の課金額を取得してSlack
チャンネルに通知するまでの一連の処理を紹介しました。
システムに絡むAWS
のサービスが多くなってくるほど課金額を肌感で意識できなくなるので、目に見える形で通知する仕組みを用意しておくと事故を防げていいかなと思います。
今回の内容が役立ちましたら幸いです。
Discussion