GitLab CI/CDでTerraform plan/applyを自動化する方法

1. 概要

  • GitLab CI/CDでTerraformのplan/applyを自動化する方法
  • GitLab CI/CD Catalog(公式Terraform Component)の活用
  • OIDCを使ったAWS認証
  • GitLab Environmentsでのデプロイ追跡

GitLabはGitHub Actionsと同様にCI/CDが組み込まれています。特にセルフホスト版GitLabを使っているチームでは、GitLab CI/CDで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. GitLab CI/CD設定(.gitlab-ci.yml)

# .gitlab-ci.yml
image: hashicorp/terraform:1.9.0

variables:
  TF_ROOT: ${CI_PROJECT_DIR}/environments/production
  AWS_DEFAULT_REGION: ap-northeast-1
  # OIDCを使う場合は以下を設定
  # AWS_ROLE_ARN: arn:aws:iam::123456789012:role/GitLabCIRole

stages:
  - validate
  - plan
  - apply

# GitLab CI/CDのOIDC認証(ID Tokens)
default:
  id_tokens:
    AWS_OIDC_TOKEN:
      aud: sts.amazonaws.com

.aws_oidc_login: &aws_oidc_login
  before_script:
    - apk add --no-cache aws-cli jq
    - |
      CREDS=$(aws sts assume-role-with-web-identity         --role-arn "$AWS_ROLE_ARN"         --role-session-name "GitLabCI-${CI_JOB_ID}"         --web-identity-token "$AWS_OIDC_TOKEN"         --duration-seconds 3600         --output json)
      export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r '.Credentials.AccessKeyId')
      export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')
      export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r '.Credentials.SessionToken')

validate:
  stage: validate
  script:
    - cd $TF_ROOT
    - terraform init -backend=false
    - terraform validate
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

plan:
  stage: plan
  <<: *aws_oidc_login
  script:
    - cd $TF_ROOT
    - terraform init
    - terraform plan -no-color -out=tfplan 2>&1 | tee plan_output.txt
  artifacts:
    name: terraform-plan
    paths:
      - $TF_ROOT/tfplan
      - $TF_ROOT/plan_output.txt
    expire_in: 7 days
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

apply:
  stage: apply
  <<: *aws_oidc_login
  script:
    - cd $TF_ROOT
    - terraform init
    - terraform apply tfplan
  environment:
    name: production
    url: https://my-app.example.com
  dependencies:
    - plan
  when: manual    # 手動実行
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

4. GitLab OIDC IAMロール設定

resource "aws_iam_openid_connect_provider" "gitlab" {
  url             = "https://gitlab.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["b3dd7606d2b5a8b4a13771dbecc9ee1cecafa38a"]

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

resource "aws_iam_role" "gitlab_ci" {
  name = "GitLabCIRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Federated = aws_iam_openid_connect_provider.gitlab.arn }
      Action    = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringEquals = {
          "gitlab.com:aud" = "sts.amazonaws.com"
        }
        StringLike = {
          "gitlab.com:sub" = "project_path:my-group/my-repo:ref_type:branch:ref:main"
        }
      }
    }]
  })

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

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

5. 関連記事


6. まとめ

  • GitLab CI/CDは.gitlab-ci.ymlでTerraformのvalidate → plan → applyパイプラインを定義する
  • GitLab OIDC(ID Tokens)を使うとAWSアクセスキーなしで認証できる
  • planのArtifactsをapplyジョブに引き渡すことで、planしたままのtfplanをapplyできる
  • when: manualでapplyを手動実行にしてPRマージ後の意図しないapplyを防止する

対象バージョン: Terraform >= 1.9 / GitLab CI/CD (2024) 公式ドキュメント: https://docs.gitlab.com/ee/user/infrastructure/iac/