0
0

GCPのBillingをDatadogに送るアプリをGoで作った

Last updated at Posted at 2024-08-16

対象者(読むと嬉しくなるかもしれない人)

  • GCPのBilling周りの通知をメール以外のプロダクトに飛ばしたい人
  • Datadogを使用している人

前書き

GCPのBillingは現状メールアドレス宛てにしか飛ばせず、予算とアラートの機能でPub/Sub トピックにデータを送ったりもできるんですが、これが予算を超えると1時間毎にPub/Subにデータが送られてくるんですよね。アプリ側で1度送信されたらそのフラグを管理するようにすればよいのですが、フラグを管理するためだけにデータソースを用意するのも大仰だな。と思ったことと折角BigQueryに出力しているのであればそれを使えばよいか。と思ったので作ってみました。
また、現状AWSのコストもDatadogに送っていることやDatadog側でMonitoringの種別を色々便利に使える(例えばForcastMonitoringで使用料金の上がり幅が急激だったり)といったことも理由にあります。

ちなみにメールからZapier等を使ってメールからslackに飛ばしてもいいんですが、自分の会社の環境だと連携が定期的に削除されてしまい、通知が来ないこともあったので作ってみました。同じ問題で困っていてかつDatadogを利用している方と割と対象者が限定的ですが送信先を変えれば何にでも使えると思うので参考になればと思います。

実装

コードはこちらにおいておきました。

内容は解説するほどもないくらいシンプルでcloud.google.com/go/bigqueryライブラリを使って当月の各サービスごとの料金を取得し、github.com/zorkian/go-datadog-apiライブラリを使ってDatadogのCustom Metricsに送っているだけです。
成功するとこのようにMetricsにデータが送信されます。
スクリーンショット 2024-08-16 23.03.27.png

また、fromdescription:datadog といった形でサービス名をセットすることでサービス毎の料金が確認できます。
スクリーンショット 2024-08-16 23.04.38.png

デプロイ

自分たちのサービスはKubernetesなので簡単にHelmにしておきました。
コードはこちらです。

helm template ./ | kubectl apply -f -

権限周り(ハマったところ)

前項のkubernetes manifestで記述しているとおり、serviceAccountName: gcp-billing でworkloadIdentity経由で権限を付与してあげます。
GKEを使っていればIAMを直接kubernetesのServiceAccountに割り当てられるので↓のようなSAは作らなくてもよいのですが、BigQueryの場合はgoogle_bigquery_dataset_iam_member を使ってdataset単位で権限付与してあげる必要があるので、今回はGoogleServiceAccountを払い出してそこに権限付与しています。

# gcp-billing
resource "google_service_account" "gcp_billing" {
  account_id   = "gcp-billing"
  display_name = "gcp-billing"
}

resource "google_bigquery_dataset_iam_member" "gcp_billing_bigquery_dataviewer" {
  for_each = toset([
    "roles/bigquery.dataViewer"
  ])
  dataset_id = "billing"
  role       = each.key
  member     = "serviceAccount:${google_service_account.gcp_billing.email}"
}

resource "google_project_iam_member" "gcp_billing_bigquery_jobuser" {
  project = var.project
  for_each = toset([
    "serviceAccount:${google_service_account.gcp_billing.email}"
  ])
  role   = "roles/bigquery.jobUser"
  member = each.key
}

resource "google_service_account_iam_member" "gcp_billing_workload_identity_user" {
  service_account_id = google_service_account.gcp_billing.name
  role               = "roles/iam.workloadIdentityUser"
  member             = "serviceAccount:${var.project}.svc.id.goog[sre/gcp-billing]"
}

監視設定

Datadogの監視設定も貼っておきます。datadogとしている部分をサービス毎に増やしていけば自動的に監視が追加できます。

variable "gcp_billing" {
  type = map(map(string))

  default = {
    datadog = {
      message_budget_1 = <<-EOT
        {{#is_alert}}
        @slack-Org-channelName
        {{/is_alert}}
        {{#is_warning}}
        @slack-Org-channelName
        {{/is_warning}}
        {{#is_recovery}}
        {{/is_recovery}}
      EOT
      budget           = "0"
    }
  }
}

resource "datadog_monitor" "gcp_budget_1" {
  for_each = var.gcp_billing

  name  = "{{#is_alert}}[Budget] ${each.key}のコストが$ {{eval \"int(threshold)\"}}(100%)を超過しました。現在の利用金額は$ {{eval \"int(value)\"}} です。{{/is_alert}}{{#is_warning}}[Budget] ${each.key}のコストが$ {{eval \"int(warn_threshold)\"}}(70%)を超過しました。現在の利用金額は$ {{eval \"int(value)\"}} です。{{/is_warning}}"
  type  = "metric alert"
  query = "max(last_1d):max:gcp.billing.total{description:${each.key}} >= ${each.value.budget * 1.0}."

  monitor_thresholds {
    critical = each.value.budget * 1.0
    warning  = each.value.budget * 0.7
  }

  require_full_window = false
  notify_no_data      = false
  timeout_h           = 0
  evaluation_delay    = 660

  include_tags = false
  message      = each.value.message_budget_1
}

gcp_budget_1 と記述しているのは、gcp_budget_nには以下のような形でアラートを増やしたいからです。

  monitor_thresholds {
    critical = each.value.budget * 0.9
    warning  = each.value.budget * 0.5
  }

おまけ

自社では最近bullseyeを使うことが多く、自分も最初はbullseyeを使っていたのですが、最後にDockerfile周りのベストプラクティスを見ながら細かい調整していたところ、作業中にやたらとimageのpushとpullに時間が掛かっていたことを思い出しました。そういえばサイズってどのくらいなんだっけ?と思って確認してみると799MBとかなりでかい。
マルチステージビルドもしていなかったので、まぁこんなもんかなと思っていたのですが、alpineと比べると結構な差がありました。

golang                                            1.23.0-bullseye     87f0914bbaf7   2 days ago     740MB
golang                                            1.23.0-alpine3.20   ec9c7c392a17   2 days ago     244MB

マルチステージビルドにしてalpineでbuildしてみると21倍という結構な差になりましたね。

docker-registry.uzabase.com/sre/gcp_billing       v1.1.3            fd9e33de801b   2 hours ago    37.8MB
docker-registry.uzabase.com/sre/gcp_billing       v1.1.2            4584d536e6a5   5 hours ago    799MB
docker-registry.uzabase.com/sre/gcp_billing       v1.1.1            4ccedd3b10fb   11 hours ago   769MB

imageが増えてくるとディスクも結構食ってしまうのでセキュリティを優先しつつ軽量化も考えていきたいですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0