AWSやGCPの料金、たまに気になりますよね。
そういえば今月どれくらい料金食ってるんだ?と思ったとき、もちろん各サービスのコンソールで課金管理のページを開けば確認できますね。
が、日常的にはお知らせが届くほうが簡単ですむよね、ということでslackに通知をさせてみました。
AWSは簡単だったのですがGCPはちょっと手間だったのでまとめておきたいと思います。
まあエンジニアとしては課金の具合を気にせず開発に集中できれば一番いいのですが、現実には会社の予算とかいろいろありますし私は課金周りの把握をする役回りもあるので、これはやっておきたいところです。
1. Pub/Subトピック作成
まずは適当なプロジェクトを用意し、Pub/Sub でトピックを作成します。
2.GCPで予算の作成
次は予算の作成です。GCPのメニューから「お支払い」→「予算とアラート」と進み、予算の作成をクリック。
予算と予算アラートの設定 | Cloud Billing | Google Cloud
- 予算は設定された額に対して実際の課金額が達したら通知されるようになっています。設定額は、「自由に設定」と「先月の額」から選べるんですね。
ちなみに5000兆円は設定できませんでした🥺
- 最後に設定額の何%になった時点でアラート出すかのしきい値を設定します。実値or予測値で、パーセンテージは自由に設定できます。
あと、先ほど作成したPub/Subトピックを忘れずに接続しておきましょう。
3. Slack App作成
Slackに通知するために、Appを作成してトークンを発行します。
コスト管理の自動レスポンスの例 | Cloud Billing | Google Cloud
https://api.slack.com/apps で Create New App
をクリックし、適当なAPP名と導入したいワークスペース名を入力してアプリを作成します。
メニューの OAuth & Permissions
を開き、 Bot Token Scopes
に chat:write
スコープを追加します。
ページ上部の Install App to Workspace
をクリックし、ワークスペースにAppをインストールします。
インストールして戻ってくれば、アクセストークンが得られます。
4. Function作成
さてCloud Functionsを作ります。ここがメインディッシュです。
GCPの料金アラートは、常に1時間に3回程度送信される仕様なようです。なのでPub/Subで受け取った通知をすべてslackに出しているといらない通知ばかり頻繁に来てしまいます😇
そこでFirestoreをちょこっと使って、しきい値突破時の通知だけをslackに投げるようにFunctionを作ってみました。
まず、Pub/Subに送られてくるデータは一例として以下のようになってます。
(詳しくはドキュメントを参照してください)
プログラムによる予算アラート通知を管理する | Cloud Billing | Google Cloud
{
"budgetDisplayName": "sitateru",
"alertThresholdExceeded": 0.8,
"costAmount": 43210,
"costIntervalStart": "2020-08-01T07:00:00Z",
"budgetAmount": 50000,
"budgetAmountType": "SPECIFIED_AMOUNT",
"currencyCode": "JPY"
}
そこで、アルゴリズムとしては、
- Pub/Subから来たデータを確認
- しきい値に達したときの通知であれば
alertThresholdExceeded
かforecastThresholdExceeded
というキーが含まれるので、キーの存在チェック budgetDisplayName
,alertThresholdExceeded
,costIntervalStart
,budgetAmountType
の値の組み合わせを見て、- Firestore上にその値のセットのデータがなければslackで通知+Firestoreに値セットを保存
- Firestore上にその値のセットのデータがあれば何もしない
というような設計にすればだいじょうぶだあ、と思いつつそこから少し調整してこのようなコードになりました。
const slack = require('slack')
const Firestore = require('@google-cloud/firestore')
// slackbotのアクセストークン
const BOT_ACCESS_TOKEN = process.env.BOT_ACCESS_TOKEN
// 投稿先slackチャンネル名
const CHANNEL = process.env.SLACK_CHANNEL
// GCPの予算ページに飛べるようにURLを入れる
const BUDGET_URL = process.env.BUDGET_URL
const collection = new Firestore({
projectId: process.env.PROJECT_ID,
keyFilename: process.env.KEYFILE
}).collection('billing_alert_slack')
exports.billingAlertSlack = async (pubsubEvent, context) => {
const pubsubData = JSON.parse(Buffer.from(pubsubEvent.data, 'base64').toString())
const postSlack = async (fields) => {
const messageFields = Object.entries(fields).map(datum => {
return {
"title": datum[0],
"value": datum[1],
"short": true
}
})
await slack.chat.postMessage({
token: BOT_ACCESS_TOKEN,
channel: CHANNEL,
text: '',
attachments: [{
"color": "#1a73e8",
"title": "GCP Billing Alert",
"title_link": BUDGET_URL,
"fields": messageFields
}]
})
return 'Slack notification sent successfully'
}
if(pubsubData.hasOwnProperty("alertThresholdExceeded") || pubsubData.hasOwnProperty("forecastThresholdExceeded")) {
const ThresholdExceeded = pubsubData.hasOwnProperty("alertThresholdExceeded") ? pubsubData.alertThresholdExceeded : pubsubData.forecastThresholdExceeded
const querySnapshot = await collection.where('budgetDisplayName', '==', pubsubData.budgetDisplayName)
.where('ThresholdExceeded', '==', ThresholdExceeded)
.where('budgetAmountType', '==', pubsubData.budgetAmountType)
.get()
if (querySnapshot.docs.length) {
const data = querySnapshot.docs[0].data()
if (pubsubData.costIntervalStart != data.costIntervalStart) {
// update document
querySnapshot.docs[0].ref.update({ costIntervalStart: pubsubData.costIntervalStart })
// post slack
return await postSlack(pubsubData)
}
} else {
// add document
collection.add({
budgetDisplayName: pubsubData.budgetDisplayName,
ThresholdExceeded: ThresholdExceeded,
budgetAmountType: pubsubData.budgetAmountType,
costIntervalStart: pubsubData.costIntervalStart
})
// post slack
return await postSlack(pubsubData)
}
} else {
return 'Slack notification was not sent'
}
}
上のコードで出てきた環境変数ですが、
-
BOT_ACCESS_TOKEN
手順3で作ったSlack App用アクセストークン SLACK_CHANNEL
通知先のSlackチャンネル名BUDGET_URL
Slackの通知からすぐ飛べるように、GCPの予算ページのURL