🤖

【Terraform】知らないうちにGoogle Cloudの課金設定を外していた話 〜犯人は自分でした〜

に公開

はじめに

初めてTerraformを使っていたのですが、意図しない変更をapplyしてしまいました。

この記事では、私が実際に体験した「新しく作ったGoogle Cloudプロジェクトの課金設定が、いつの間にか外れていた」というインシデントについて、その原因と対策を共有します。

この失敗談が、Terraformを使い始めた方や、同様のミスを防ぎたいと思っている方の助けになれば幸いです。

事件発生:ある日、課金設定が消えた

ある日、私は新しいGoogle CloudプロジェクトをTerraformで作成しました。

main.tf
# project-aというプロジェクトを作成
resource "google_project" "project-a" {
  name       = "project-a"
  project_id = "project-a"
  org_id     = "xxxxxxxxxx"
}

そして、組織の管理者であるCTOに依頼し、このプロジェクトに課金アカウントを紐づけてもらいました。コンソール上で課金が有効になっていることを確認し、一安心。

しかし、次の日。
そのプロジェクトにリソースを構築しようとterraform applyを実行すると、エラーが発生しました。原因を調べてみると、プロジェクトの課金設定が無効になっていました。
「え、どうして?CTOがせっかく設定してくれたのに...」

もちろん、私が手動で課金設定を外した覚えは一切ありません。

操作ログで犯人捜し

何が起きたのか全く分からなかったので、まずは事実確認のために操作ログを確認することにしました。ログを辿っていくと、

「昨日、私のアカウントが課金設定を無効化している」

ログには、私が課金設定を外したという記録が残っていました。意図しない操作だったとはいえ、犯人は自分でした。

原因:Terraformの思想とbilling_account
なぜ、私は無意識に課金設定を外してしまったのでしょうか?
原因は、Terraformのコードにありました。

main.tf
# project-aというプロジェクトを作成
resource "google_project" "project-a" {
  name       = "project-a"
  project_id = "project-a"
  org_id     = "xxxxxxxxxx"
  # billing_account が指定されていない!
}

# その後、このプロジェクトに何らかのリソースを追加して apply した
resource "google_storage_bucket" "example" {
  project = google_project.project-a.project_id
  name    = "my-unique-bucket-for-project-a"
  location = "US"
}

Terraformは、コードに書かれた状態が「あるべき姿(正)」だと考えます。

私のコードではgoogle_projectリソースにbilling_accountが指定されていませんでした。
そのため、Terraformは「このプロジェクトには課金アカウントが紐付いていない状態が正しい」と判断します。

その結果、CTOが手動で設定してくれた課金アカウントの紐付けを、Terraformが「差分」として検知し削除してしまったのです。

terraform planの結果を見逃していた
私がterraform planの結果を注意深く確認していれば、この意図しない変更は、防ぐことができました。

# google_project.project-a will be updated in-place
~ resource "google_project" "project-a" {
    id              = "projects/project-a"
    name            = "project-a"
  ~ billing_account = "XXXXXX-XXXXXX-XXXXXX" -> null # この行!
    # ... other attributes
  }

気づきと学び

この経験から、以下のことを学びました。

1. Terraformはコードで定義した状態があるべき姿
手動でのコンソール操作は、次のapplyでTerraformによって上書き(あるいは削除)される可能性があることを常に意識する。

2. terraform planはちゃんと確認する
特に-> nullとなっている箇所や、変更(~)、削除(-)の差分は、意図した変更であるかをちゃんと確認する。

3. まずは自分を疑う
「自分はそんな操作はしていない」と思いましたが、操作ログを確認したことは良かったです。
操作ログを確認していなければ、原因に気づかず同じ過ちを繰り返すところでした。

再発防止策

同じ過ちを繰り返さないために、いくつかの対策を考えました。

対策1:lifecycleで特定の変更を無視する
手軽にできる対策として、lifecycleブロックのignore_changesがあるようです。
これにより、Terraformは指定された属性の変更を無視するようになります。

main.tf
resource "google_project" "project-a" {
  name       = "project-a"
  project_id = "project-a"
  org_id     = "xxxxxxxxxx"

  lifecycle {
    ignore_changes = [
      billing_account,
    ]
  }
}

メリット:

  • 簡単に設定でき、意図しない課金設定の削除をすぐに防げる。
  • 課金設定の権限を別の人が持っている場合に有効。

デメリット:

  • 課金設定がTerraformの管理対象外となり、コードと実際の状態が乖離する可能性がある。

対策2:コードでbilling_accountを明示的に指定する
課金アカウントIDをコードに直接書くことになるため、Githubなどで管理している場合は特に、IDが直接見えて良いかの確認が必要そうです。
.tfvarsファイルや環境変数を使ってIDを外部から注入する方法については深堀りして調べる必要がありそうです。

メリット:

  • コードと実際の状態が乖離しない

デメリット:

  • うっかりミスでbilling_accountを削除してしまう可能性がなくはない

対策3:プロジェクト作成権限を持つ人とリソース作成権限を持つ人を分ける
これが今回の問題に対する策として、根本的には一番良さそうです。
ただ、手間は増えるので気が向いた時に再検討します。

私はひとまず対策1を取ることにしました。
ベストプラクティス的なもの知ってる方いらっしゃればコメントください🙏

まとめ

Terraformが宣言的にインフラストラクチャを定義するというのは、作業前から知識としては入れてたつもりでしたが、
今回の失敗によって身に染みて理解できました。

NCDC テックブログ

Discussion