TECHSTEP

ITインフラ関連の記事を公開してます。

GitLab CI/CDからTerraform / OpenTofuを実行する

今回はGitLabとTerraformを連携し、GitLab CI/CDからTerraform (OpenTofu) を利用する方法を紹介します。

docs.gitlab.com

背景

GitLabはTerraformと連携する以下のような機能を提供しています。

  • GitLabをTerraformのstateファイル保存場所として提供
  • GitLab CI/CDからTerraform/OpenTofuを利用するテンプレートを提供
  • Terraform module registryの提供
  • GitLab Terraform Helperの提供

今回は上記機能も使いつつ、GitLab CI/CDからTerraform/OpenTofuによるplan / applyを実行してみました。

検証

検証環境は以下の通りです。

素のTerraformをCI/CD Jobから呼び出す

GitLab CI/CDからTerraformコマンドを実行する方法は複数ありますが、まずはTerraformコマンドを含んだコンテナイメージを使い実行する方法を試してみました。

まず、今回使ったTerraformファイルを紹介します。

backend.tf

terraform {
  backend "http" {
    address="https://gitlab.com/api/v4/projects/<Project ID>/terraform/state/default"
    lock_address = "https://gitlab.com/api/v4/projects/<Project ID>/terraform/state/default/lock"
    unlock_address = "https://gitlab.com/api/v4/projects/<Project ID>/terraform/state/default/lock"
    lock_method = "POST"
    unlock_method = "DELETE"
    retry_wait_max = 5
  }
}

main.tf

terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

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

resource "aws_s3_bucket" "test" {
  bucket = "terraform-gitlab-test-20240311"
}

main.tfは特に説明不要かと思うので、ここではbackend.tfについて補足します。

今回はTerraform stateの管理場所にGItLabを選択しました。GitLabをTerraform state管理場所として使うには、terraform initで以下のようなコマンドを実行する必要があります。

export GITLAB_ACCESS_TOKEN=<YOUR-ACCESS-TOKEN>
export TF_STATE_NAME=default
terraform init \
    -backend-config="address=https://gitlab.com/api/v4/projects/<Project ID>/terraform/state/$TF_STATE_NAME" \
    -backend-config="lock_address=https://gitlab.com/api/v4/projects/<Project ID>/terraform/state/$TF_STATE_NAME/lock" \
    -backend-config="unlock_address=https://gitlab.com/api/v4/projects/<Project ID>/terraform/state/$TF_STATE_NAME/lock" \
    -backend-config="username=fy0323" \
    -backend-config="password=$GITLAB_ACCESS_TOKEN" \
    -backend-config="lock_method=POST" \
    -backend-config="unlock_method=DELETE" \
    -backend-config="retry_wait_min=5"

上記コマンドはGitLab Project画面から 操作Terraformステータス と移動しても確認できます。

これをGitLab CI/CDから実行するため、backend.tfに秘匿情報以外を記載しておき、initコマンドのオプションから秘匿情報を渡します。

次に .gitlab-ci.yml は以下の通りです。

variables:
  GITLAB_ACCESS_TOKEN: $GITLAB_ACCESS_TOKEN
  GITLAB_USERNAME: $GITLAB_USER_LOGIN
  TF_ROOT: ${CI_PROJECT_DIR}

stages:
  - init
  - plan
  - apply

default:
  image: 
    name: hashicorp/terraform:1.7
    entrypoint: [""]
  cache:
    key: "${TF_ROOT}"
    paths:
      - ${TF_ROOT}/.terraform/**
      - ${TF_ROOT}/.terraform
      - ${TF_ROOT}/.terraform.lock.hcl

init:
  stage: init
  script:
    - > 
      terraform init \
        --backend-config="username=${GITLAB_USERNAME}" \
        --backend-config="password=${GITLAB_ACCESS_TOKEN}"
  artifacts:
    when: always

plan:
  stage: plan
  needs:
    - init
  script:
    - terraform plan
  artifacts:
    when: always

apply:
  stage: apply
  needs:
    - init
    - plan
  script:
    - terraform apply -auto-approve
  when: manual

いくつか補足します。

  • variables: GITLAB_ACCESS_TOKEN は、あらかじめ取得したAccess tokenをCI/CD変数に設定します。 GITLAB_USER_LOGIN CI_PROJECT_DIR は定義済みの変数 (Predefined variables) です。
  • default: ここではHashiCorpの提供するTerraformコンテナイメージを使用しています。また terraform init 実行後に生成されるファイルを後段で使うため cache の設定をしています。
  • init: 変数に設定したユーザー名・Access tokenを使い、初期化を行います。
  • plan: 前段の init stageで生成されたファイルを使うため needsinit stageを指定します。
  • apply: plan と同じような設定ですが、Jobは手動実行のみ許可しています。

なお、上記3つのファイルを配置する前に、GitLab・AWSへの認証を有効にするため、CI/CD変数を設定しておきます。

変数を設定後、ファイルを配置してパイプラインを起動すると、plan stageが成功する様子を確認できます。手動実行で apply stageを起動すると、そちらも成功しました。

実行ログを確認すると、plan/applyの様子を確認できます。

一応AWS側も確認しますが、想定通りのS3バケットが作成されていました。

OpenTofu componentを使う

GitLabはTerraform / OpenTofuを利用するためのtemplate / CI/CD Catalogを提供しています。ただしTerraformのほうはGitLab 18.0で廃止予定のため、今回はOpenTofuのほうを使います。

OpenTofuのCI/CD Catalogは以下に配置されています。

gitlab.com

OpenTofuで利用したファイルは以下の通りです。ここでもTarraform stateの管理先はGitLabとします。先ほどと比べ、 backend.tf .gitlab-ci.yml の記述量が少なく、シンプルであることがわかります。

backend.tf

terraform {
  backend "http" {
  }
}

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

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

resource "aws_s3_bucket" "test" {
  bucket = "terraform-gitlab-test-20240330"
}

.gitlab-ci.yml

include:
  - component: gitlab.com/components/opentofu/validate-plan-apply@~latest
    inputs:
      version: latest
      opentofu_version: 1.6.1

stages:
  - validate
  - build
  - deploy

fmt:
  stage: validate

plan:
  stage: build

apply:
  stage: deploy

上記ファイルに加え、CI/CD変数の設定もしておきます。

変数設定後にパイプラインを実行すると、以下のようなJobが実行されます。ここでも apply は手動実行をトリガーにしています。

apply も実行後にログを見ると、 gitlab-tofu というコマンドが各処理を実行していることが分かります。

この gitlab-tofu の実体はこちらのShell scriptであり、 gitlab-opentofu というコンテナイメージに含まれる tofu バイナリが処理を行う中心です。

参考情報