📛

tfmigrate と terraform import/removed/moved block の使い分け

2024/03/21に公開

はじめに

trocco SRE チームの高塚 (@tk3fftk) です。

Terraform の state 操作は基本的に CLI で行う必要があり、即座に state に反映されてしまうため、CI ツール上で行うのが煩雑になる、ローカルで試していたつもりがうっかり本番の state を触ってしまっていた…などの課題があったかと思います。

この隙間を埋めてくれるのが @minamijoyo 氏の minamijoyo/tfmigrate です。
state 操作を非破壊的に検証・レビュー可能にし、かつ CI にも組み込みやすく[1]、という(少なくとも個人的には)神ツールです。

一方、Terraform 1.7 より removed block が利用可能になりました。
そのため、terraform plan/apply 経由での state 操作において頻出の import/rm/mv が出揃ったことになります。
tfmigrate からこっちに切り替えたほうがもしかして良いのでは…? この前の LT [2] でも 「ネイティブの機能を使おう」って自分で言ってたし…

ということで、trocco SRE チームでは、ここ数ヶ月間で大量の state 操作とレビューする機会[3]があり、tfmigrate と Terraform ネイティブの state 操作 blocks (以降は単に blocks と記載します[4]) の両方を使ってみたうえでの使い分けに関する考えや運用方法を綴ってみます。

前提として、trocco SRE チームでは suzuki-shunsuke/tfaction を利用して Terraform 実行ワークフローを組んでいます。

TL;DR

  • 安全に state を操作したいなら tfmigrate を使うべき。
    • state 操作後の状態と Terraform 設定ファイルの状態が異なると失敗扱いにしてくれる。
    • trocco SRE チームでは基本的に tfmigrate を使いましょう、とルールを定めました。
  • import/removed/moved と設定の修正を同時に行ったほうが効率や見通しが良いなら terraform 組み込みの state 操作を利用すると便利
    • blocks が意図通りに解釈されていることを目視確認する必要があるため、plan 結果のレビューは複雑になる。
    • 操作終了後の blocks はその後も対象リソースを操作した場合にも読み込まれるので副作用が起こる可能性あり。

tfmigrate

概要

@minamijoyo 氏の minamijoyo/tfmigrate は、state 操作を非破壊的に検証・レビュー可能にする、かつ操作履歴の保存が可能となります。

設計思想や使い方は GitHub リポジトリや作者のTerraformのstate操作をgitにコミットしたくてtfmigrateというツールを書いた- Qiitaを読むのがわかりやすいと思います。

pros

  • state 操作後の状態と設定ファイルにズレがあったら失敗扱いにしてくれるため安全に state 操作ができる。
    • 手で作成してきたリソースを大量に import するケースや、 module 化のタイミングで大量に stete mv のような Pull Request をレビューするケースで 「tfmigrate plan 通ってるからヨシ!」ができるので助かる。
  • state 操作の定義方法が、以下のようにほぼ terraform コマンドそのままリストに列挙するだけなので学習コストが低い。
    • Copilot Chat など LLM にも書いてもらいやすい (気がする)
migration "state" "test" {
  actions = [
    "mv aws_security_group.foo aws_security_group.foo2",
    "mv aws_security_group.bar aws_security_group.bar2",
  ]
}

cons

  • 仕組み上実行時間が長くなってしまう。
  • .tf ファイルではないのでエディタによっては補完が効かせにくい。
  • (主観ですが) 列挙されたコマンドたちを目 grep, 目 diff するのがつらい。
    • 何かしら typo だったり、指定ミスをやりがち😇

Terraform blocks

概要

この記事における blocksimport, removed, moved block の総称としています。
それぞれの block について、何をするのか、相当するコマンド、利用可能になったバージョン、公式ドキュメントのリンクを表にまとめました。

block どのような操作か 相当するコマンド version 公式ドキュメント
import 既存のリソースを terraform の state 管理下に入れる terraform import v1.5+ Import
removed リソースを terraform の state 管理下から外す terraform state rm v1.7+ Resources
moved terraform の state 管理上のリソースアドレスを変える terraform state mv v1.1+ Refactoring

以下、サンプルコードです。

  • import
import {
  to = aws_instance.example
  id = "i-abcd1234"
}
  • removed
removed {
  from = aws_instance.example

  lifecycle {
    destroy = false # true にするとリソース自体の削除も行う
  }
}
  • moved
moved {
  from = aws_instance.old_name
  to   = aws_instance.new_name
}

pros

  • state 操作と対象のリソースの修正を同時に行える。
    • 例えば、import しつつ、aws provider の default-tags で付与される tag の追加など。
      • tfmigrate だと手で AWS 側の差分を解消するか、 force をつけて通したあとに別途 apply する必要がある。
  • Terraform のコードとして書ける。
    • CLI と HCL のコンテキストスイッチが不要
    • エディタ補完も効きやすい。
  • tfmigrate と比較すると高速

cons

  • blocks が意図通りに解釈されていることを目で確認する必要がある。
    • 例えば、10 リソース import したい場合、定義が仮に漏れている場合でも plan は成功するため、 import block が漏れなく書かれていることを plan 結果から目で確認する必要があります。
  • blocks をどこに定義していくべきか問題がある。
    • import に関しては公式ドキュメントで imports.tf というファイル名が言及されています[5] が、これに合わせて moved.tf removed.tf 的な感じで3ファイル作るべきか…?
      • trocco SRE チームでは state_migrate.tf というファイル名にしています。
  • blocks が state 操作が済んだあとも解釈されてしまう。
    • 例えば、 import block で import したあと、そのリソースが不要となって tf ファイルから削除して plan 実行するとエラーになります。
      • import block も合わせて削除する必要あり
      • 削除以外の state 操作に関しても同様
    • 削除するフローとした場合、いつのタイミングで消してもノイズ感がある。
      • 後続の任意の Pull Request に混ぜるのか、削除だけの Pull Request を出すのか、意図せぬふるまいをし始めたら消すのか…

まとめ

  • tfmigrateblocks の pros/cons を並べてみました。
  • terraform 1.7 の現時点では、行いたい操作に応じて使い分けるのが良さそうです。
    • ようやく公式が追いついてきましたが、 tfmigrate は神ツール

脚注
  1. minamijoyo/tfmigrate: A Terraform / OpenTofu state migration tool for GitOps - Why? ↩︎

  2. 緊急SOS!KubernetesのCompletedな10万Jobぜんぶ消す - Speaker Deck ↩︎

  3. Terraform Modules で trocco の国際化に対応する ↩︎

  4. 公式の総称がない気がする。どなたか知ってたら教えてください… ↩︎

  5. A common pattern is to create an imports.tf file, or to place each import block beside the resource block it imports into.

    ↩︎
株式会社primeNumber

Discussion