BigQuery 破産で泣かないために。。GCP 利用料金を bot で Slack に通知してみた

2020.03.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、みかみです。

業務などで大量データを扱う必要がある場合、やはり特に BigQuery の利用料金が心配。。

また、GCP の各サービスの利用コストについて、まだあまり把握していないので、知らないうちに利用料金がすごいことになってたら大変。。。

やりたいこと

  • GCP 管理コンソールアクセスなしで、定期的に GCP の利用料金を知りたい
  • BigQuery や 他の GCP サービスの利用費を Slack で確認したい

予算アラートを設定して Slack に通知する

GCP の各種サービスに関する課金情報は、Cloud Billing で管理されているようです。

ドキュメントを確認していると、Cloud Billing では予算アラートの設定機能があり、Cloud Functions で Slack にアラート通知するプログラム例の記載もありました。

試しに、BigQuery の予算アラートを設定して Slack に通知してみたいと思います。

予算アラートを作成

GCP 管理コンソールナビゲーションメニューの「お支払い」から、「予算とアラート」を選択します。

画面上部の「予算を作成」から、任意の「名前」を入力し、「プロジェクト」と「プロダクト」を選択して「次へ」。

「Budget type」で、「指定額」か「先月の利用額」のどちらかを選択し、「指定額」の場合は「Target amount」に予算額を入力して「次へ」。

「アラートしきい値ルールの設定」画面で、アラート通知条件を設定し、「Pub/Sub トピックをこの予算に接続する」にチェックすると、プログラムからアラート通知するための Pub/Sub トピックを設定することができます。プロジェクトと通知用の Pub/Sub トピックを指定して「終了」。

Slack のボットアプリを作成&チャンネル ID を取得

以下を参考に、Slack のボットアプリを作成します。

アプリ名と通知先のワークスペースを指定してアプリを作成したら、「OAuth & Permissions」の「Scopes」で「Bot Token Scopes」に「chat:write」を追加します。

「App Home」で「App Display Name」を設定してワークスペースにインストールします。

インストール完了後に表示されるアクセストークンは、プログラムから指定する必要があるのでメモっておきます。

メッセージを通知するチャンネルの ID も必要なので、確認しておきます。

作成したボットを通知先の Slack チャンネルに追加しました。

Cloud Functions の関数を作成

Cloud Functions のページから「関数を作成」します。

任意の「名前」を入力し、「トリガー」プルダウンから、「Cloud Pub/Sub」と先ほど予算アラートで指定した Pub/Sub トピックを選択します。

続いて「インラインエディタ」で Python 3.7 を選択し、ソースコードを入力します。

Slack に通知されたメッセージを確認してみると、可読性がいまいちだったので、

メッセージ部分を修正して、最終的に以下のソースコードをデプロイしました。( Slack のアクセストークンとチャンネル ID は伏字に変更しています。)

from slackclient import SlackClient
import json
import base64

def notify_slack(data, context):
    BOT_ACCESS_TOKEN = 'xxxx-XXXX-XXXX-XXXX'
    CHANNEL_ID = 'XXXXXXXX'

    slack_client = SlackClient(BOT_ACCESS_TOKEN)

    pubsub_message = data

    notification_data = base64.b64decode(data['data']).decode('utf-8')
    notification_data = json.loads(notification_data)
    budget_notification_text = '予算額:{0} ({1})\n利用料金:{2} ({1})\n利用開始日時:{3}'.format(notification_data.get('budgetAmount'), notification_data.get('currencyCode'), notification_data.get('costAmount'), notification_data.get('costIntervalStart'))

    slack_client.api_call(
      'chat.postMessage',
      channel=CHANNEL_ID,
      text=budget_notification_text)

まだ無料利用枠内での使用なので料金は \0 ですが、Slack への通知が確認できました。

GCP 利用料金を BigQuery にエクスポートしてから Slack に通知する

Cloud Billing では、管理コンソールから設定するだけで GCP 利用料金データを BigQuery にエクスポートできるとのこと。

このエクスポートしたテーブルを参照し、Cloud Scheduler -> Pub/Sub -> Cloud Functions を使用して、GCP 利用料金を定期的に Slack に通知してみます。

Billing データを BigQuery にエクスポートする

まずは、BigQuery にデータエクスポート用のデータセットを作成します。

続いて BigQuery への Billing データエクスポートを有効に設定します。

GCP 管理コンソールナビゲーションメニューから、「お支払い」を選択した後、「課金データのエクスポート」を選択して、「BIGQUERY EXPORT」タブ画面で「設定を編集」ボタンをクリック。

編集画面で「プロジェクト」と「課金データのエクスポート先データセット」プルダウンで、先ほど作成したエクスポート用データセットを指定し、「保存」ボタンをクリック。

しばらく放置した後で見てみると、BigQuery に課金データがエクスポートされていることが確認できました。

メッセージ通知用の Slack ボットアプリを作成

予算アラート通知時と同様に、Slack 通知用のアプリを作成し、通知先の Slack ワークスペースにインストールしました。

通知に必要なアクセストークンとチャンネル ID を控えて、アプリを通知先チャンネルにインストールしておきます。

利用料金通知用の Cloud Functions 関数を作成

GCP 管理コンソールから、Cloud Pub/Sub をトリガーとする Cloud Functions の関数を新規作成します。

BigQuery にエクスポートされた Billing データテーブルから、自分のプロジェクトの月ごと&サービスごとの課金情報を取得して、Slack に通知します。

ランタイムは Python 3.7 を選択し、以下のソースコードをデプロイしました。( Slack のアクセストークンとチャンネル ID は伏字に変更しています。)

from google.cloud import bigquery
from slackclient import SlackClient

query = """
SELECT
    invoice.month,
    service.description,
      SUM(cost)
        + SUM(IFNULL((SELECT SUM(c.amount)
                      FROM UNNEST(credits) c), 0))
        AS total,
      (SUM(CAST(cost * 1000000 AS int64))
        + SUM(IFNULL((SELECT SUM(CAST(c.amount * 1000000 as int64))
                      FROM UNNEST(credits) c), 0))) / 1000000
        AS total_exact
FROM
    `cm-da-mikami-yuki-258308.billing.gcp_billing_export_v1_01A74C_7C2053_85DC25`
WHERE
    project.name = 'cm-da-mikami-yuki'
GROUP BY 1, 2
ORDER BY 1, 3 DESC
;
    """

def notify_slack(data, context):
    # GCP 利用料金取得
    client = bigquery.Client()
    query_job = client.query(query)
    rows = query_job.result()
    info_text = '\n'.join(['・{}:{} -> {}(tax:{})'.format(row.month, row.description, row.total, row.total_exact) for row in rows])

    # 利用額を Slack に通知
    BOT_ACCESS_TOKEN = 'xxxx-XXXX-XXXX-XXXX'
    CHANNEL_ID = 'XXXXXXXX'
    slack_client = SlackClient(BOT_ACCESS_TOKEN)
    slack_client.api_call(
      'chat.postMessage',
      channel=CHANNEL_ID,
      text=info_text)

Cloud Scheduler で Cloud Functions を定期実行

Cloud Scheduler を使えば、サーバーを立てることなく、プログラムをスケジュール実行することができます。

以下のドキュメントを参考に Cloud Scheduler のジョブを作成して、Cloud Functions のトリガーに指定した Cloud Pub/Sub トピック を関連付けます。

GCP コンソール Cloud Scheduler 画面から「ジョブを作成」で、ロケーション選択後にジョブの「説明」と「頻度」を入力し、「タイムゾーン」に JST、「ターゲット」で「Pub/Sub」を選択したら、「トピック」に Cloud Functions のトリガーとなる Pub/Sub トピックを入力します。「ペイロード」には任意の文字列を入力しました。

Cloud Scheduler 画面の「今すぐ実行」ボタンで、テスト実行してみます。

Slack にメッセージが通知されることが確認できました。

さらに、実行時間に指定した 22:00 まで待ってみます。

スケジュール実行でも、期待通り、GCP の利用料金が Slack に通知されることが確認できました。

つまずいたところ

予算アラート通知用 Cloud Functions のコード

はじめ、Cloud Functions のコードには、下記ドキュメントに記載されていたサンプルコードをそのまま入力しました。

テスト実行してみましたが、うまく動かず。。

Error Reporting とログを確認しながら、以下を修正しました。

  • import する slack クライアントライブラリ名とインスタンス作成処理
  • json と base64 ライブラリの import を追加
  • アクセストークンとチャンネル ID の変数宣言をメソッド内に移動

以下のサイトのコードも参考にさせていただきました。

予算アラートの通知間隔

予算アラートからアラート通知条件の閾値を設定していましたが、Pub/Sub トピックを関連付けた場合、閾値関係なく30分間隔で Pub/Sub にメッセージがパブリッシュされるようです。

ドキュメントには特に通知間隔の記載は見当たらなかったので、私の設定内容や手順の問題かもしれませんが、予算額変えたり、最終的に閾値のルールを全て削除もしてみたのですが、やはり30分間隔で通知が発生しました。

Cloud Functions のコード内で料金判定などの通知制御処理をはさめば、問題なく動作できそうです。

利用料金エクスポート完了時間

BigQuery への Cloud Billing エクスポートを有効にした直後に、使用量テーブルが自動的に作成されます。

ドキュメントには上記記載がありましたが、エクスポート設定直後、指定したデータセットにはなかなかテーブルが作成されません。。 何か設定間違ったかと不安になりつつ。。

BigQuery への Cloud Billing エクスポートを有効にすると、Google が所有および管理するサービス アカウントが、指定したデータセットのオーナーとして追加されます。サービス アカウントは次のようになります。

billing-export-bigquery@system.gserviceaccount.com

との記載を見つけ、GCP 管理コンソールホーム「アクティビティ」画面で、該当データセットに billing-export-bigquery@system.gserviceaccount.com のロールが追加されていることを確認できたので、しばらく待ってみました。

最終的には無事エクスポートされました。エクスポート時間によると、設定してから 2h 後くらいにエクスポートされたようです。

エクスポート可能なデータの範囲や頻度に関しては、以下のドキュメントに記載がありました。

まとめ(所感)

BigQuery にエクスポートされる Billing データでは、エクスポート設定前の情報は参照できないそうなので、GCP 利用開始時に合わせて設定しておくのがよさそうです。

また、BigQuery にエクスポートしたデータを Google データポータルや他の BI ツールなどで可視化することで、よりコスト管理が楽になりそうです。

Cloud Functions など他のサービスと組み合わせれば、いろいろなツールと連携したコストの自動管理が可能なので、自分の利用環境に合った管理方法を模索してみるのも面白そうです。

参考