マージリクエストでのTerraformインテグレーション

Infrastructure as Code (IaC)の変更に関するコラボレーションでは、コードの変更と予想されるインフラの変更の両方をチェックし、承認する必要があります。GitLabはマージリクエストページを使って、Terraformのコード変更とその期待される効果に関するコラボレーションを支援するソリューションを提供します。これにより、ユーザーはカスタムツールを構築したり、IaCワークフローを合理化するためにサードパーティのソリューションに頼る必要がなくなります。

Terraform Plan情報をマージリクエストに出力します。

GitLab Terraform Reportアーティファクトを使うと、terraform plan の実行の詳細をマージリクエストウィジェットに直接公開することができ、Terraformが作成、変更、破棄したリソースに関する統計を見ることができます。

caution
他のジョブアーティファクトと同様に、Terraform Planのデータはリポジトリのゲストロールを持つ人なら誰でも見ることができます。TerraformもGitLabもデフォルトではプランファイルを暗号化しません。Terraform Planにパスワード、アクセストークン、証明書などの機密データが含まれている場合は、プラン出力を暗号化するか、プロジェクトの可視性設定を変更することを強くお勧めします。

Terraformレポートアーティファクトの設定

GitLabは、GitLabが管理するTerraformの状態を使用し、マージリクエストにTerraformの変更を表示するCI/CDテンプレートを通してTerraformとインテグレーションします。ビルド済みのイメージをカスタマイズし、gitlab-terraform ヘルパーを利用して素早くセットアップすることをお勧めします。

GitLab Terraform Reportアーティファクトを手動で設定するには:

  1. 簡単にするために、これらのファイルを複数回参照できるように再利用可能な変数をいくつか定義しておきましょう:

    variables:
      PLAN: plan.cache
      PLAN_JSON: plan.json
    
  2. 軽量で柔軟なコマンドライン JSON プロセッサーであるjq をインストールします。
  3. terraform plan 出力から抽出したい情報を解析する、特定のjq コマンドのエイリアスを作成します:

    before_script:
      - apk --no-cache add jq
      - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
    
    note
    Bashを使用するディストリビューション(Ubuntuなど)では、alias ステートメントは非対話モードでは展開されません。convert_report: command not foundというエラーでパイプラインが失敗する場合、スクリプトにshopt コマンドを追加することで、明示的にエイリアス展開を有効にできます:
    before_script:
      - shopt -s expand_aliases
      - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
    
  4. terraform plan およびterraform showを実行するscript を定義します。これらのコマンドは出力をパイプし、関連するビットをストア変数PLAN_JSON に変換します。このJSONは、GitLab Terraformレポートのアーティファクトを作成するために使われます。Terraform レポートは Terraformtfplan.json ファイルを取得します。収集されたTerraformプランレポートはアーティファクトとしてGitLabにアップロードされ、マージリクエストに表示されます。

    plan:
      stage: build
      script:
        - terraform plan -out=$PLAN
        - terraform show --json $PLAN | convert_report > $PLAN_JSON
      artifacts:
        reports:
          terraform: $PLAN_JSON
    

    ビルド済みイメージを使った完全な例については、Example.gitlab-ci.yml fileを参照してください。

    複数のレポーターを表示する例については、.gitlab-ci.yml 複数のレポーター ファイルを参照してください。

  5. パイプラインを実行すると、次のようにマージリクエストのウィジェットが表示されます:

    merge request Terraform widget

  6. ウィジェットの [View Full Log] ボタンを選択すると、パイプラインログにあるプラン出力に直接アクセスできます:

    Terraform plan logs

.gitlab-ci.yml ファイルの例

default:
  image: registry.gitlab.com/gitlab-org/terraform-images/stable:latest
  cache:
    key: example-production
    paths:
      - ${TF_ROOT}/.terraform
  before_script:
    - cd ${TF_ROOT}

variables:
  TF_ROOT: ${CI_PROJECT_DIR}/environments/example/production
  TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/example-production

stages:
  - prepare
  - validate
  - build
  - deploy

init:
  stage: prepare
  script:
    - gitlab-terraform init

validate:
  stage: validate
  script:
    - gitlab-terraform validate

plan:
  stage: build
  script:
    - gitlab-terraform plan
    - gitlab-terraform plan-json
  artifacts:
    name: plan
    paths:
      - ${TF_ROOT}/plan.cache
    reports:
      terraform: ${TF_ROOT}/plan.json

apply:
  stage: deploy
  environment:
    name: production
  script:
    - gitlab-terraform apply
  dependencies:
    - plan
  when: manual
  only:
    - master

複数のTerraformプランレポーター

GitLab バージョン 13.2 から、マージリクエストページに複数のレポートを表示できるようになりました。レポートにはartifacts: name: も表示されます。 以下の設定例を参照してください。

default:
  image:
    name: registry.gitlab.com/gitlab-org/gitlab-build-images:terraform
    entrypoint:
      - '/usr/bin/env'
      - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
  cache:
    paths:
      - .terraform

stages:
  - build

.terraform-plan-generation:
  stage: build
  variables:
    PLAN: plan.tfplan
    JSON_PLAN_FILE: tfplan.json
  before_script:
    - cd ${TERRAFORM_DIRECTORY}
    - terraform --version
    - terraform init
    - apk --no-cache add jq
  script:
    - terraform validate
    - terraform plan -out=${PLAN}
    - terraform show --json ${PLAN} | jq -r '([.resource_changes[]?.change.actions?]|flatten)|{"create":(map(select(.=="create"))|length),"update":(map(select(.=="update"))|length),"delete":(map(select(.=="delete"))|length)}' > ${JSON_PLAN_FILE}
  artifacts:
    reports:
      terraform: ${TERRAFORM_DIRECTORY}/${JSON_PLAN_FILE}

review_plan:
  extends: .terraform-plan-generation
  variables:
    TERRAFORM_DIRECTORY: "review/"
  # Review will not include an artifact name

staging_plan:
  extends: .terraform-plan-generation
  variables:
    TERRAFORM_DIRECTORY: "staging/"
  artifacts:
    name: Staging

production_plan:
  extends: .terraform-plan-generation
  variables:
    TERRAFORM_DIRECTORY: "production/"
  artifacts:
    name: Production