CTS-KB

Terraform IaC 実践ガイド:準備編 — WIF キーレス認証・bootstrap・Secret Manager 設計

⏱ 約 9 分で読めます
#Terraform #IaC #Google Cloud #AWS #WIF #Secret Manager

⚠️ なぜ「準備」が最も重要なのか

Terraform で最も多いトラブルは、準備不足のまま terraform apply を実行することで起きます。

よくある失敗パターン:

  • 権限不足で apply 失敗 → 中途半端なリソースが残る
  • API が有効化されておらず 403 SERVICE_DISABLED
  • State の保存先を決めずにローカルで実行 → チームで共有できない
  • 手動で作ったリソースと Terraform が衝突する

コードを書く前に、環境を整える。 これが Terraform 運用の鉄則です。

🗺️ 準備の全体像

本記事はシリーズ 「Terraform IaC 実践ガイド」第 2 回(準備編) です。第 1 回 概要 で示したマルチクラウド基盤を、コードを書く前に設計で固める段階を扱います。

Terraform を導入する前に、以下を整理します。

0. bootstrap(鶏と卵の解決)  ← State バケットと土台プロジェクトを手で作る

1. クラウドアカウント設計

2. 認証方式の選定(WIF)

3. 権限(IAM)設計

4. State 管理の設計

5. Secret Manager で変数を集約

6. ディレクトリ構成の設計

順番が重要です。上から順に決めないと、後工程で手戻りが発生します。

🥚 0. bootstrap — 鶏と卵を解く

Terraform で全部を管理したい。しかし State の保存先(GCS バケット)は、Terraform を動かす前に存在していないといけない。これが「鶏と卵」問題です。

解決策は、最小限の土台だけを Terraform 管理外で手作業で作ること。

# State バケットだけは手で作る(Terraform 管理外・ローカル実行)
gsutil mb -l asia-northeast1 gs://my-project-tfstate
gsutil versioning set on gs://my-project-tfstate

実運用では、この土台を bootstrap プロジェクトとして 1 つ用意し、そこに以下を集約します。

bootstrap が持つもの役割
State バケット全スタックの State を集約(プレフィックスで分離)
WIF Pool / ProviderCI/CD のキーレス認証の入口
CI 用サービスアカウントSecret Manager 読み取り・各スタック SA への橋渡し
Secret Manager各スタックの変数(tfvars)を集約

落とし穴: bootstrap プロジェクトは「最初の 1 回だけローカルから apply」する特別なスタックです。ここを CI に載せようとすると、また鶏と卵になります。bootstrap だけはローカル apply を許容し、それ以外は CI 経由、と割り切ります。

☁️ 1. クラウドアカウント設計

環境分離の原則

本番・ステージング・開発は、プロジェクト(アカウント)レベルで分離します。

クラウド分離単位
Google Cloudプロジェクトmyapp-prod, myapp-stg
AWSアカウント本番アカウント、検証アカウント
Azureサブスクリプション本番サブスクリプション、検証サブスクリプション

同一プロジェクト内で環境を分けると、本番リソースを誤って削除するリスクが生まれます。

マルチクラウドの場合

クラウドごとに役割を明確にします。

例:
  Google Cloud → メインクラウド(Cloud Run, Cloud SQL 等)
  AWS          → 認証基盤のみ(Cognito)

「両方で同じことをやる」のではなく、各クラウドの得意領域を活かす設計にします。

🔐 2. 認証方式の選定

サービスアカウントキーは使わない

Terraform の認証で最も避けるべきは、サービスアカウントキー(JSON キー)の利用です。

方式セキュリティ運用コスト推奨
サービスアカウントキー漏洩リスクありキーのローテーション必要NG
Workload Identity Federation (WIF)キーレスローテーション不要推奨
gcloud auth(ローカル開発)個人認証手動ログイン開発のみ

Workload Identity Federation(WIF)

CI/CD パイプラインからの認証は WIF を使います。GitLab / GitHub Actions の OIDC トークンで、キーなしに Google Cloud や AWS に認証できます。

CI/CD ランナー
  ↓ OIDC トークン
Workload Identity Pool
  ↓ トークン交換
サービスアカウント(権限あり)
  ↓ 認証済み
Terraform 実行

attribute mapping と最小権限

WIF Provider では、OIDC トークンの主張(assertion)を GCP 側の属性にマッピングし、どのリポジトリ・どのブランチからの実行を許すかを絞ります。マッピングする主な属性は「リポジトリ識別子・ブランチ/タグ・参照種別・保護ブランチか否か」です。

これにより、本番スタックは保護ブランチからのみ・検証スタックは MR からも回せる、といった環境別の絞り込みができます(条件分岐の考え方は第 5 回 運用編)。

設計判断: WIF Provider はリポジトリ単位で分けます。1 つの Provider を全社共有にすると条件が緩くなり最小権限が崩れるため、クライアント・リポジトリごとに切るのが安全です。

クロスクラウド認証

Google Cloud と AWS の両方を Terraform で管理する場合:

GitLab Runner
  ├→ GCP WIF で Google Cloud に認証
  └→ GCP SA の OIDC トークン → AWS STS AssumeRoleWithWebIdentity
     → AWS IAM Role で AWS に認証

キーの受け渡しなしで、クラウド間の認証が成立します。

AWS の落とし穴: OIDC Provider は AWS アカウントに 1 つしか作れません。stg と prod が同一 AWS アカウントを共有する場合、各スタックで resource として作ると EntityAlreadyExists で衝突します。OIDC Provider は bootstrap 側で 1 度だけ作り、各スタックは data ソースで参照するだけにします。詳細は GCP → AWS キーレス認証の落とし穴

🛡️ 3. 権限(IAM)設計

最小権限の原則

Terraform 用のサービスアカウントには、必要最小限の権限だけを付与します。

✗ roles/owner(何でもできる → 危険)
✗ roles/editor(広すぎる)
○ 必要なロールだけを個別に付与

サービスアカウントの分離

1 つのサービスアカウントで全部やらず、用途別に分離します。実運用では命名規約を定めたうえで、おおむね 3 系統に分けています。

サービスアカウント用途権限
Terraform 実行用インフラ構築・変更リソース作成・変更
CI デプロイ用アプリのデプロイCloud Run デプロイのみ
ランタイム用実行時のサービス実行に必要な最小権限

「Terraform を動かす権限」と「アプリが実行時に使う権限」を分けるのが肝です。これを混ぜると、アプリ起動時の権限事故が Terraform 全体に波及します。命名は環境・用途が一目で分かる規約をプロジェクトごとに定めておきます。

API の有効化を忘れない(両プロジェクトで)

Terraform でリソースを作る前に、対象の API を有効化します。

# Google Cloud の場合
gcloud services enable run.googleapis.com           # Cloud Run
gcloud services enable sqladmin.googleapis.com       # Cloud SQL
gcloud services enable secretmanager.googleapis.com  # Secret Manager
gcloud services enable iamcredentials.googleapis.com # WIF の SA impersonation

これを忘れると 403 SERVICE_DISABLED になります。

マルチプロジェクトの落とし穴: 対象プロジェクトで API を有効化しても、bootstrap プロジェクト側でも同じ API が必要になるケースがあります(SA impersonation や横断操作)。「対象側は有効なのに 403 が出る」ときは、bootstrap 側の API を疑ってください(第 5 回 運用編 の障害 3)。

組織ポリシーの事前確認

GCP の組織ポリシーは、デフォルトで allUsers(公開アクセス)をブロックすることがあります。公開エンドポイントを作る予定があるなら、Tag Binding を使った解放の仕組みを準備段階で設計しておきます(実装手順は第 5 回 運用編 の障害 4)。準備段階で気付かないと、apply 時に does not belong to a permitted customer で詰まります。

💾 4. State 管理の設計

State とは

Terraform の State は、「Terraform が管理しているリソースの一覧」 です。

State ファイル(terraform.tfstate)
  ├ Cloud Run サービス A → 実際の GCP リソース
  ├ Cloud SQL インスタンス B → 実際の GCP リソース
  └ S3 バケット C → 実際の AWS リソース

State がないと、Terraform はどのリソースを自分が作ったのか判別できません。

リモートバックエンド必須

State はリモートに保存します。ローカルに置くと、チームで共有できません。

GCS に統合する

マルチクラウド構成(GCP + AWS + Azure)の場合、State の保存先をクラウドごとに分けると管理が煩雑になります。GCS(Cloud Storage)に統合すると、State の管理対象が 1 箇所にまとまり、運用がシンプルになります。

# GCP のスタックも、AWS のスタックも、同じ GCS バックエンド
terraform {
  backend "gcs" {
    bucket = "my-project-tfstate"
  }
}

State バケットの作成

State 保存用のバケットは Terraform 管理外で作成します(鶏と卵の問題)。

gsutil mb -l asia-northeast1 gs://my-project-tfstate
gsutil versioning set on gs://my-project-tfstate

バージョニングは必ず有効化します。State を壊した場合の復旧に必要です。

🗝️ 5. Secret Manager で変数を集約する

スタックごとの変数(プロジェクト ID・WIF audience・SA・バックエンド設定など)を GitLab Variables に散らすのではなく、Secret Manager に集約します。CI/CD は実行時に bootstrap 認証で Secret Manager から読み出します。

ポイントは 2 つだけです。

  • スタック × 環境を一意に特定できる命名規約を決める(スタックが増えても破綻しない)
  • 実値は Secret Manager 側にのみ置き、リポジトリや GitLab Variables には持たせない

狙い: 認証情報の置き場所を 1 箇所に集約し、GitLab 側には「Secret Manager を読む最小権限」だけを持たせます。スタック追加 = Secret を 1 つ足すだけ、という運用にします。命名規約の具体形はプロジェクト内で定義します。

📁 6. ディレクトリ構成の設計

module / environments 分離パターン

terraform/
├── environments/               # 環境別エントリーポイント(薄い)
│   ├── stg/
│   │   ├── client-a/           # クライアントスタック
│   │   └── auth-stack/         # 認証スタック(AWS Cognito + GCP)
│   └── prod/
│       ├── client-a/
│       ├── client-b/
│       └── auth-stack/
├── infra/
│   └── prod/infra-bootstrap/   # bootstrap(State バケット・WIF Pool 等/ローカル実行)
└── modules/
    ├── infra/                  # 基盤プリミティブ(1 リソース群 = 1 モジュール)
    │   ├── gcp/                #   cloud-run, cloud-sql, load-balancer, dns-zone, gitlab-wif, ...
    │   └── aws/                #   cognito, ses, lightsail, ...
    ├── clients/                # クライアント単位の構成(infra を束ねる)
    │   ├── client-a/
    │   └── client-b/
    └── services/               # 横断サービス({業務ドメイン}/{サービス})
        └── app-domain/
            └── shared-svc/

設計原則

原則説明
単一責任各モジュールは 1 つの責務のみ
環境差分は変数でコードの重複を排除
State はスタック単位environments/prod/my-app ごとに 1 つの State

モジュールに環境固有の値をハードコードしてはいけません。すべて変数で渡すのが原則です。

🚫 やってはいけないこと

禁止事項理由
CLI で直接リソース作成State と実態が不一致になる
ローカルから本番に直接 apply事故のリスク、CI/CD を通すこと
State ファイルをコミット機密情報が含まれる
サービスアカウントキーの利用漏洩リスク、WIF を使う
本番環境で terraform destroy多層防御で禁止すべき

特に CLI で直接リソースを作ると、State との不整合(State drift) が発生します。Terraform で管理すると決めたら、すべて Terraform 経由で変更してください。

📚 シリーズ記事

#タイトル内容
1概要マルチクラウド構成の全体像と AI 駆動運用
2準備編(本記事)アカウント設計・WIF 認証・bootstrap・State 管理
3モジュール設計編再利用可能なモジュールの設計原則
4CI/CD パイプライン編動的 child pipeline・WIF キーレス認証・GCP↔AWS
5運用編State drift 解消・障害対応・AI 運用ループ

関連記事