Rebounder Tech Blog

運用している当事者が書く、本番システムの記録。

terraform の enabled = false では環境を撤去できない

公開 読了時間 約4分執筆: Rebounder 開発チーム(当該システムの運用当事者)

※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。

結論

terraform のモジュールに持たせた enabled フラグはモジュール内部の count を切るだけで、module 呼び出し自体は常に評価されるため、出力を参照している側は null を受け取り、環境まるごとの撤去には使えない。

結論

環境を畳むなら destroy を使います。enabled = false への一括書き換えでは撤去できません。

モジュールに enabled のようなフラグを持たせている構成はよくあります。フラグでリソースの生成を止められるなら、環境全体を畳むときも全部 false にすれば済みそうに見えます。済みません。

enabled が切っているのはモジュール内部の count だけです。module 呼び出し自体は常に評価されます。リソースが消えても出力は定義されたまま残り、値が null になります。その null が、参照している側へそのまま流れます。

症状

環境の全モジュールを enabled = false にして plan を流すと、こう出ます。

Error: Invalid template interpolation value

出方に特徴があります。

  • エラーが出るのは、無効化したモジュールではなく参照している側
  • **参照している数だけ同時に出る。**1つ直しても次が出るのではなく、最初から全部出る
  • 落ちるのは設定を評価している段階で、リソースは1つも消えていない

実際に、レジストリを持つモジュールを1つ落としたところ、その URL を文字列補間していた 13 モジュールが同時に落ちました。切ったのは1つです。

原因

enabled フラグの実装は、たいていこの形です。

resource "google_artifact_registry_repository" "this" {
  count = var.enabled ? 1 : 0
  # ...
}

count = 0 になるとリソースは作られません。ここまでは意図どおりです。

問題はその外側です。module "artifact_registry" { ... } という呼び出しそのものは、enabled が何であっても評価されます。 モジュールの output も定義されたまま残ります。中身のリソースが存在しないので、値だけが null になります。

enabled = true   → image_repo_url = <レジストリの URL>
enabled = false  → image_repo_url = null   ← 定義は残る。値だけ消える

参照側は、その出力を文字列に埋め込んでいます。

image = "${module.artifact_registry.image_repo_url}/<image>:${var.image_tag}"

null を文字列に埋め込もうとした時点で Invalid template interpolation value です。terraform は「この値は文字列にできない」と言っているだけで、enabled の話は一言も出てきません。エラーメッセージから原因のモジュールへは辿れません。

なぜ「全部 false にする」が通りそうに見えるか

モジュール単位で見ると、この操作は正しく動きます。依存の葉にあるモジュールなら、false にしても誰も参照していないので何も起きません。 検証もそこで済ませがちです。

壊れるのは、依存の根に近いモジュールを落としたときだけです。そして環境まるごとを畳む操作は、必ず根を含みます。個別に試したときの成功体験が、そのまま裏切られます。

直す

destroy を使います。

terraform -chdir=infrastructure/terraform/envs/<env> destroy

destroy は state にあるリソースを削除する操作で、参照関係を壊しません。コードは有効なまま、実体だけが消えます。enabled を触る必要はありません。

畳んだあとにコードを残す場合は、enabled を false に書き換えるのではなく、そのルートを二度と apply しないという状態にしておきます。書き換えてしまうと、次に誰かが plan を流したときに同じエラーに当たります。

再発しない形にする

手順書には書きました。ただしそれだけでは届きません。撤去は年に何度もやる操作ではないので、そのとき手順書を開くとは限らないからです。

そこで置き場所を増やしました。撤去しようとした人が必ず開くファイル、つまり畳む対象のルート main.tf の冒頭に、同じことを3行で書いてあります。

  • 撤去は destroy で行う(コマンドをそのまま)
  • enabled = false の一括書き換えでは撤去できない(試して失敗した事実つき)
  • 復活させる場合の入口(依存順の apply と、コードに無い実値の再投入が要ること)

手順書は「探しに行く人」にしか届きません。ルートの main.tf は、撤去しようとしている人が必ず開きます。届く場所に置くほうが、正しい手順書を書くより効きます。

enabled フラグそのものは残しています。**個別モジュールの休眠には使えるからです。**使えないのは環境まるごとの撤去だけで、フラグ側の問題ではありません。休眠と撤去は別の操作だと切り分けておけば、この形は踏まずに済みます。

同じ「エラーメッセージが原因を指していない」形の話として、本番だけ SECURITY DEFINER 関数が効かないのは実行ロールのせい も書いています。あちらはエラーすら出ませんでした。

よくある質問

Q1enabled = false と terraform destroy は何が違いますか?

enabled = false はモジュール内部の count を 0 にしてリソースの生成を止めるだけで、module 呼び出し自体は評価され続けます。出力は定義されたまま null になり、参照している側に null が流れます。destroy は state にあるリソースを実際に削除する操作で、参照関係を壊さずに環境を畳めます。撤去したいなら destroy を使います。

Q2Invalid template interpolation value はなぜ出るのですか?

文字列補間の中に null が入ると出ます。この形では、無効化したモジュールの出力を参照している側が null を受け取り、それを文字列に埋め込もうとした時点で落ちます。エラーは補間している側のファイルで出るので、原因になったモジュールとは別の場所に出ます。参照している数だけ同時に出ます。

Q3enabled フラグ自体が設計として間違っているのですか?

用途が違うだけです。個別のモジュールを一時的に休ませる用途では機能します。使えないのは環境まるごとの撤去で、依存の根に近いモジュールを落とすと、その出力を参照している側が連鎖して落ちます。休眠と撤去は別の操作だと切り分けておくと迷いません。

Q4撤去したあとに元へ戻せますか?

コードから戻せる範囲と戻せない範囲があります。terraform の管理下にあるリソースは依存順に apply すれば作り直せますが、シークレットの実値のようにコードに入っていないものは再投入が要ります。撤去を不可逆な操作として扱い、何を手で入れ直す必要があるかを先に書き出しておくのが安全です。