1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub ActionsでTerraformのCICDパイプライン構築!

Last updated at Posted at 2025-05-04

記事の内容

本記事では、GitHub Actions を使った Terraform のCI/CD 環境構築について説明します。
GCP環境を利用します。

GitHub Actions 導入後のTerraformアクションのフロー

  1. main ブランチへのPR作成
  2. ファイル変更の検知(ファイルの変更のある環境のみにフィルタリング)
  3. ファイル変更を検知した環境にて、terraform planを実行
  4. mainにマージをトリガーに、terraform applyを実行

ディレクトリ構造

rep/
├── modules/
│   └── ...
├── environments/
│   └── develop/
│   │    ├── main.tf
│   │    ├── locals.tf
│   │    ├── variables.tf
│   │    └── backend.tf
│   └── staging/
│        ├── main.tf
│        ├── locals.tf
│        ├── variables.tf
│        └── backend.tf
└── .github/
│        └── workflows/
│        └── terraform.yml
└── .gitignore     

構築手順

  1. GCSバケットを手動作成(Terraformのstate用)
  2. TerraformでWorkload Identityリソース一式をapply
  3. GitHub Actionsのworkflow.ymlを作成
  4. GitHub上でSecretsを設定する
  5. develop環境でVPC作成PRを作成 → develop環境でplanが自動実行される
  6. PRをmainにマージし、applyを実行

実際に構築

1. GCSバケットを手動作成(Terraformのstate用)

image.png

2. TerraformでWorkload Identityリソース一式をapply

状態ファイル保存先を先ほど作成したGCSバケットに設定の上、下記リソースを作成します。

- `google_service_account`: GitHub Actions から使うTerraform用サービスアカウント
- `google_project_iam_member`: 上記SAに `roles/editor` の権限を付与
- `google_iam_workload_identity_pool`: GitHub OIDC用のIDプール
- `google_iam_workload_identity_pool_provider`: GitHub発行のトークンを受け入れるOIDCプロバイダ
- `google_service_account_iam_member`: SAとWorkload Identity Federationの紐付け(`roles/iam.workloadIdentityUser`)

以下、上記のリソースをまとめたファイル内容です。

// main.tf
resource "google_project_iam_member" "sa_roles" {
  project = var.project_id
  role    = "roles/editor"
  member  = "serviceAccount:${google_service_account.terraform.email}"
}


resource "google_iam_workload_identity_pool" "github_pool" {
  workload_identity_pool_id = "github-pool"
  display_name              = "GitHub Actions Pool"
  description               = "OIDC identity pool for GitHub Actions"
}

resource "google_iam_workload_identity_pool_provider" "github_provider" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.github_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "github"
  display_name                       = "GitHub OIDC Provider"

  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }

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

  attribute_condition = "assertion.repository == '${local.github_repo}'"
}

resource "google_service_account" "terraform" {
  account_id   = "terraform-cicd"
  display_name = "Terraform GitHub Actions"
}

resource "google_service_account_iam_member" "wif_binding" {
  service_account_id = google_service_account.terraform.name
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_pool.name}/attribute.repository/${local.github_repo}"
}
// variables.tf
variable "project_id" {
  default="qiita-dev-458806"
  type = string
}


// locals.tf
locals {
  github_repo    = "<org/repo>"
}

3. GitHub Actionsのworkflow.ymlを作成

.github/workflow.yml
name: Terraform

on:
  push:
    branches:
      - main 
    paths:
      - 'environments/**' #environmentsディレクトリ配下の変更時のみをトリガー
  pull_request:
    paths:
      - 'environments/**' #environmentsディレクトリ配下の変更時のみをトリガー

permissions:
  contents: read #リポジトリの読取りに必要な権限
  pull-requests: write #PRコメントに必要な権限
  id-token: write #GCPログインに必要な権限

jobs:
  detect-changes:
    name: Detect changed environments #環境ごとのファイル変更の検知
    runs-on: ubuntu-latest
    outputs:  #各環境のファイルが変更されたかどうかのoutput。trueかfalseが代入される。
      develop: ${{ steps.changes.outputs.develop }}
      staging: ${{ steps.changes.outputs.staging }}
    steps:
      # https://github.com/actions/checkout/tree/v3/
      - uses: actions/checkout@v3 #GitHubのコードをrunnerにクローン。本stepであるファイル変更チェックに必要。

      # https://github.com/dorny/paths-filter/tree/v2/
      - uses: dorny/paths-filter@v2 
        id: changes
        with:
          filters: |
            develop:
              - 'environments/develop/**'
            staging:
              - 'environments/staging/**'

  terraform-develop: #develop環境のファイルで変更を検知した場合にこのjobを実行
    needs: detect-changes #detects-changes jobの後にこのjobを実行することを指定
    if: needs.detect-changes.outputs.develop == 'true'
    runs-on: ubuntu-latest
    name: Terraform (develop)
    env:
      tf_actions_working_dir_dev: environments/develop

    defaults:
      run:
        working-directory: ${{ env.tf_actions_working_dir_dev }} #environments/develop配下で本jobを実行することを指定
    steps:
      # https://github.com/actions/checkout/tree/v3/
      - uses: actions/checkout@v3  # このリポジトリの Terraform コードを runner にチェックアウト

      # https://github.com/hashicorp/setup-terraform/tree/v2/
      - uses: hashicorp/setup-terraform@v2 # 指定バージョンの Terraform をインストールして使えるようにする
        with:
          terraform_version: 1.6.6

      - name: Authenticate to Google Cloud via Workload Identity Federation
      # https://github.com/google-github-actions/auth/tree/v1/
        uses: google-github-actions/auth@v1 #OIDC認証
        with:
          workload_identity_provider: "projects/${{ secrets.GCP_PROJECT_NUMBER_DEV }}/locations/global/workloadIdentityPools/github-pool/providers/github" #このWorkload Identity Providerを作成したGCPプロジェクトの「プロジェクト番号」を指定
          service_account: "terraform-cicd@qiita-dev-458806.iam.gserviceaccount.com" #作成したSA

      - name: Terraform fmt
        id: fmt
        run: terraform fmt -check
        continue-on-error: true

      - name: Terraform Init
        id: init
        run: terraform init

      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color

      - name: Terraform Plan
        id: plan
        run: |
          terraform plan -no-color | tee plan.txt
          echo "stdout<<EOF" >> $GITHUB_OUTPUT
          cat plan.txt >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT
        continue-on-error: true
        
      - uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
          GCP_PROJECT_ID_DEV: ${{ secrets.GCP_PROJECT_ID_DEV }}
          GCP_PROJECT_NUMBER_DEV: ${{ secrets.GCP_PROJECT_NUMBER_DEV }}

        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const output = `### Project: **${process.env.GCP_PROJECT_ID_DEV}** 

            #### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
            <details><summary>Validation Output</summary>
      
            \`\`\`\n
            ${{ steps.validate.outputs.stdout }}
            \`\`\`
      
            </details>
      
            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
      
            <details><summary>Show Plan</summary>
      
            \`\`\`\n
            ${process.env.PLAN}
            \`\`\`
      
            </details>
      
            *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir_dev }}\`, Workflow: \`${{ github.workflow }}\`*`;
      
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })
      - name: Terraform Apply
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: terraform apply --auto-approve


  terraform-staging: #staging環境のファイルで変更を検知した場合にこのjobを実行
    needs: detect-changes #detects-changes jobの後にこのjobを実行することを指定
    if: needs.detect-changes.outputs.staging == 'true'
    runs-on: ubuntu-latest
    name: Terraform (staging)
    (下記develop環境と同様に設定)

参考:

4. GitHub上でSecretsを設定する

上記.github/workflows/terraform.ymlで定義したSecret値について、GitHubリポジトリ上で設定します。

  • GCP_PROJECT_NUMBER_DEV:プロジェクト番号
    (gcloud projects describe <プロジェクトID> --format="value(projectNumber)" コマンドで確認可能)
  • GCP_PROJECT_ID_DEV:プロジェクトID
    image.png

なぜプロジェクトID・プロジェクト番号を Secret にするのか?
workflow.yml に記述される GCP 認証設定では、環境ごとに異なる GCP プロジェクトの ID(文字列)および プロジェクト番号(数値)を使用します。
これらは環境によって変わる上、Terraformの apply 対象を決める重要な値であるため、以下の理由から Secret 化しています:

  • 🔐 セキュリティ:コード上に公開されるリスクを防止
  • 🔁 環境の切り替えを簡単に:staging / production で再利用可能に
  • 🚫 誤デプロイ防止:対象環境を Secret で明示することで、意図しないプロジェクトへの操作を防ぐ

5. develop環境でVPC作成PRを作成 → develop環境でplanが自動実行される

environments/develop配下のファイルでVPC作成コードを記載し、PRを作成します。
ファイル変更を検知しなかったstaging環境はplanの実行をスキップします。

PRのコメントにて、変更を検知したdevelop環境でterraform planが自動実行され、Plan結果が出力されます。
image.png
image.png

6. PRをmainにマージし、applyを実行

mainにマージする
image.png

Actionsタブにて、Apply結果を確認します。
image.png

構築を終えての感想

GitHub Actionsの作成について、何となく難しいと思っており、抵抗があったが、意外と公式ドキュメントを確認しながら手軽に構築できました。
AtlantisなどGitHub Actions以外にterraformのCICD環境があるが、昨今、セキュリティ問題が多い中、Atlantisのように自前でサーバーを構築する必要ないことがメリットだと感じました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?