GitLab CIでTerraformをいくつか動かしてるのですがGitLab CI独自の機能をうまく使うといい感じにCIができるようになったのでメモ
前提
- AWSの各アカウントをTerraformで管理している
- アカウントごとにTerraformのリポジトリを作成
- 1アカウント = 1リポジトリ
- 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 plan
と tflint
を自動実行しつつ、必要なら terraform apply
を手動実行
masterブランチでは terraform apply
を自動実行
Terraform実行専用のRunnerを作る
前述の設定で tags
に terraform
を書いてたやつです。
この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で排他ロックをかけるのがデファクトになってます。
しかしこれだと同時に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を動かしてた時によく起こってた排他ロックエラーが一切発生しなくなったのでおすすめです。