CircleCIでTerraform plan/applyを自動化する方法

1. 概要

  • CircleCI でTerraformのCI/CDを構築する手順
  • CircleCI OrbのTerraform実行
  • OIDC認証(CircleCI → AWS)
  • ワークフローの設計(plan/approve/apply)

CircleCIは独立したCI/CDサービスです。GitLabやGitHubと組み合わせて使います。CircleCI専用のTerraform Orbがあり、最小限の設定でTerraformを実行できます。


2. Terraform設定

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

  backend "s3" {
    bucket         = "my-company-tfstate"
    key            = "app/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-lock"
  }
}

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

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

data "aws_ami" "amazon_linux_2023" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  tags = {
    Name        = "${var.environment}-web"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

3. CircleCI設定(.circleci/config.yml)

# .circleci/config.yml
version: 2.1

orbs:
  aws-cli: circleci/aws-cli@4

jobs:
  terraform-plan:
    docker:
      - image: hashicorp/terraform:1.9.0
    steps:
      - checkout
      - aws-cli/setup:
          role_arn: arn:aws:iam::123456789012:role/CircleCIRole
          region: ap-northeast-1
      - run:
          name: Terraform Init
          command: terraform init
      - run:
          name: Terraform Validate
          command: terraform validate
      - run:
          name: Terraform Plan
          command: terraform plan -no-color -out=tfplan | tee /tmp/plan.txt
      - store_artifacts:
          path: /tmp/plan.txt
          destination: terraform-plan
      - persist_to_workspace:
          root: .
          paths:
            - tfplan

  terraform-apply:
    docker:
      - image: hashicorp/terraform:1.9.0
    steps:
      - checkout
      - aws-cli/setup:
          role_arn: arn:aws:iam::123456789012:role/CircleCIRole
          region: ap-northeast-1
      - attach_workspace:
          at: .
      - run:
          name: Terraform Init
          command: terraform init
      - run:
          name: Terraform Apply
          command: terraform apply tfplan

workflows:
  terraform:
    jobs:
      - terraform-plan:
          filters:
            branches:
              only: /.*/   # 全ブランチでplan
      - hold:
          type: approval       # 手動承認ゲート
          requires:
            - terraform-plan
          filters:
            branches:
              only: main       # mainブランチのみapply
      - terraform-apply:
          requires:
            - hold
          filters:
            branches:
              only: main

4. CircleCI OIDCのIAMロール設定

resource "aws_iam_openid_connect_provider" "circleci" {
  url             = "https://oidc.circleci.com/org/${var.circleci_org_id}"
  client_id_list  = ["${var.circleci_org_id}"]
  thumbprint_list = ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"]

  tags = {
    Name        = "circleci-oidc-provider"
    Environment = "dev"
    ManagedBy   = "terraform"
  }
}

variable "circleci_org_id" {
  description = "CircleCIオーガニゼーションID"
  type        = string
}

resource "aws_iam_role" "circleci" {
  name = "CircleCIRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Federated = aws_iam_openid_connect_provider.circleci.arn }
      Action    = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringLike = {
          "oidc.circleci.com/org/${var.circleci_org_id}:sub" = "org/${var.circleci_org_id}/project/*"
        }
      }
    }]
  })

  tags = {
    Name        = "CircleCIRole"
    Environment = "dev"
    ManagedBy   = "terraform"
  }
}

resource "aws_iam_role_policy_attachment" "circleci" {
  role       = aws_iam_role.circleci.name
  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

5. 関連記事


6. まとめ

  • CircleCIではholdタイプのjobで手動承認ゲートを設けてからapplyを実行できる
  • persist_to_workspace / attach_workspaceでplanの出力をapplyジョブに引き渡す
  • CircleCI OIDCを使うとAWSアクセスキーなしで認証できる
  • 2024年現在、新規プロジェクトではGitHub Actionsが主流。CircleCIは既存ユーザー向け

対象バージョン: Terraform >= 1.9 / CircleCI (2024) 公式ドキュメント: https://circleci.com/developer/orbs/orb/circleci/terraform