G-gen の武井です。
当記事では Infrastructure as Code (IaC) を実現する Terraform を Google Cloud (旧称 GCP) で実行する際、GitHub の CI/CD 機能である GitHub Actions を介して実行する方法を紹介します。
GitHub Actions とは
概要
GitHub Actions とは、ソースコード管理ツールである GitHub に包含される機能の1つで、GitHub 上で管理されるソースをもとに CI/CD (継続的インティグレーション / 継続的デリバリー) を実現します。
例えば Terraform を GitHub Actions で実行する場合、Pull Request や Merge などのイベントをトリガーにして、plan や apply などの処理を自動化できます。
ワークフロー
GitHub Actions で自動化したい処理とその内容・条件を定義したものを ワークフロー (Workflow) と言います。
ワークフローは以下の条件に従い定義します。
- 定義ファイルは YAML 形式 で記述する
- 定義ファイルは
.github/workflows
ディレクトリ に保存する
図説
GitHub Actions で Terraform を実行した際のイメージは以下のとおりです。
- main ブランチ へ Pull Request が行われた場合、Google Cloud 環境に
terraform plan
を実行するワークフローが起動 (図①) - main ブランチ へ Merge が行われた場合、Google Cloud 環境に
terraform apply
を実行するワークフローが起動 (図②)
Google Cloud との連携
概要
GitHub Actions で Google Cloud 上のリソースを管理する場合、Google Cloud への 認証 を通す必要があります。
従来の認証
従来は サービスアカウントキーによる認証 が必要でした。キーによる認証では以下のような制約や懸念が考えられます。
- キーの エクスポート / 保存 といった管理工数の発生
- キー漏洩時のセキュリティリスクの懸念
- 組織のポリシー (
constraints/iam.disableServiceAccountKeyCreation
) で統制された環境下での利用制限
Workload Identity 連携による認証
2021年10月、GitHub Actions で OIDC (OpenID Connect) トークンを使った認証 がサポートされたことで、Workload Identity 連携を使用したキーレスな認証 が実現可能となりました。
Workload Identity 連携
仕組み
Workload Identity 連携 は、GitHub 等の外部 ID プロバイダ (IdP) と連携し、Google Cloud のリソースを呼び出します。
GitHub のワークロードはセキュリティトークンサービス (STS) エンドポイントを呼び出し、IdP から取得した認証トークンを有効期限が短い Google Cloud アクセストークン と交換します。
メリット
ワークロード (GitHub Actions) はトークンの交換でサービスアカウントの権限を借用できます。そのため、従来のようなキー発行は不要となり、それに伴う制約や懸念が解消されます。
図説
Workload Identity 連携を図に表すと以下のイメージとなります。
設定方法
概要
以下の流れで GitHub Actions を設定します。
- Terraform のセットアップ
- GitHub Actions の設定
Terraform のセットアップ
GitHub Actions の実行に必要なリソースを Terraform でデプロイするため、以下のリソースを事前に準備します。
- プロジェクト
- tfstate ファイル格納用バケット
- Terraform 用サービスアカウント
gcloud
コマンドで準備する場合、以下の記事の 「3章 事前準備」 にて説明しています。
GitHub Actions の設定
Terraform のセットアップが完了したら、GitHub Actions を設定します。
ディレクトリ構成
GitHub 上で管理するソースのディレクトリ構成は以下の通りです。この章では main.tf
と terraform.yml
について説明します。
Workload Identity 連携の設定
main.tf
に Workload Identity 連携に関する設定を定義し、Cloud Shell や Local 環境などから Terraform を実行してリソースをデプロイします。
コードは以下の通りで、詳細については 弊社ブログ で解説しています。
# local 定義 locals { github_repository = "myuser/myrepository" project_id = "myproject" region = "asia-northeast1" terraform_service_account = "tf-exec@myproject.iam.gserviceaccount.com" # api 有効化用 services = toset([ # Workload Identity 連携用 "iam.googleapis.com", # IAM "cloudresourcemanager.googleapis.com", # Resource Manager "iamcredentials.googleapis.com", # Service Account Credentials "sts.googleapis.com" # Security Token Service API ]) } # provider 設定 terraform { required_providers { google = { source = "hashicorp/google" version = ">= 4.0.0" } } required_version = ">= 1.3.0" backend "gcs" { bucket = "myproject_terraform_tfstate" prefix = "terraform/state" } } ## API の有効化(Workload Identity 用) resource "google_project_service" "enable_api" { for_each = local.services project = local.project_id service = each.value disable_dependent_services = true } # Workload Identity Pool 設定 resource "google_iam_workload_identity_pool" "mypool" { provider = google-beta project = local.project_id workload_identity_pool_id = "mypool" display_name = "mypool" description = "GitHub Actions で使用" } # Workload Identity Provider 設定 resource "google_iam_workload_identity_pool_provider" "myprovider" { provider = google-beta project = local.project_id workload_identity_pool_id = google_iam_workload_identity_pool.mypool.workload_identity_pool_id workload_identity_pool_provider_id = "myprovider" display_name = "myprovider" description = "GitHub Actions で使用" attribute_mapping = { "google.subject" = "assertion.sub" "attribute.repository" = "assertion.repository" } oidc { issuer_uri = "https://token.actions.githubusercontent.com" } } # GitHub Actions が借用するサービスアカウント data "google_service_account" "terraform_sa" { account_id = local.terraform_service_account } # サービスアカウントの IAM Policy 設定と GitHub リポジトリの指定 resource "google_service_account_iam_member" "terraform_sa" { service_account_id = data.google_service_account.terraform_sa.id role = "roles/iam.workloadIdentityUser" member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.mypool.name}/attribute.repository/${local.github_repository}" }
ワークフローの定義
次に、ワークフローの定義ファイル (terraform.yml
) を準備します。
※ .github/workflows/
ディレクトリに格納してください。
コードは HashiCorp 公式サイト と Google Cloud 公式ガイド を参考にしています。
※ 32行目の ${PROJECT_NUMBER}
は適宜置き換えてください。
name: terraform # main ブランチへのPull Request と Merge をトリガーに指定 on: push: branches: - main pull_request: # 作業ディレクトリの指定 defaults: run: working-directory: ./ # ジョブ / ステップ / アクションの定義 jobs: terraform-workflow: runs-on: ubuntu-latest permissions: id-token: write contents: read pull-requests: write # Workload Identity 連携 steps: # https://cloud.google.com/iam/docs/using-workload-identity-federation#generate-automatic - uses: actions/checkout@v3 - id: 'auth' name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v1' with: workload_identity_provider: 'projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/mypool/providers/myprovider' service_account: 'tf-exec@myproject.iam.gserviceaccount.com' # https://github.com/hashicorp/setup-terraform - uses: hashicorp/setup-terraform@v2 - name: Terraform fmt id: fmt run: terraform fmt -check -recursive 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 continue-on-error: true - name: Comment Terraform Plan uses: actions/github-script@v6 if: github.event_name == 'pull_request' env: PLAN: "terraform\n${{ steps.plan.outputs.stdout }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const output = `#### 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 }}\`, Workflow: \`${{ github.workflow }}\`*`; github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: output }) - name: Terraform Plan Status if: steps.plan.outcome == 'failure' run: exit 1 # main ブランチに push した場合にだけ terraform apply も実行される - name: Terraform Apply if: github.ref == 'refs/heads/main' && github.event_name == 'push' run: terraform apply -auto-approve -input=false
動作検証
概要
以下の流れで GitHub Actions の動作検証を行います。
- 動作検証用リソースの定義
- Pull Request 実行
- Merge 実行
動作検証用リソースの定義
test.tf
に動作検証用に作成する Cloud Storage バケットを定義します。
resource "google_storage_bucket" "test001" { name = "${local.project_id}-bucket-test001" project = local.project_id location = local.region force_destroy = true uniform_bucket_level_access = true }
Pull Request 実行
GitHub にログインし、main ブランチに Pull Request
を実行します。
Actions タブから画面を確認すると、ワークフローが実行されたことがわかります。
ワークフロー名をクリックすると詳細が確認できます。
今回のトリガーは main ブランチへの Pull Request
ですので、terrafrom plan
が実行されています。
Merge 実行
Pull Request の結果が問題ない事を確認したら main ブランチに Merge
を実行します。
再度 Actions タブから画面を確認すると、さらにワークフローが自動実行されたことがわかります。
今回のトリガーは main ブランチへの Merge
ですので、先程とは異なり terrafrom apply
が実行されています。
武井 祐介 (記事一覧)
クラウドソリューション部所属。G-gen唯一の山梨県在住エンジニア
Google Cloud Partner Top Engineer 2025 選出。IaC や CI/CD 周りのサービスやプロダクトが興味分野です。
趣味はロードバイク、ロードレースやサッカー観戦です。
Follow @ggenyutakei