Article

GitHub ActionsとTerraformで実現するインフラ自動化の完全ガイド2025

高田晃太郎
GitHub ActionsとTerraformで実現するインフラ自動化の完全ガイド2025

主要調査の傾向を俯瞰すると、自動化に資するプラットフォームを組織的に導入したチームほど、デプロイ頻度と変更失敗率で一貫して高い成績を示すことがわかる[1]。DORAの研究では、デプロイ頻度、変更のリードタイム、変更失敗率、サービス回復時間という四指標の改善に自動化が強く相関することが示され[1]、HashiCorpのState of Cloud Strategyでは、すでにマルチクラウドを採用している組織が約76%に達し、今後2年以内の計画を含めると9割前後がマルチクラウド志向にあると報告された[2,3]。クラウドの利用が複雑化するほど、手作業の運用は累積的なリスクとコストを生む。そこで注目すべきは、GitHub ActionsとTerraformを中核に据えたCI/CDである。開発者が日常的に使うリポジトリと、宣言的なインフラ定義を結びつけることで、変更はPull Requestで見える化され、審査、テスト、適用までが一気通貫になる。ここでは2025年の前提条件、つまりOIDC(OpenID Connect:短命トークンによる信頼フェデレーション)によるシークレットレス運用、ポリシー・アズ・コード(機械可読なルールでの自動審査)、ドリフト検知(コードと実環境の乖離検出)、コスト可視化までを含め、実装と運用の両面から具体的に解きほぐしていく[5,6,9,8]。あわせて2025年の視点として、マルチクラウドでのOIDCの標準化やFinOps(コスト最適化)のシフトレフトといった潮流も踏まえ、次の一手を示す。

戦略と全体設計:ブランチ戦略、環境、ステートの責務分離

まず構成の重心をどこに置くかを定める。アプリケーションのトランクベース開発を前提に、インフラ側はモノレポかレポ分割かをチーム単位の変更粒度で決めるとよい。組織間での権限境界を越える場合はレポジトリを分割し、同一チーム内のサービス群ならモノレポでモジュール管理すると、レビューと変更の整合が取りやすい[7]。環境はプレビュー、ステージング、本番の三層を基本とし、環境ごとにState(Terraformが管理対象リソースの実態と紐付けるための状態ファイル)とバックエンド(Stateの保管先)を分離して衝突を避ける。GitHubのEnvironment保護ルールを適用すれば、特定のブランチやレビュー承認なしに本番が変更されることはなくなる。Terraformのバックエンドは信頼できる一元管理が不可欠で、S3とDynamoDBの組み合わせやTerraform Cloudのリモートバックエンドを使うと、排他制御とロールバック可能性が担保される[4]。OIDCでGitHubからクラウドにフェデレーションすることで、長期鍵を保管しないシークレットレスの実行基盤に移行できる点も、2025年時点では前提としたい[5]。

GitHub Actions×OIDC×AWS:最小構成のワークフロー

Pull Requestでplan、mainへのマージでapplyという直感的な体験を、OIDCで安全に実現する[5]。以下は自己ホスト不要の最小実装で、プロバイダキャッシュと同時実行制御も含めている。planは変更内容のシミュレーション、applyは実適用と捉えると理解が早い。

name: terraform-ci
on:
  pull_request:
    paths:
      - 'infra/**'
  push:
    branches: [ main ]
    paths:
      - 'infra/**'
  workflow_dispatch: {}
permissions:
  id-token: write
  contents: read
  pull-requests: write
concurrency:
  group: terraform-${{ github.ref }}
  cancel-in-progress: false
jobs:
  plan:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-24.04
    defaults:
      run:
        working-directory: infra
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.5
      - uses: actions/cache@v4
        with:
          path: |
            ~/.terraform.d/plugin-cache
            infra/.terraform
          key: tf-${{ runner.os }}-${{ hashFiles('infra/**/*.tf*') }}
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-oidc-ci
          aws-region: ap-northeast-1
      - run: |
          set -euo pipefail
          terraform -chdir=. init -input=false -upgrade=false
          terraform -chdir=. validate
          terraform -chdir=. plan -input=false -no-color -out=tfplan
          terraform -chdir=. show -json tfplan > plan.json
      - name: Annotate PR
        run: python3 scripts/annotate_plan.py plan.json
  apply:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-24.04
    environment: production
    defaults:
      run:
        working-directory: infra
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.5
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-oidc-cd
          aws-region: ap-northeast-1
      - run: |
          set -euo pipefail
          terraform -chdir=. init -input=false -upgrade=false
          terraform -chdir=. apply -input=false -auto-approve

本番適用の前にEnvironmentの必須承認者を設定しておけば、変更はレビューを経ないと反映されない。concurrencyのグルーピングで並行applyを抑止しているため、競合でステートが破損する危険も避けられる。

Terraformのバックエンドとプロバイダ:堅牢なState管理

永続Stateは信頼の根であり、暗号化とロックが欠かせない。S3とDynamoDBでの構成を最初に決めておくと、後からの移行コストを抑えられる。以下は環境ごとにキーを分ける典型例である[4]。

terraform {
  backend "s3" {
    bucket         = "org-terraform-state"
    key            = "prod/network/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}
provider "aws" {
  region = var.aws_region
  assume_role {
    role_arn     = "arn:aws:iam::123456789012:role/terraform-prod"
    session_name = "tf-${var.environment}"
  }
}
variable "aws_region" { type = string }
variable "environment" { type = string }

Stateのキーをワークスペースや環境変数と組み合わせて分割すると、プレビュー環境でも本番のStateに干渉しない。暗号化はKMSを併用すれば監査線も明瞭になる。バックエンドの選定時点で運用の骨格が固まるため、設計段階での合意が重要だ。

実装の要所:モジュール化、品質ゲート、PR体験

大規模化を見越すなら、モジュール化と品質ゲートを早い段階から導入する。モジュール境界はチームの責務に一致させ、入出力を明確にするほど再利用性が高まる。品質ゲートは静的解析、ポリシー・アズ・コード、コストガードレールが三本柱になる[6]。PRは情報が過不足なく集約されていることが大切で、plan結果、ポリシー判定、コスト見積りが一画面で俯瞰できるとレビューの生産性は目に見えて上がる。

モジュールの基本形:入力と出力の明示

入出力が曖昧だと依存の連鎖が生まれる。変数のデフォルトと検証、出力のスコープを揃えるのが肝要だ[7]。

// modules/network/variables.tf
variable "vpc_cidr" {
  type        = string
  description = "VPC CIDR block"
  validation {
    condition     = can(cidrnetmask(var.vpc_cidr))
    error_message = "Invalid CIDR."
  }
}
variable "tags" {
  type        = map(string)
  default     = {}
  description = "Common tags"
}
// modules/network/main.tf
resource "aws_vpc" "this" {
  cidr_block = var.vpc_cidr
  tags       = merge(var.tags, { Name = "core-vpc" })
}
// modules/network/outputs.tf
output "vpc_id" {
  value = aws_vpc.this.id
}

この程度の粒度でも、PRで差分が明確に見える。検証に失敗した場合はplan前に弾かれるため、レビューアはより本質的な設計判断に集中できる。

ポリシー・アズ・コード:Regoでのガードレール

強制力のあるガードレールは、手作業のレビューに頼らない安心感をもたらす。OPA(Open Policy Agent)とConftestを用い、planのJSONを評価することで、タグ欠落や危険な公開設定を自動で拒否できる[6]。RegoはOPAのポリシー記述言語で、変更の許可・拒否の条件を宣言的に表現できる。

package terraform.analysis
violation[msg] {
  some r
  input.resource_changes[r]
  input.resource_changes[r].type == "aws_s3_bucket"
  input.resource_changes[r].change.after.acl == "public-read"
  msg := sprintf("S3 bucket %v is public", [input.resource_changes[r].name])
}
violation[msg] {
  some r
  rc := input.resource_changes[r]
  not rc.change.after.tags["Owner"]
  msg := sprintf("%v missing Owner tag", [rc.name])
}

ワークフローにはConftestの実行を挿入し、違反時はPRに注釈してマージをブロックする。ルールは監査証跡としても機能し、変更理由の説明責任を軽減する。

PR注釈の自動化:Pythonで差分を要約

planの全文を貼るとレビューは難しくなる。JSONを要約して変更件数、リソース種別、ガードレール違反の有無をコメントすれば、判断が速くなる。以下はPyGithubを用いる簡潔な実装である。

#!/usr/bin/env python3
import json
import os
import sys
from collections import Counter
from github import Github

def summarize(plan_json: str) -> str:
    data = json.loads(plan_json)
    changes = data.get("resource_changes", [])
    actions = Counter()
    kinds = Counter()
    for c in changes:
        act = "/".join(sorted(set(c.get("change", {}).get("actions", []))))
        actions[act] += 1
        kinds[c.get("type")] += 1
    lines = [
        f"Total resources changed: {sum(actions.values())}",
        "Actions: " + ", ".join(f"{k}={v}" for k, v in actions.items()),
        "Types: " + ", ".join(f"{k}={v}" for k, v in kinds.most_common(5))
    ]
    return "\n".join(lines)

def main():
    if len(sys.argv) < 2):
        print("usage: annotate_plan.py plan.json", file=sys.stderr)
        sys.exit(2)
    with open(sys.argv[1]) as f:
        body = summarize(f.read())
    token = os.environ.get("GITHUB_TOKEN")
    repo = os.environ["GITHUB_REPOSITORY"]
    pr_number = os.environ.get("PR_NUMBER") or os.environ.get("GITHUB_REF_NAME", "").replace("refs/pull/", "").split("/")[0]
    gh = Github(token)
    issue = gh.get_repo(repo).get_issue(int(pr_number))
    issue.create_comment(f"Terraform Plan Summary\n\n````\n{body}\n````")

if __name__ == "__main__":
    main()

GITHUB_TOKENの権限は最小限にし、レート制限とエラー時のリトライを必要に応じて追加すると堅牢性が増す。要約された指標はレビューの集中力を損なわず、重要な差分を見逃さない設計に寄与する。

セキュリティとガバナンス:シークレットレス、監査、コストの見える化

セキュリティとガバナンスは、導入段階で折り込むほど運用コストが小さくなる。OIDCでトークンを短命化し、アカウント側で信頼ドメイン、クレーム、条件を厳密に指定する[5]。監査は変更の起点がPRに集約されていれば容易で、誰がいつ何を変更したかはGitの履歴とワークフローの実行ログで完全に追跡できる。コストは設計意図と相互参照できる形でレビューに載せるのが望ましい。プレビュー段階で差分コストを提示できれば、本番適用前に費用の逸脱を抑えられる[8]。

Infracostの差分表示:PRでコスト逸脱を検知

コストの差分をPRに自動反映すると、設計段階での意思決定が速くなる。InfracostのCLIをワークフローに組み込み、APIキーはリポジトリシークレットから供給する[8]。

- name: Setup Infracost
  uses: infracost/actions/setup@v3
- name: Generate Infracost diff
  env:
    INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }}
  run: |
    infracost breakdown --path=infra --format=json --out-file=baseline.json
    git fetch origin main --depth=1
    infracost diff --path=infra --compare-to=baseline.json --format=github-comment --out-file=cost.md
- name: Comment cost
  run: gh pr comment $PR_NUMBER --body-file cost.md

差分がしきい値を超えた場合はステータスチェックを失敗させるようにすると、コスト逸脱の早期検知が制度として機能する。費用対効果の観点では、月次のチリツモを未然に抑えるこの仕組みが、導入初期から確かな便益をもたらす。

ドリフト検知と自動修復:定期planと軽量ガード

手作業や外部ツールによる変更は避けきれない。そこで定期的にplanを実行し、ドリフトを検知したらアラートや自動修復のフローにつなげる。以下は毎夜の検査を行う例である[9]。ドリフト検知は「コードが単一の真実である」前提を守るための最低限の保険だ。

name: drift-detection
on:
  schedule:
    - cron: '23 2 * * *'
permissions:
  id-token: write
  contents: read
jobs:
  drift:
    runs-on: ubuntu-24.04
    defaults:
      run:
        working-directory: infra
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
            terraform_version: 1.7.5
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-oidc-ci
          aws-region: ap-northeast-1
      - name: Plan with exit code
        run: |
          set -euo pipefail
          terraform init -input=false
          terraform plan -detailed-exitcode -no-color || code=$?
          if [ "${code:-0}" = "2" ]; then echo "Drift detected" && exit 1; fi

ドリフトは早期に見つけるほど是正コストが低い。運用ルールとして、コンソール変更の禁止だけでなく、検知と通知の仕組みで行動を支える設計が現実的だ[9]。

運用・パフォーマンス・ROI:計測と継続的最適化

CI/CDは作って終わりではない。パイプラインのボトルネックを継続的に計測し、成果をDORA指標に還元していく[1]。キャッシュ、並列実行、自己ホストランナーの導入は、適切な順序で施すと費用対効果が高まる。まずはプラグインとモジュールのキャッシュを有効化し、ワークフローのセットアップ時間を圧縮する[10]。その上でplanとテストを並列化し、applyは直列で安全に進める。自己ホストは最後の切り札として、リソース集約的なプロジェクトに限定するのが合理的だ。

参考ベンチマーク例:実行時間と最適化の寄与

一般的な規模感(数百リソース、十数モジュール程度)を想定した測定例では、初回実行のplanは数分台半ば、キャッシュ有効化後の再実行では2〜3割の短縮が見込める。applyは初回が数分台後半、差分が小さい場合は数分台前半まで短縮される傾向がある。自己ホストランナー(例:4 vCPU/8GB)を用いた場合、plan時間がさらに1〜2割短縮されるケースも観測される。プラグインキャッシュはplan時間のボトルネック(依存のダウンロード)を安定して圧縮する一方、並列度を上げすぎるとAPIレート制限やプロバイダのスロットリングに突き当たるため、PRごとの並列実行は2〜3本程度に抑え、applyは常に直列で行うと、全体の待ち時間と失敗率のトレードオフが良好になる。これらの数値は環境に強く依存するため、まずは自組織のパイプラインでベースラインを計測し、改善の寄与を定量化していくのが王道だ。

品質面では、PRにおけるplan要約、ポリシー判定、コスト差分の三点表示を揃えることで、レビューと承認のリードタイム短縮や変更失敗率の低下が期待できる。DORA指標に落とすと、リードタイム短縮と変更失敗率の両面で改善を狙いやすく、特にガードレール導入後はロールバック頻度の低下につながりやすい。重要なのは、計測と可視化をパイプラインに組み込み、改善サイクルを途切れさせないことだ。

エラー処理と回復性:失敗の仕方を設計する

インフラCI/CDは失敗しないことより、失敗しても安全であることが重要である。計画段階での失敗はPRステータスで止め、適用段階での失敗は部分適用の影響を理解した上での再実行が基本になる。スクリプトではset -euo pipefailで早期失敗を徹底し、ネットワーク不安定による一過性のエラーには指数バックオフの再試行を用意する。プロバイダのレート制限は適切なsleepと並列度の制御で回避し、Stateのロックが残留した場合はDynamoDBのロック解放手順を運用Runbookとして明文化する。こうした運用上の知恵が、結果的にはビジネス継続性のリスクを引き下げる。

最後に、GitHubの環境保護ルールとコードオーナーを併用すると、責任の所在が明確になり、レビューループの迷いが減る。ドキュメントはレポジトリ内のREADMEとADRに集約し、変更の意図と決定理由をPull Requestに添える運用にすると、属人性が時間とともに薄まる。仕組みが日常に溶け込んだとき、チームは本来の価値創出に集中できる。2025年以降は、OIDCの条件付きアクセスの高度化や、ポリシー違反の自動修復、コストのしきい値管理といった仕組みがさらに一般化していく。いま取り入れた基盤は、そのまま次の進化に接続できる。

まとめ:2025年の標準装備としてのインフラCI/CD

GitHub ActionsとTerraformを軸に据えたインフラCI/CDは、もはや一部の先進チームだけのものではない。OIDCでのシークレットレス運用、ポリシー・アズ・コードによるガードレール、PRでのplan要約とコスト差分、定期的なドリフト検知という要素が揃えば、変更は常に見える化され、失敗は早期に検出され、コストは逸脱しにくくなる。計測に基づく改善を続けることで、デプロイのリードタイムと変更失敗率という根本指標が着実に良化し、最終的には新機能の市場投入スピードが上がる[1]。

次の一歩は難しくない。最小のワークフローを作り、S3やTerraform CloudでStateを分離し、PRにplanとコストを表示するところまでを小さく始めてほしい。最初の成功体験がチームの標準を押し上げる。あなたの組織では、どの変更から自動化するのが最も価値が高いだろうか。明日のPRで、最初の一歩を刻んでみてほしい。

参考文献

  1. DevOps Research and Assessment (DORA). Accelerate: State of DevOps 2018. https://dora.dev/research/2018/dora-report/#:~:text=The%20report%20introduces%20the%20concept,times%20lower%20change%20failure%20rates
  2. HashiCorp. State of Cloud Strategy Survey 2022: Adoption and plans. https://2022.stateofthecloud.hashicorp.com/#:~:text=Even%20though%20the%20methodology%20was,cloud%20within%20two%20years
  3. HashiCorp. 9 Out Of 10 Say Multi-Cloud Is Working For Them. https://2022.stateofthecloud.hashicorp.com/#:~:text=9%20Out%20Of%2010%20Say,Cloud%20Is%20Working%20For%20Them
  4. HashiCorp Terraform Docs. S3 Backend (State storage and locking). https://developer.hashicorp.com/terraform/language/backend/s3?ref=last9-sre-platform#:~:text=Stores%20the%20state%20as%20a,variables
  5. GitHub Docs. Configuring OpenID Connect in Amazon Web Services for GitHub Actions. https://docs.github.com/de/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#:~:text=OpenID%20Connect%20,lived%20GitHub%20secrets
  6. Open Policy Agent. Policy for Terraform plans with OPA/Conftest. https://www.openpolicyagent.org/docs/terraform#:~:text=,after%20applying%20it%20to%20staging
  7. HashiCorp Blog. Terraform mono-repo vs multi-repo: the great debate. https://www.hashicorp.com/en/blog/terraform-mono-repo-vs-multi-repo-the-great-debate#:~:text=Monorepos%20work%20if%20you%20have,vpc
  8. Infracost Blog. Infracost diff for pull requests. https://www.infracost.io/blog/infracost-diff/#:~:text=Recently%20we%C2%A0released%C2%A0a%20new%C2%A0,understand%20some%20of%20the%20nuances
  9. HashiCorp Terraform Cloud. Drift detection and health assessments. https://developer.hashicorp.com/terraform/tutorials/cloud/drift-detection#:~:text=HCP%20Terraform%27s%20health%20assessments%20monitor,so%20you%20can%20resolve%20them
  10. GitHub Docs. Caching dependencies to speed up workflows. https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows?ref=dept-engineering-blog.ghost.io#:~:text=Workflow%20runs%20often%20reuse%20the,local%20cache%20of%20downloaded%20dependencies