💡

GCPでTerraformを使った自動デプロイ実装とトラブルシューティング

2025/03/11に公開

今回のGCP環境でのTerraform自動デプロイ実装において、いくつかのトラブルが発生しました。これらの問題と解決策をまとめることで、同様の実装を行う方の参考になるでしょう。

1. 発生した問題と解決策

問題1: バケットへのアクセス権限不足

エラーメッセージ:

Error: Failed to get existing workspaces: querying Cloud Storage failed: googleapi: Error 403: [サービスアカウント名] does not have storage.objects.list access to the Google Cloud Storage bucket. Permission 'storage.objects.list' denied on resource (or it may not exist)., forbidden

原因:
サービスアカウントにTerraformの状態を保存するGCSバケットへのアクセス権限がありませんでした。

解決策:

# バケットに対してサービスアカウントに権限を付与
gsutil iam ch serviceAccount:[サービスアカウント名]:roles/storage.admin gs://[バケット名]

問題2: バケット作成権限の不足

エラーメッセージ:

Error: googleapi: Error 403: [サービスアカウント名] does not have storage.buckets.create access to the Google Cloud project. Permission 'storage.buckets.create' denied on resource (or it may not exist)., forbidden

原因:
サービスアカウントにプロジェクトレベルでバケットを作成する権限がありませんでした。

解決策:

# プロジェクトレベルでサービスアカウントにStorage Admin権限を付与
gcloud projects add-iam-policy-binding [プロジェクトID] --member=serviceAccount:[サービスアカウント名] --role=roles/storage.admin

# より広範な権限も付与
gcloud projects add-iam-policy-binding [プロジェクトID] --member=serviceAccount:[サービスアカウント名] --role=roles/editor

問題3: 存在しないサービスアカウントの参照

エラーメッセージ:

Error: Error applying IAM policy for storage bucket "b/[バケット名]": Error setting IAM policy for storage bucket "b/[バケット名]": googleapi: Error 400: Service account github-actions@your-project-id.iam.gserviceaccount.com does not exist., invalid

原因:
Terraformの設定ファイル内で、実際には存在しないサービスアカウントを参照していました。

解決策:
variables.tfファイルのservice_account_email変数のデフォルト値を実際のサービスアカウントに修正しました。

variable "service_account_email" {
  description = "アクセス権を付与するサービスアカウントのメールアドレス"
  type        = string
-  default     = "github-actions@your-project-id.iam.gserviceaccount.com"
+  default     = "[実際のサービスアカウント名]"
}

2. GCPでTerraformを使った自動デプロイの実装手順

以下に、GCPでTerraformを使った自動デプロイを実装するための手順をまとめます。

2.1 前提条件

  • GitHubアカウント
  • GCPアカウントとプロジェクト
  • 基本的なTerraformとGCPの知識

2.2 実装手順

1. GCPプロジェクトの準備

  1. GCPコンソールでプロジェクトを作成または選択

    # プロジェクトの確認
    gcloud projects list
    
    # プロジェクトの選択
    gcloud config set project [プロジェクトID]
    
  2. 必要なAPIを有効化

    # Storage APIの有効化
    gcloud services enable storage.googleapis.com
    
    # Resource Manager APIの有効化
    gcloud services enable cloudresourcemanager.googleapis.com
    

2. サービスアカウントの作成と権限設定

  1. サービスアカウントの作成

    gcloud iam service-accounts create [サービスアカウント名] \
      --display-name="[表示名]"
    
  2. 必要な権限の付与

    # Storage管理者権限
    gcloud projects add-iam-policy-binding [プロジェクトID] \
      --member="serviceAccount:[サービスアカウント名]@[プロジェクトID].iam.gserviceaccount.com" \
      --role="roles/storage.admin"
    
    # 編集者権限(より広範な権限)
    gcloud projects add-iam-policy-binding [プロジェクトID] \
      --member="serviceAccount:[サービスアカウント名]@[プロジェクトID].iam.gserviceaccount.com" \
      --role="roles/editor"
    
  3. サービスアカウントキーの作成

    gcloud iam service-accounts keys create key.json \
      --iam-account=[サービスアカウント名]@[プロジェクトID].iam.gserviceaccount.com
    

3. Terraformの状態管理用バケットの作成

  1. バケットの作成

    gsutil mb -l [リージョン] gs://[バケット名]
    
  2. バージョニングの有効化

    gsutil versioning set on gs://[バケット名]
    
  3. サービスアカウントにバケットへのアクセス権限を付与

    gsutil iam ch serviceAccount:[サービスアカウント名]@[プロジェクトID].iam.gserviceaccount.com:roles/storage.admin gs://[バケット名]
    

4. Terraformの設定ファイルの作成

  1. プロバイダー設定(provider.tf

    provider "google" {
      project = var.gcp_project_id
      region  = var.gcp_region
    }
    
  2. バックエンド設定(backend.tf

    terraform {
      backend "gcs" {
        bucket = "[バケット名]"
        prefix = "terraform/state"
      }
    }
    
  3. 変数定義(variables.tf

    variable "gcp_project_id" {
      description = "GCPのプロジェクトID"
      type        = string
    }
    
    variable "gcp_region" {
      description = "GCPのリージョン"
      type        = string
      default     = "asia-northeast1"
    }
    
    variable "service_account_email" {
      description = "アクセス権を付与するサービスアカウントのメールアドレス"
      type        = string
      default     = "[サービスアカウント名]@[プロジェクトID].iam.gserviceaccount.com"
    }
    
  4. リソース定義(main.tf

    resource "google_storage_bucket" "example" {
      name     = "example-bucket-${random_string.random.result}"
      location = var.gcp_region
      
      versioning {
        enabled = true
      }
    }
    
    resource "random_string" "random" {
      length  = 8
      special = false
      lower   = true
      upper   = false
    }
    

5. GitHub Actionsの設定

  1. GitHub Secretsの設定

    • GCP_CREDENTIALS: サービスアカウントのJSONキー
    • GCP_PROJECT_ID: GCPプロジェクトID
  2. ワークフローファイルの作成(.github/workflows/terraform.yml

    name: Terraform GCP Deployment
    
    on:
      push:
        branches: [ main ]
      pull_request:
        branches: [ main ]
      workflow_dispatch:
    
    jobs:
      terraform:
        runs-on: ubuntu-latest
        
        steps:
        - name: Checkout
          uses: actions/checkout@v3
    
        - name: Setup Terraform
          uses: hashicorp/setup-terraform@v2
          with:
            terraform_version: 1.5.0
    
        - name: Authenticate to Google Cloud
          uses: google-github-actions/auth@v1
          with:
            credentials_json: ${{ secrets.GCP_CREDENTIALS }}
    
        - name: Set up Google Cloud SDK
          uses: google-github-actions/setup-gcloud@v1
    
        - name: Terraform Init
          run: terraform init
          working-directory: ./gcp
    
        - name: Terraform Format
          run: terraform fmt -check
          working-directory: ./gcp
    
        - name: Terraform Validate
          run: terraform validate
          working-directory: ./gcp
    
        - name: Terraform Plan
          run: terraform plan -var="gcp_project_id=${{ secrets.GCP_PROJECT_ID }}"
          working-directory: ./gcp
          if: github.event_name == 'pull_request'
    
        - name: Terraform Apply
          run: terraform apply -auto-approve -var="gcp_project_id=${{ secrets.GCP_PROJECT_ID }}"
          working-directory: ./gcp
          if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
    

3. トラブルシューティングのポイント

3.1 権限関連の問題

  1. バケットアクセス権限の確認

    # バケットのIAMポリシーを確認
    gsutil iam get gs://[バケット名]
    
  2. サービスアカウントの権限確認

    # サービスアカウントの権限を確認
    gcloud projects get-iam-policy [プロジェクトID] \
      --flatten="bindings[].members" \
      --format="table(bindings.role)" \
      --filter="bindings.members:serviceAccount:[サービスアカウント名]@[プロジェクトID].iam.gserviceaccount.com"
    

3.2 Terraform設定の問題

  1. バックエンド設定の確認

    • バケット名が正しいか
    • リージョンが正しいか
    • プレフィックスが適切か
  2. 変数のデフォルト値の確認

    • プレースホルダーが実際の値に置き換えられているか
    • 特にサービスアカウントのメールアドレスが正確か

3.3 GitHub Actions関連の問題

  1. シークレットの確認

    • GCP_CREDENTIALSが正しいJSONキーを含んでいるか
    • GCP_PROJECT_IDが正確なプロジェクトIDを含んでいるか
  2. ワークフローの確認

    • 作業ディレクトリが正しく設定されているか
    • 必要な環境変数がすべて設定されているか

4. ベストプラクティス

  1. 最小権限の原則

    • サービスアカウントには必要最小限の権限のみを付与する
    • 本番環境ではroles/editorのような広範な権限は避ける
  2. 状態ファイルの保護

    • バケットのバージョニングを有効にする
    • バケットへのアクセスを制限する
  3. 変数の使用

    • ハードコードされた値ではなく変数を使用する
    • 環境ごとに異なる値は変数で管理する
  4. モジュール化

    • 再利用可能なコンポーネントはモジュール化する
    • 環境ごとの違いはモジュールのパラメータで吸収する
  5. コードレビュー

    • インフラの変更は必ずコードレビューを行う
    • terraform planの結果を確認する

5. まとめ

GCPでTerraformを使った自動デプロイを実装する際には、以下の点に注意することが重要です:

  1. 適切な権限設定:サービスアカウントには必要な権限を正確に付与する
  2. 状態管理の設定:Terraformの状態を保存するバケットを適切に設定する
  3. 変数の正確な設定:特にサービスアカウントのメールアドレスなどのプレースホルダーを実際の値に置き換える
  4. GitHub Secretsの正確な設定:認証情報やプロジェクトIDを正確に設定する

これらのポイントに注意することで、GCPでのTerraformを使った自動デプロイをスムーズに実装することができます。トラブルが発生した場合も、エラーメッセージを注意深く読み、適切な権限設定や設定ファイルの修正を行うことで解決できるでしょう。

Discussion