CI/CDパイプラインとTerraform — GitHub Actions連携例

1. 概要

  • TerraformとCI/CDを組み合わせる理由
  • GitHub Actions連携の実例
  • planをPRにコメントする設定
  • 環境ごとのdeploy戦略
  • セキュリティ上の注意点

手動でterraform applyを実行するワークフローは、レビューなしの変更・実行環境の差異・誰がいつapplyしたかのトレーサビリティ欠如、といった問題を生みます。CI/CDパイプラインに組み込むことでこれらを解消できます。


2. 基本的なGitHub Actionsワークフロー

# .github/workflows/terraform.yml
name: Terraform CI/CD

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  TF_VERSION: "1.9.0"
  AWS_REGION: "ap-northeast-1"

jobs:
  terraform:
    name: Terraform
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write  # PRへのコメント権限
      id-token: write       # OIDC認証用

    defaults:
      run:
        working-directory: environments/prd

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # AWS認証(OIDCで一時認証情報を取得 — シークレット不要)
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-terraform
          aws-region: ${{ env.AWS_REGION }}

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: Terraform Init
        run: terraform init

      - name: Terraform Format Check
        run: terraform fmt -check -recursive

      - name: Terraform Validate
        run: terraform validate

      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color -out=tfplan
        continue-on-error: true  # planが失敗してもコメントを投稿する

      # PRにplanの結果をコメント
      - name: Comment Plan Result
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const output = `#### Terraform Plan 📖
            \`\`\`
            ${{ steps.plan.outputs.stdout }}
            \`\`\`
            *Pusher: @${{ github.actor }}*`;
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

      # mainブランチへのマージ時のみapply
      - name: Terraform Apply
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: terraform apply -auto-approve tfplan

3. OIDCによるAWS認証設定

シークレットとしてアクセスキーを管理するのではなく、OIDCで一時認証情報を使います。

terraform {
  required_version = ">= 1.9"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

variable "environment" {
  description = "環境名"
  type        = string
  default     = "prd"
}

variable "github_org" {
  description = "GitHubオーガニゼーション名"
  type        = string
}

variable "github_repo" {
  description = "GitHubリポジトリ名"
  type        = string
}

# GitHub Actions用OIDCプロバイダー
resource "aws_iam_openid_connect_provider" "github" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = ["sts.amazonaws.com"]

  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]

  tags = {
    Name        = "github-actions-oidc"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

# GitHub Actions用IAMロール
resource "aws_iam_role" "github_actions_terraform" {
  name = "github-actions-terraform"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Federated = aws_iam_openid_connect_provider.github.arn
      }
      Action = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringEquals = {
          "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
        }
        StringLike = {
          "token.actions.githubusercontent.com:sub" = "repo:${var.github_org}/${var.github_repo}:*"
        }
      }
    }]
  })

  tags = {
    Name        = "github-actions-terraform"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

# Terraformに必要な権限(最小権限の原則)
resource "aws_iam_role_policy_attachment" "github_actions_terraform" {
  role       = aws_iam_role.github_actions_terraform.name
  policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
}

4. 環境ごとのdeploy戦略

# PRからdev自動deploy、mainマージでprd deploy
name: Terraform Multi-env

on:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  plan-dev:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: environments/dev
    steps:
      - uses: actions/checkout@v4
      - name: Plan Dev
        run: |
          terraform init
          terraform plan

  apply-dev:
    if: github.ref == 'refs/heads/develop'
    needs: plan-dev
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: environments/dev
    steps:
      - uses: actions/checkout@v4
      - name: Apply Dev
        run: |
          terraform init
          terraform apply -auto-approve

  apply-prd:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production  # GitHubの手動承認gate
    defaults:
      run:
        working-directory: environments/prd
    steps:
      - uses: actions/checkout@v4
      - name: Apply Prd
        run: |
          terraform init
          terraform apply -auto-approve

5. セキュリティ上の注意点

リスク対策
AWSアクセスキーの漏洩OIDCで一時認証情報を使う(シークレット不要)
state内のsensitive値S3バックエンド + バケット暗号化 + アクセス制限
誤ったprd環境へのapplyenvironment: productionでGitHubの手動承認を要求
planの結果が外部に漏洩PRコメントには概要のみ。詳細はCIログで確認

6. 関連記事


7. まとめ

  • GitHub ActionsでInit → fmt-check → validate → plan → applyの順で実行する
  • AWSの認証はOIDCで一時認証情報を使う(アクセスキーのシークレット管理は不要)
  • PRにplanをコメントしてレビューを促す
  • prd環境へのapplyはenvironment: productionで手動承認ゲートを設ける
  • stateはS3バックエンド + 暗号化 + アクセス制限で保護する

動作確認バージョン: Terraform >= 1.9 公式ドキュメント: https://developer.hashicorp.com/terraform/tutorials/automation/github-actions