くりにっき

フルスタックキュアエンジニアです

GitLab CIでTerraformを動かす

GitLab CIでTerraformをいくつか動かしてるのですがGitLab CI独自の機能をうまく使うといい感じにCIができるようになったのでメモ

前提

  • AWSの各アカウントをTerraformで管理している
  • アカウントごとにTerraformのリポジトリを作成
  • masterブランチ以外では terraform plan を実行し、masterブランチで terraform apply を実行
    • terraform plan があまり信用できないから別アカウントへの terraform apply のパターンもあるかもしれないですが、 terraform plan の方が基本形かなと思ってます

ベースになるジョブをincludeで別リポジトリから読み込むと便利

includeとはGitLab 11.4にcoreに入った機能で、外部のCI設定を .gitlab-ci.yml に取り込む機能です。

https://docs.gitlab.com/ee/ci/yaml/#include

他のCIサービスでいうとCircleCIのorbsが一番近いです。

include導入前

includeを使う前は各リポジトリに下記のような .gitlab-ci.yml が点在していました。

.terraform: &terraform
  image:
    name: hashicorp/terraform:0.12.5
    entrypoint: [""]

  before_script:
    - terraform init -input=false

  tags:
    - terraform

stages:
  - test
  - deploy

cache:
  key: "${CI_JOB_NAME}"
  untracked: true
  paths:
    - .terraform/

tflint:
  <<: *terraform

  stage: test

  variables:
    TFLINT_VERSION: "0.9.2"

  script:
    # tflintのDockerイメージを使うとterraform initできなくてmoduleが読めないためバイナリをDLする
    - cd /tmp
    - wget https://github.com/wata727/tflint/releases/download/v${TFLINT_VERSION}/tflint_linux_amd64.zip
    - unzip tflint_linux_amd64.zip

    - ./tflint

  tags:
    - docker

terraform-plan:
  <<: *terraform

  stage: test

  script:
    - terraform plan -input=false

  except:
    - master

terraform-apply:
  <<: *terraform

  stage: deploy

  script:
    - terraform apply -input=false -auto-approve

  only:
    - master

terraform-apply-manual:
  <<: *terraform

  stage: deploy

  script:
    - terraform apply -input=false -auto-approve

  except:
    - master

  when: manual

2箇所くらいならまだ許せたのですが4つ超えたあたりで厳しい感じになりました

include導入後

先日会社のGitLabをバージョンアップしたので includeを使ってリファクタリングしました。

まずは下記のようにテンプレートファイルのみを用意したリポジトリを用意

variables:
  TERRAFORM_VERSION: ""
  TFLINT_VERSION:    ""

.terraform:
  image:
    name: hashicorp/terraform:${TERRAFORM_VERSION}
    entrypoint: [""]

  before_script:
    - terraform init -input=false

  tags:
    - terraform

stages:
  - test
  - deploy

cache:
  key: "${CI_JOB_NAME}"
  untracked: true
  paths:
    - .terraform/

tflint:
  extends: .terraform

  stage: test

  script:
    # tflintのDockerイメージを使うとterraform initできなくてmoduleが読めないためバイナリをDLする
    - cd /tmp
    - wget https://github.com/wata727/tflint/releases/download/v${TFLINT_VERSION}/tflint_linux_amd64.zip
    - unzip tflint_linux_amd64.zip

    - ./tflint

  except:
    - master

  tags:
    - docker

terraform-plan:
  extends: .terraform

  stage: test

  script:
    - terraform plan -input=false

  except:
    - master

terraform-apply:
  extends: .terraform

  stage: deploy

  script:
    - terraform apply -input=false -auto-approve

  only:
    - master

terraform-apply-manual:
  extends: .terraform

  stage: deploy

  script:
    - terraform apply -input=false -auto-approve

  except:
    - master

  when: manual

Terraformリポジトリでは下記のように別リポジトリのtemplateを呼び出すだけで使えるようになります

include:
  - project: "group/gitlabci-terraform-template"
    file:    "/template.yml"

variables:
  TERRAFORM_VERSION: "0.12.5"
  TFLINT_VERSION:    "0.9.2"

メリット

  • CIの設定を一箇所に集約したことにより、利用側ではTerraformとtflintのバージョンだけ渡すだけで使えるようになる

上記templateでやってること

masterブランチ以外では terraform plantflint を自動実行しつつ、必要なら terraform apply を手動実行

f:id:sue445:20190920235810p:plain

masterブランチでは terraform apply を自動実行

f:id:sue445:20190920235819p:plain

Terraform実行専用のRunnerを作る

前述の設定で tagsterraform を書いてたやつです。

このTerraform専用のRunnerですが、下記のようにlimitを1にしたdocker executorを作っています。

[[runners]]
  name = "terraform"
  executor = "docker"
  limit = 1

https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section

limit = 1 にする理由

AWSでTerraformを使う場合にはbackendをs3にしつつ、DynamoDBで排他ロックをかけるのがデファクトになってます。

www.terraform.io

しかしこれだと同時に2つのブランチをpushした場合に後から実行された方が排他ロックエラーになってジョブが失敗します。

Terraformを触る人数が多いとこの手のエラーはしょっちゅう起こるし、1人しか触ってない場合でもトピックブランチをpush後にミスに気づいてforce pushしたような場合にもエラーになります。

それを防ぐためにRunnerを limit = 1 にする(つまり複数のジョブの同時実行をさせない)ことでジョブが確実に1つずつ実行されるようになり、さっきのような排他ロックエラーを回避できます。(planの時だけロックかけないってのも頑張ればできそうだけど怖いのでやったことはない)

ちなみにジョブの並列実行数を意図的に制限するというのは僕が知る限りGitLab CIでしか実現できない機能です。(少なくともTravis CI, CircleCI, Wercker, GitHub Actionsには存在しないです)

このTerraform専用Runnerはここ1年くらいずっと使っていますが、CircleCIでTerraformを動かしてた時によく起こってた排他ロックエラーが一切発生しなくなったのでおすすめです。