はじめに
terraformで管理しているリソースが増えてきて、tfstateが肥大化してくると、terraform planがどんどん遅くなり、待ってる間にインターネッツを眺めがちです。
根本解決は、適度な粒度にtfstate分割ですが、もっとお手軽な小手先のテクニックとして、 terraform plan --parallelism の紹介記事を以前書きました。
ただこの方法は、環境によって効果が出たり出なかったり、確実な方法ではありませんでした。
そんなあなたからインターネッツを眺める言い訳を奪ってしまい大変恐縮ではありますが、Terraform v1.17以降では、terraform plan -minimal-refresh というフラグが生えており、これによりrefresh範囲を最小化してplanを高速化できるようになります。
※本稿執筆時点では、Terraform v1.17はまだベータ版です。
そもそもterraform planの仕組みについて少しだけ補足しておくと、terraform planは前回保存したtfstateとtfファイルの差分を検出していると思われがちですが、実際には前回保存したtfstateと実リソースのドリフトを検出するためにrefreshというフェーズがあり、refreshしたメモリ上のtfstateとtfファイルの差分を検出しています。Terraform管理外でリソースをいじってドリフトが発生していた場合、refreshフェーズでの差分の検出は「Objects have changed outside of Terraform」というような見え方でplan上も区別され、デグレに気づけます。
しかしながら、tfstateが肥大化してくると、このrefreshフェーズが重いので、 terraform plan -refresh=false で、多少のリスクを許容してrefreshを無効化するというハックもありました。
新しく追加された terraform plan -minimal-refresh は、全リソースを無条件にrefreshせず、前回保存したtfstateを基準にtfファイルと差分のあるリソースを特定してからピンポイントにrefreshして、refresh対象を更新するリソースに絞ることでドリフトによるデグレのリスクを回避しつつ、planを高速化します。
というわけで、前置きが長くなりましたが、さっそく試してみた。
環境
手元の環境は以下のとおりです。
- Terraform v1.17.0-beta1
- Terraform AWS Provider: v6.63.0
本稿執筆時点では、Terraform v1.17はまだベータ版なのでご注意下さい。
やってみた
呼び出すAPIやネットワーク構成などに依存するので、時間の計測結果は参考値程度ですが、
とりあえず検証するのに手頃なサイズ感かつ、refreshが遅そうなRoute53のリソースが200個ぐらいあるtfstateで試してみた。
比較のため、まずはTerraform v1.16.2で、普通にterraform planして時間を計測しておきます。手元の環境では50秒ぐらいかかりました。
$ terraform --version
Terraform v1.16.2
on darwin_arm64
+ provider registry.terraform.io/hashicorp/aws v6.63.0
$ time terraform plan
xxx: Refreshing state... [id=xxx]
xxx: Refreshing state... [id=xxx]
xxx: Refreshing state... [id=xxx]
xxx: Refreshing state... [id=xxx]
(snip.)
No changes. Your infrastructure matches the configuration.
terraform plan 5.40s user 1.43s system 13% cpu 49.698 total
状態によって結果のばらつきがあるので、一応参考までにTerraform v1.17.0-beta1の素の状態も計測しておきます。45秒ぐらいでした。手元の環境だと、Route53のAPIはこれぐらいのブレはあるようです。
$ terraform --version
Terraform v1.17.0-beta1
on darwin_arm64
+ provider registry.terraform.io/hashicorp/aws v6.63.0
$ time terraform plan
xxx: Refreshing state... [id=xxx]
xxx: Refreshing state... [id=xxx]
xxx: Refreshing state... [id=xxx]
xxx: Refreshing state... [id=xxx]
(snip.)
No changes. Your infrastructure matches the configuration.
terraform plan 5.98s user 1.50s system 16% cpu 45.118 total
次に、本題の terraform plan -minimal-refresh を試します。
まずはコードを1行もいじらない場合。
$ time terraform plan -minimal-refresh
No changes. Your infrastructure matches the configuration.
terraform plan -minimal-refresh 3.16s user 0.53s system 103% cpu 3.549 total
諸般の事情で生ログが貼れないんですが、「Refreshing state...」のログが出ず、3〜4秒ぐらいで終わりました。爆速じゃん。
次に、適当にtfファイル側のリソース定義をいじって、1リソースだけdiffが出るようにしてみます。
$ time terraform plan -minimal-refresh
xxx: Refreshing state... [id=xxx]
(snip.)
Plan: 0 to add, 1 to change, 0 to destroy.
terraform plan -minimal-refresh 3.18s user 0.54s system 91% cpu 4.058 total
4秒で終わりました。爆速じゃん。
該当の1リソースだけ「Refreshing state...」が出ていました。
一応挙動が気になったので、あるリソースに変更があった場合、依存するリソースにもrefreshが波及するかも試してみた。
$ time terraform plan -minimal-refresh
xxx: Refreshing state... [id=xxx]
xxx: Refreshing state... [id=xxx]
xxx: Refreshing state... [id=xxx]
(snip.)
Plan: 21 to add, 0 to change, 21 to destroy.
terraform plan -minimal-refresh 3.55s user 0.61s system 79% cpu 5.206 total
21リソース差分が出ていますが、5秒で終わりました。
ログの出力例は長くなるので省略していますが、依存するリソースも含めて影響を受ける21リソース分「Refreshing state...」が出ていました。ちゃんとしている。
デフォルトで有効にしたい場合は、環境変数 TF_CLI_ARGS_plan に設定して下さい。
export TF_CLI_ARGS_plan="-minimal-refresh"
applyの場合は、環境変数 TF_CLI_ARGS_apply です。
おわりに
Terraform v1.17で追加される、 terraform plan -minimal-refresh を試してみました。
Terraform管理外のドリフトによるデグレのリスクを回避しつつ、planを高速化する現実的なバランスを取った選択肢として、よいかんじではないでしょうか。
-minimal-refresh と通常のrefreshの使い分けは検討の余地がありそうですが、たとえば、手元の普段遣いを -minimal-refresh にしつつ、CI/CDはちゃんと通常のplanを回すとか、あるいはCI/CDを -minimal-refresh で高速化したいなら、日次でmainブランチの全リソースを通常のrefreshありのplanでドリフトチェックしておくなど、各自で適当な落としどころを見つけて下さい。