TerraformでGoogle Cloud APIの有効化漏れを事前検知する方法
はじめに
Google CloudでTerraformを使っていると、こんなエラーに遭遇したことはありませんか?
Error: Error creating Instance: googleapi: Error 403:
Cloud SQL Admin API has not been used in project XXXXX before or it is disabled.
これは、必要なAPIがプロジェクトで有効化されていないために発生するエラーです。特に新しいプロジェクトで作業する際や、新しいサービスを使い始めるときによく遭遇します。
terraform applyを実行して初めてエラーに気づき、APIを有効化してから再実行...という手順を踏むのは非効率です。できればterraform planの段階で気づきたいですよね。
この記事では、data.google_project_serviceとpreconditionを組み合わせて、plan時にAPI有効化漏れを検知する方法をご紹介します。
従来の問題点
Terraformで新しいGoogle Cloudリソースを作成する際、該当サービスのAPIが有効化されていないと、terraform apply時にエラーで失敗します。
# Cloud SQLインスタンスを作成しようとする
resource "google_sql_database_instance" "postgres" {
name = "my-postgres-instance"
database_version = "POSTGRES_15"
region = "asia-northeast1"
# ...
}
上記のコードでterraform applyすると、Cloud SQL Admin APIが有効化されていない場合にエラーになります。
問題点:
-
terraform planは成功するが、terraform applyで初めてエラーに気づく - エラーメッセージからどのAPIを有効化すればいいのか分かりにくい場合がある
- API有効化後、再度
terraform applyが必要
解決策:preconditionによる事前チェック
Terraform 1.2以降では、lifecycleブロックのpreconditionを使って、リソース作成前に条件をチェックできます。
これとdata.google_project_serviceを組み合わせることで、plan時にAPIの有効化状態を確認できます。
なぜModuleにpreconditionを実装するのか
この記事では、Module内にpreconditionを実装するパターンを紹介しています。その理由は以下の通りです。
1. 一度有効化したら同じプロジェクト内では再確認不要
同じプロジェクト内で開発を続けている限り、一度APIを有効化すれば、そのAPIが無効化されることはほとんどありません。そのため、同じプロジェクトでの開発では、このチェックは初回にしか役立ちません。
2. Moduleの再利用時に威力を発揮
しかし、Moduleを別のプロジェクトで流用する際に、このpreconditionが真価を発揮します。
例えば:
- 新しいプロジェクトでCloud SQLモジュールを使おうとした際
- 既存のモジュールを他のチームに共有した際
- 異なる環境(dev/staging/production)を構築する際
このような場合、モジュールに組み込まれたpreconditionが、必要なAPIの有効化漏れに気づかせてくれます。
# 新しいプロジェクトで既存のCloud SQLモジュールを使用
module "cloudsql" {
source = "git::https://github.com/your-org/terraform-modules.git//gcp-cloudsql-postgres"
gcp_project_id = "new-project-id" # 新しいプロジェクト
# ...
}
# terraform plan時にAPIが有効化されていないことに気づける!
3. Moduleの移植性と自己完結性を高める
Moduleにpreconditionを実装することで:
- モジュール利用者が、必要なAPIを明示的に知ることができる
- モジュール単体で前提条件をチェックできるため、ドキュメントを読まなくても使える
- エラーメッセージで対処方法も提示されるため、初めて使う人でも迷わない
実装例:Cloud SQLモジュール
実際のCloud SQLモジュールの実装例を見てみましょう。
# 必要なAPIが有効になっているかチェック
data "google_project_service" "sqladmin" {
project = var.gcp_project_id
service = "sqladmin.googleapis.com"
}
data "google_project_service" "servicenetworking" {
project = var.gcp_project_id
service = "servicenetworking.googleapis.com"
}
resource "google_sql_database_instance" "postgres" {
name = "my-postgres-instance"
database_version = "POSTGRES_15"
region = "asia-northeast1"
project = var.gcp_project_id
settings {
tier = "db-f1-micro"
# ...
}
# 必要なAPIが有効かチェック
lifecycle {
precondition {
condition = data.google_project_service.sqladmin.id != null
error_message = <<-EOT
【必須API未有効】Cloud SQL Admin API (sqladmin.googleapis.com) がプロジェクト ${var.gcp_project_id} で有効化されていません。
このモジュールでCloud SQLインスタンスを作成・管理するには、Cloud SQL Admin APIが必要です。
【対処方法】Terraformで以下のリソースを追加してください:
resource "google_project_service" "sqladmin" {
project = "${var.gcp_project_id}"
service = "sqladmin.googleapis.com"
}
または、gcloudコマンドで有効化:
gcloud services enable sqladmin.googleapis.com --project=${var.gcp_project_id}
EOT
}
precondition {
condition = data.google_project_service.servicenetworking.id != null
error_message = <<-EOT
【必須API未有効】Service Networking API (servicenetworking.googleapis.com) がプロジェクト ${var.gcp_project_id} で有効化されていません。
このモジュールでプライベートVPC接続を使用したCloud SQLインスタンスを作成するには、Service Networking APIが必要です。
【対処方法】Terraformで以下のリソースを追加してください:
resource "google_project_service" "servicenetworking" {
project = "${var.gcp_project_id}"
service = "servicenetworking.googleapis.com"
}
または、gcloudコマンドで有効化:
gcloud services enable servicenetworking.googleapis.com --project=${var.gcp_project_id}
EOT
}
}
}
仕組みの解説
-
data.google_project_serviceでAPIの状態を取得- 指定したAPIがプロジェクトで有効かどうかを確認
- 有効な場合は情報を取得、無効な場合は
idがnullになる
-
preconditionで事前条件をチェック-
conditionでAPIが有効か(id != null)をチェック - 条件を満たさない場合、詳細なエラーメッセージを表示
terraform planの段階でエラーが表示される
-
-
分かりやすいエラーメッセージ
- どのAPIが必要か明示
- 対処方法を具体的に記載(Terraformでの有効化、gcloudコマンドでの有効化)
plan時のエラー表示例
APIが有効化されていない場合、terraform plan時に以下のようなエラーが表示されます:
Error: Resource precondition failed
on main.tf line 104, in resource "google_sql_database_instance" "postgres":
104: condition = data.google_project_service.sqladmin.id != null
【必須API未有効】Cloud SQL Admin API (sqladmin.googleapis.com) がプロジェクト my-project で有効化されていません。
このモジュールでCloud SQLインスタンスを作成・管理するには、Cloud SQL Admin APIが必要です。
【対処方法】Terraformで以下のリソースを追加してください:
resource "google_project_service" "sqladmin" {
project = "my-project"
service = "sqladmin.googleapis.com"
}
または、gcloudコマンドで有効化:
gcloud services enable sqladmin.googleapis.com --project=my-project
落とし穴:API有効化リソースとの同時適用
ここまで読んで、「じゃあTerraformでAPI有効化リソースを一緒に定義すればいいじゃん!」と思った方もいるかもしれません。
# API有効化
resource "google_project_service" "sqladmin" {
project = var.gcp_project_id
service = "sqladmin.googleapis.com"
}
# Cloud SQLインスタンス(上記と同じファイル内)
resource "google_sql_database_instance" "postgres" {
name = "my-postgres-instance"
database_version = "POSTGRES_15"
# ...
lifecycle {
precondition {
condition = data.google_project_service.sqladmin.id != null
error_message = "API未有効..."
}
}
}
しかし、これだけでは最初の適用時に失敗します。
なぜなら:
-
terraform plan時点では、まだAPIは有効化されていない -
data.google_project_serviceは現在の状態を確認する -
preconditionが失敗する
APIはリソースとして適用されるまで有効化されません。つまり、terraform applyを実行してAPI有効化リソースが適用されるまでは、data.google_project_serviceはAPIが無効だと判定します。
対策方法
この問題を回避するには、いくつかの方法があります。
対策1:モジュール呼び出しを一時的にコメントアウト
最初の適用時のみ、モジュール呼び出しをコメントアウトしてAPI有効化を先に適用します。
# API有効化を先に適用
resource "google_project_service" "sqladmin" {
project = var.gcp_project_id
service = "sqladmin.googleapis.com"
}
# 最初の適用時はコメントアウト
# module "cloudsql" {
# source = "./modules/gcp-cloudsql-postgres"
# # ...
# }
手順:
- モジュールをコメントアウトした状態で
terraform applyしてAPIを有効化 - コメントを外して再度
terraform apply
対策2:--targetオプションを使う
特定のリソースのみを先に適用します。
# API有効化を先に適用
terraform apply -target=google_project_service.sqladmin
# その後、全体を適用
terraform apply
対策3:gcloudコマンドやWebコンソールで事前に有効化
TerraformでAPI有効化を宣言する前に、手動で有効化しておく方法です。
gcloud services enable sqladmin.googleapis.com --project=my-project
gcloud services enable servicenetworking.googleapis.com --project=my-project
または、Google CloudのWebコンソールから有効化します。
既に有効化されているAPIをTerraformで再度有効化してもエラーにはなりません。そのため、手動で有効化した後にTerraformで管理し始めても問題ありません。
# 手動で有効化
gcloud services enable sqladmin.googleapis.com --project=my-project
# Terraformで管理開始(エラーにならない)
terraform apply
なぜモジュール内にAPI有効化リソースを含めないのか
「モジュール内にAPI有効化リソースも含めればいいのでは?」という疑問を持つ方もいるかもしれません。
# モジュール内でAPI有効化も行う例
module "cloudsql_module" {
# モジュール内部
resource "google_project_service" "sqladmin" {
project = var.gcp_project_id
service = "sqladmin.googleapis.com"
}
resource "google_sql_database_instance" "postgres" {
# ...
}
}
しかし、これは推奨されません。理由は以下の通りです。
問題点:複数モジュールでの競合
複数のモジュールで同じAPIを使う場合、各モジュールが同じAPI有効化リソースを持つことになります。
# モジュールA(Cloud SQL)
module "cloudsql" {
# 内部で servicenetworking.googleapis.com を有効化
}
# モジュールB(別のネットワークリソース)
module "network_resource" {
# 内部で servicenetworking.googleapis.com を有効化
}
この状態で、片方のモジュールを削除すると、API有効化も削除されてしまいます。
# モジュールAを削除
# module "cloudsql" {
# ...
# }
# モジュールBだけ残す
module "network_resource" {
# servicenetworking.googleapis.com が無効化されてしまう可能性
}
結果:
- モジュールAの削除時に
google_project_serviceも削除される - モジュールBが依存しているAPIが無効化され、モジュールBのリソースにも影響が出る
- 意図しないリソース削除や障害につながる
推奨パターン
API有効化は、プロジェクトレベルで一元管理するのがベストプラクティスです。
# ルートモジュールでAPI有効化を一元管理
resource "google_project_service" "sqladmin" {
project = var.gcp_project_id
service = "sqladmin.googleapis.com"
}
resource "google_project_service" "servicenetworking" {
project = var.gcp_project_id
service = "servicenetworking.googleapis.com"
}
# モジュールはAPI有効化を前提に動作
module "cloudsql" {
source = "./modules/gcp-cloudsql-postgres"
# preconditionでAPI有効化をチェック
}
module "network_resource" {
source = "./modules/gcp-network-resource"
# preconditionでAPI有効化をチェック
}
この方法なら:
- API有効化の状態が一目で分かる
- 複数モジュール間での競合が起きない
- モジュールの削除がAPI有効化に影響しない
まとめ
Google CloudでTerraformを使う際のAPI有効化漏れ対策として、以下の方法を紹介しました。
-
data.google_project_serviceとpreconditionで事前チェック- plan時にAPI有効化状態を確認できる
- 分かりやすいエラーメッセージで対処方法を提示
-
初回適用時の落とし穴に注意
- API有効化リソースとモジュールを同時に適用すると失敗する
- 対策:コメントアウト、
--targetオプション、事前の手動有効化
-
API有効化はプロジェクトレベルで一元管理
- モジュール内にAPI有効化を含めない
- 複数モジュール間での競合を避ける
この方法を使えば、API有効化漏れによるterraform applyの失敗を事前に防げます。特に、チームで開発している場合や、複数の環境を管理している場合に効果的です。
参考
- Custom Conditions - Terraform公式ドキュメント - preconditionとpostconditionの詳細
- Validate modules with custom conditions - Terraform公式チュートリアル
- Terraform Lifecycle Meta-Arguments
- google_project_service | Resources | hashicorp/google | Terraform Registry
- google_project_service | Data Sources | hashicorp/google | Terraform Registry
Discussion