🔄

Terraformリソース移行で学んだ「最初の設計」の重要性 の気づき(個人メモ)

に公開

学習目的でGCP + Terraformを使ったインフラ構築を進めていく中で、「最初の設計が重要」ということを身をもって体感する出来事があったので記録として記事にしました。

Workload Identity Federationっていうリソースをdashboardフォルダに作ってたんですが、apiも作ることになって「あ、これ共通で使うやつだった」と気づいた時の移行をしました。
私がTerraformに慣れていないこともあり、既存のリソースが壊れないようにしながら対応をすることはとても怖かったです。

何が起きたか

最初はこのような形で進めていました。

apps/terraform/gcp/environments/dev/
├── shared/          # 共通リソース用(空っぽ)
└── dashboard/       # フロントエンド用
    ├── main.tf      # Workload Identity Federationも含む
    └── outputs.tf

とりあえず動かしたくて、dashboardフォルダに全部まとめていました。

そして共通化が必要になった

api用の環境も作ろうとしたときに、予想していた通りWorkload Identity Federationの共通化が必要になりました。

「GitHub Actionsの認証設定、これdashboardとapi両方で必要だよね」
「両方に同じリソース作ったら競合するから、やっぱりsharedに移そう」

最初から分かっていたことでしたが、実際にやってみると想像以上に怖い作業でした。

こうしたかった

apps/terraform/gcp/environments/dev/
├── shared/
│   └── workload-identity.tf  # 共通の認証設定
├── dashboard/                # dashboard専用リソース
└── api/                      # api専用リソース(将来作成)

移行作業

Step 1: sharedに新しく作る

まずsharedフォルダにWorkload Identity Federationを作り直しました。

shared/workload-identity.tf
# Workload Identity Pool
resource "google_iam_workload_identity_pool" "github_actions_pool" {
  workload_identity_pool_id = "github-actions-pool"
  display_name              = "GitHub Actions Pool"
  description               = "Workload Identity Pool for GitHub Actions"
}

# Workload Identity Provider
resource "google_iam_workload_identity_pool_provider" "github_actions_provider" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.github_actions_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-provider"
  display_name                       = "GitHub Actions Provider"
  description                        = "OIDC identity pool provider for GitHub Actions"

  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.actor"      = "assertion.actor"
    "attribute.repository" = "assertion.repository"
    "attribute.ref"        = "assertion.ref"
  }

  attribute_condition = "assertion.repository == '${var.github_repository}'"

  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}
shared/outputs.tf
output "workload_identity_pool_name" {
  description = "Workload Identity Pool Name"
  value       = google_iam_workload_identity_pool.github_actions_pool.name
}

output "workload_identity_provider_name" {
  description = "Workload Identity Provider Name"
  value       = google_iam_workload_identity_pool_provider.github_actions_provider.name
}

Step 2: 既存リソースをimport

既にGCP上にあるリソースをTerraformの管理下に移す作業です。とても怖かったです。

cd apps/terraform/gcp/environments/dev/shared

# 既存のWorkload Identity Poolをimport
terraform import google_iam_workload_identity_pool.github_actions_pool \
  projects/PROJECT_ID/locations/global/workloadIdentityPools/github-actions-pool

# 既存のWorkload Identity Providerをimport
terraform import google_iam_workload_identity_pool_provider.github_actions_provider \
  projects/PROJECT_ID/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider

Step 3: dashboardから削除

cd apps/terraform/gcp/environments/dev/dashboard

# dashboardのstateから削除(実際のGCPリソースは削除されない)
terraform state rm google_iam_workload_identity_pool.github_actions_pool
terraform state rm google_iam_workload_identity_pool_provider.github_actions_provider

Step 4: dashboardの参照先を変更

dashboard/main.tf
# sharedリソースの参照を追加
data "terraform_remote_state" "shared" {
  backend = "gcs"
  config = {
    bucket = "terraform-state-bucket"
    prefix = "dev/shared"
  }
}

# サービスアカウントの権限設定を更新
resource "google_service_account_iam_member" "github_actions_workload_identity_user" {
  service_account_id = google_service_account.github_actions_deployer.name
  role               = "roles/iam.workloadIdentityUser"
  # sharedから参照するように変更
  member             = "principalSet://iam.googleapis.com/${data.terraform_remote_state.shared.outputs.workload_identity_pool_name}/attribute.repository/${var.github_repository}"
}

Step 5: outputsの掃除

dashboardのoutputs.tfに残っていた不要な出力を削除しました。terraform planでエラーが出て焦りました。

dashboard/outputs.tf
-output "workload_identity_provider" {
-  description = "Workload Identity Provider resource name for GitHub Actions"
-  value       = google_iam_workload_identity_pool_provider.github_actions_provider.name
-}

 output "github_actions_configuration" {
   description = "Configuration information for GitHub Actions"
   value = {
-    workload_identity_provider = google_iam_workload_identity_pool_provider.github_actions_provider.name
+    workload_identity_provider = data.terraform_remote_state.shared.outputs.workload_identity_provider_name
     service_account            = google_service_account.github_actions_deployer.email
     project_id                 = var.project_id
     repository                 = var.github_repository
   }
 }

やってみてわかったこと

リソース移行は怖い

  • terraform importterraform state rmなど、一文字でも間違えたらリソースが削除される可能性があります
  • 設定ファイルと実際のGCPリソースがズレている時のデバッグが困難でした
  • どのファイルがどこを参照しているか分からなくなりました
  • 本番環境でこれをやったら事故につながると思います

設計って大事だった

推奨: 最初から適切な分離設計
├── shared/           # 共通認証、ネットワーク基盤
├── dashboard/        # dashboard専用リソース + 専用権限
└── api/              # api専用リソース + 専用権限

最初からsharedに置くべきもの

  • Custom IAM Role (cache_invalidator) - プロジェクト全体で使用可能
  • Service Account - 複数サービスのデプロイで使用したり
  • ネットワーク基盤として一元管理
  • 認証周り(Workload Identity Federation)
  • APIの有効化

各サービス専用で良いもの

  • 特定のサービスでしか確実に使わないもの
    • もしも今後共通で利用をする可能性があればsharedにおいておく

本番でやったらヤバかった

  • 「とりあえず動けばいい」で作った結果のツケが後で回ってきます
  • リソース移行は本番環境では絶対にやりたくありません
  • 学習環境だから安心してミスできるのは本当に価値がありました

まとめ

結果的には移行できましたが、最初からちゃんと設計しておけば不要な作業だったというのが最大の学びでした。

学習環境だから安全にミスできて良かったです。本番だったら絶対やりたくありません。

これからTerraformを始める人は、最初の設計でちゃんと時間をかけた方がいいと思います。

参考資料

Discussion