🛠️

Terraform 1.7.0 のリリースを読んでみた (1.7.1 もあるよ)

2024/01/29に公開

こんにちは、クラウドエース SRE 部の阿部です。
この記事では、2024 年 1 月 17 日にリリースされた Terraform 1.7.0 の変更点についてざっくり説明します。
また、1 月 24 日に Terraform 1.7.1 がリリースされておりますので、合わせて紹介します。

概要

2024 年 1 月 17 日にリリースされた Terraform 1.7.0 がリリースされました。
Terraform 1.6.6 から以下のような変更がありました。

v1.6.6 からの主な変更点

  • Terraform 1.3 から実装された Check ブロックを実装しているときに、 terraform_remote_state を使って State を参照するとエラーになる問題を修正
    • Terraform 1.7.0 だけでなく、 Terraform 1.3.10 、Terraform 1.4.7 、Terraform 1.5.7 でも修正されています。Terraform 1.6 系は影響がないため対象外です。
  • nonsensitive 関数で sensitive 属性がない値を設定した際にエラー扱いしていた動作を、エラー扱いしないよう変更
  • terraform graph で表示される依存関係の対象をリソースのみにするよう変更
  • terraform test で削除されるリソースの順序を run ブロックの逆順として単純化
  • terraform test でモックを使ったテストを実装 (mock_provider, override_resource, override_data, override_module を追加)
  • removed ブロックを追加
  • import ブロックで for_each 引数を追加

上記以外にも、細かな機能追加やバグ修正があります。詳しくは Terraform 1.7.0 のリリースノートを参照してください。

v1.7.0 から v1.7.1 の修正点

基本的にはバグフィックスのみです。

  • terraform testvariables ブロックの変数や関数を参照した際にクラッシュする問題を修正
  • terraform testoverride_module ブロックに outputs 属性が不足している場合にクラッシュする問題を修正

詳細は Terraform 1.7.1 のリリースノートを参照してください。

個人的な注目機能

個人的に注目したい以下の新機能や機能追加について、解説したいと思います。

  • terraform graph の仕様変更
  • terraform test のモック機能
  • removed ブロック
  • import ブロックの for_each 引数

また、この記事では解説はしませんが terraform_remote_state を使っている方は、動作が改善されている各マイナーバージョンの最新にアップデートしておくことをおすすめします。

terraform graph の仕様変更

terraform graph の表示が非常にシンプルになりました。 v1.6.6 以前は Root Module から始まり、 Terraform 内部の依存関係グラフをほぼそのまま表示しているような状態で、Terraform 自体の動作のデバッグには役立つものの、何かの資料で流用できるようなものではありませんでした。
v1.7.0 以降では、リソースのみに限定して、極めて分かりやすい出力に変更されています。以下は、同じソースコードから v1.6.6 と v1.7.1 で terraform graph の出力した依存関係グラフを示します。また、参考までに Pluralith での出力結果も示します。※どれくらいグラフがシンプルになったかを見て頂きたいだけなので、ソースコードは省略します。

terraform graph 1.6.6
Terraform 1.6.6 の terraform graph

terraform graph 1.7.1
Terraform 1.7.1 の terraform graph

Pluralith sample
Pluralith の出力結果

こうして比較してみると、一目瞭然ですね。さらに言うと、 Pluralith はモジュールを考慮しませんが、 Terraform 1.7 ではモジュールとリソースの包含関係も考慮して出力します。
グラフの出力結果に、リソースの一部属性(例えば、 namezone といったもの)も合わせて出力できると、詳細設計書等に添付してもよいくらいの情報量になりそうです。

terraform test のモック機能

terraform test の大きな追加として、モックが使えるようになりました。
terraform testplan モードと apply モードの 2 種類があり、 plan の情報でテストするか、実際に apply して適用された結果を元にテストするか、という動作でした。
plan モードは、主に読み取り系 API のみで検証するため現状のリソースには影響を与えませんが、リソース設定後に暗黙で他のパラメータも変更されるようなケースでは厳密なテストができません。
一方で apply モードは実際にリソースを作成して検証するため、確実性の高いテストが実施できますが、実際にリソース作成するため時間もお金もかかります。さらに、テスト用リソースが既存のリソースと重複してしまう場合ではテストに失敗します。
モックを上手く使うことで、 apply モード的なテストを疑似リソースで代行し、 plan モードより確実性の高いテストが行える…… というものだと思います。

筆者が少し触ってみた感じでは、まだモックを使ったサンプルが少なく mock_provider をはじめとする各ブロックでどのような設定を行うと、 plan モードと apply モードのいいとこどりができそうか、わかりませんでした。(一応、動いたな、くらいの感触)
モック機能を使いこなすにはもう少しキャッチアップが必要そうです。モック機能については今後様々な情報が出てくることも期待し、 Google Cloud 上でテストする上でよりよい使い方がわかれば記事にしていきたいです。

removed ブロック

removed ブロックは、ソースコード上で削除設定を記述することで terraform state rm を安全に実行できるというものです。
terraform state rm の操作はそんなに難しくないですが、 システム運用の安全性を高めるために Remote State ファイルへのアクセスを制限しているケースがあると、おいそれと実行できません。
CI/CD のパイプライン上で安全に実行できればそれにこしたことはありませんので、なんらかの事情で Terraform 管理外にしたいリソースがあるが、 terraform state rm 実行はなるべくやりたくないといった場合に有用だと思います。

import ブロックの for_each

import ブロックで for_each メタ引数が使えるようになりました。
例えば、以下のようなソースコードがあったとします。

main.tf
main.tf
locals {
  instances = [
    {
      name = "test-alpha"
      zone = "asia-northeast1-a"
    },
    {
      name = "test-bravo"
      zone = "asia-northeast1-b"
    },
    {
      name = "test-charlie"
      zone = "asia-northeast1-c"
    }
  ]
}

resource "google_compute_instance" "test" {
  for_each     = { for v in local.instances : format("%s/%s", v["zone"], v["name"]) => v }
  name         = each.value["name"]
  zone         = each.value["zone"]
  machine_type = "e2-medium"

  boot_disk {
    auto_delete = true
    initialize_params {
      image = "debian-cloud/debian-11"
    }
  }

  network_interface {
    network    = "default"
    subnetwork = "default"
  }
}

なお、このソースコードで boot_disk ブロックで initialize_params を記述している部分、および、 network_interfacedefault をリテラル文字列で記述している部分は、説明のため簡略化する目的であり、推奨するコーディング方法ではありません。

このソースコードで terraform apply を実行すると、以下 3 つのインスタンスが起動します。

  • test-alpha
  • test-bravo
  • test-charlie

このような for_each を使ったソースコードで import ブロックを記述する場合、 v1.6.6 以前は以下のような書き方になってしまいます。

main_import.tf (v1.6.6)
main_import.tf
import {
  to = google_compute_instance.test["asia-northeast1-a/test-alpha"]
  id = format("%s/%s/%s", var.project_id, local.instances[0]["zone"], local.instances[0]["name"])
}

import {
  to = google_compute_instance.test["asia-northeast1-b/test-bravo"]
  id = format("%s/%s/%s", var.project_id, local.instances[1]["zone"], local.instances[1]["name"])
}

import {
  to = google_compute_instance.test["asia-northeast1-c/test-charlie"]
  id = format("%s/%s/%s", var.project_id, local.instances[2]["zone"], local.instances[2]["name"])
}

これは明らかにイケてないですね。私もそう思います。
さらに言えば、 v1.6.6 までは、 import ブロックの to 引数には変数(localvariable)が指定できません。
そのため、 for_each で指定するリストやマップの要素数だけ import ブロックを記述し、to 引数はリテラル文字列を使う必要がありました。

これが、v1.7.0 以降は以下のように記述できます。

main_import.tf (v1.7.0)
main_import.tf
import {
  for_each = { for v in local.instances : format("%s/%s", v["zone"], v["name"]) => v }
  to       = google_compute_instance.test[each.key]
  id       = format("%s/%s/%s", var.project_id, each.value["zone"], each.value["name"])
}

はい、最高です。 for_each 引数のおかげで 1 つのブロックに集約できました。
また、for_each 引数に指定する内容は main.tfgoogle_compute_instance.test と合わせるだけですので、一貫性もあります。
これから import ブロックを使って既存リソースを Terraform 管理下に置くことを検討している方は、積極的に v1.7 以降を使用していきましょう。

まとめ

この記事では、 Terraform 1.7.0 で更新された機能について紹介しました。
IaC をする上で便利な機能が盛り込まれており、積極的にアップデートして実務でも使っていきたいところです。
この記事が、Terraform を利用する方のお役に立ちましたら幸いです。

Discussion