こんにちは。 SRE の @suzuki-shunsuke です。
100 個以上の Terraform state がある Monorepo で Terraform を v0.14 に upgrade しつつ Terraform Provider の自動 update を実現した方法を紹介したいと思います。 Terraform v0.14 の新機能とかにはあまり触れず、 upgrade するために何をしたかという点にフォーカスします。 Terraform 公式のドキュメントも参照してください。
なお、弊社の Terraform の CI/CD に関しては下記の記事も参照してください。
quipper.hatenablog.com quipper.hatenablog.com
Terraform v0.13 から v0.14 への update では、 Terraform のコード(*.tf)の変更はほとんど必要ありませんでした。
terraform 0.14upgrade
のようなコマンドも提供されていません。
ただし、 lock ファイル .terraform.lock.hcl が導入されています。
そのため、 terraform init
を実行し、 lock ファイルを生成する必要があります。
また、幾つか deprecation warning にも対応しました。 例えば、 provider configuration block の中で provider のバージョンを指定しないようにする必要がありました。
provider "aws" { region = "ap-northeast-1" max_retries = 3 version = "3.31.0" }
Warning: Version constraints inside provider configuration blocks are deprecated on provider.tf line 4, in provider "aws": 4: version = "3.31.0" Terraform 0.13 and earlier allowed provider version constraints inside the provider configuration block, but that is now deprecated and will be removed in a future version of Terraform. To silence this warning, move the provider version constraint into the required_providers block.
代わりに required_providers で指定するようにしました。
terraform { required_version = ">= 0.14" required_providers { aws = { source = "hashicorp/aws" version = "3.31.0" } } }
弊社の場合 state が 110 個程度あり、全部手作業で対応するのは辛いので、簡単なスクリプトを書いて terraform init
の実行を自動化しました。
まず対象の state のパスのリストを 10 個ずつ程度にファイルに分けておきます。 そのファイルを list.txt とします。
予め terraform.tfstate*
, .terraform
を消しておきます。
$ cat list.txt | xargs -I{} rm -Rf {}/terraform.tfstate* {}/.terraform
PR 用に default branch から branch を予め切っておきます。 そしてスクリプトを実行してコミットを追加していきます。
#!/usr/bin/env bash set -eux set -o pipefail while read -r target; do cp provider.tf versions.tf .terraform-version "$target" pushd "$target" terraform init git add . git commit -m "chore: update Terraform of $target" popd done < list.txt
あとは PR を投げるだけです。これを 10 回くらい繰り返しました。
Renovate による Terraform Provider update の際に lock ファイルの更新とマージを自動化
Renovate の Tips - スタディサプリ Product Team Blog でも紹介したように、
Terraform Provider の update を Renovate で行い、
terraform plan
の実行結果が no change
であれば Renovate によって automerge され、
そうでなければ CI を失敗させるようにしています。
しかし lock ファイルが導入されたことにより、 lock ファイルも更新しないといけなくなりました。
required_providers
のバージョン指定だけ更新して terraform init
すると失敗します。
$ terraform init Error: Failed to query available provider packages Could not retrieve the list of available versions for provider hashicorp/aws: locked provider registry.terraform.io/hashicorp/aws 3.28.0 does not match configured version constraint 3.29.0; must use terraform init -upgrade to allow selection of new versions
Go module を Renovate で update する場合、 go mod tidy
を自動実行できますが *1、
Terraform の lock ファイルはそうもいきません。
Renovate をセルフホストしていれば postUpgradeTasks を設定して出来るかもしれませんが、
自分たちはセルフホストしていないのでそれも出来ません。
そこで Renovate で Terraform provider が update された場合、 CI で自動的に terraform init -upgrade
を実行し、更新された lock ファイルをブランチに push するようにしました。
Renovate では PR の label にテンプレート変数が使えます*2。
https://docs.renovatebot.com/templates/
そこで次のような label を設定します。
"labels": [ "datasource:{{datasource}}" ]
Terraform Provider が update されると datasource:terraform-provider
という label がセットされます。
自動で terraform init -upgrade
を実行するコードは次のようになります。
以下のツールを使っています
root_dir=$PWD cd "$STATE_DIR" if [ "$CI_INFO_PR_AUTHOR" = "renovate[bot]" ] && grep "datasource:terraform-provider" "$CI_INFO_TEMP_DIR/labels.txt"; then if ! terraform init -input=false; then # we have to run `terraform init -upgrade` to update `.terraform.lock.hcl` terraform init -input=false -upgrade ghcp commit -u "$CI_INFO_REPO_OWNER" -r "$CI_INFO_REPO_NAME" -b "$CI_INFO_HEAD_REF" \ -m "chore(terraform-provider): terraform init -upgrade" \ -C "$root_dir" "$STATE_DIR/.terraform.lock.hcl" exit 1 fi else terraform init -input=false fi
GitHub の automerge 機能を活用
こうすることで自動で lock ファイルを更新することが出来るようになりましたが、まだ問題があります。
Renovate が作成した PR に対してコミットを追加すると、 Renovate の automerge
が有効になっていても、自動でマージしてくれなくなります。
これはドキュメントを読んでもそのような記述は見られないので自分の勘違いかもしれませんが、そのように思われます。
そこで、 terraform plan
の結果が no change
であれば自動でマージされるようにしました。
GitHub の automerge 機能を使います。
リポジトリの設定で Allow auto-merge
を有効化する必要があります。
GitHub CLI の gh pr merge コマンドを使い automerge を有効化することで、 CI が終わったら勝手に merge されます。
--auto
をつけずに即マージしようとすると、 Required status check が pending の場合に失敗するので、 --auto
を指定しています。
弊社の設定の場合、 merge には 1 approve 必要ですが、そこは renovate-approve が approve してくれるので問題ありません。
automerge を有効化する際には、安全な場合のみ automerge するように if 文で制限しています。
if [ "$CI_INFO_PR_MERGED" = "false" ] && [ "$CI_INFO_PR_AUTHOR" = "renovate[bot]" ] && [ "$(bash ci/list-all-updated-states.sh | wc -l)" -eq 1 ]; then echo "===> This pull request would be merged automatically" >&2 # --auto option is needed because if other platform's CI has not been finished it would fail to merge the pull request. # > Required status check "AWS CodeBuild ap-northeast-1 (sapuri-terraform)" is pending. gh pr merge --merge --auto "$CI_INFO_PR_NUMBER" || : fi
- PR がマージされていない
- PR の author が Renovate
- PR で対象になっている state が 1 つだけ
- Monorepo になっており複数の state の CI が同時に実行されうるので、 1 つだけの場合のみ automerge
- 基本的に Renovate の PR では複数の state の CI は同時に走らないようになっているので、この制約があっても問題にならない
- plan の結果が no change (これは if 文に反映されていませんが、そもそも
no change
でなければここに来ないようになっている)
こうすることで、 Renovate の PR にコミットを追加している場合でも安全に自動で PR をマージできるようになりました。 Renovate の automerge 機能はマージされるまでに数時間かかることもよくあるのが不満だったのですが、このやり方だと即時にマージされるため、とても快適になりました。
最後に
以上、 Terraform を v0.14 に upgrade するさいに何をしたか説明しました。
特に Renovate を使って .terraform.lock.hcl
を自動で更新するやり方や、 GitHub の automerge 機能を活用して自動マージするやり方は参考になればと思います。
*2:使えるようにしたのは自分です https://github.com/renovatebot/renovate/pull/8138