Terraform バージョンアップグレードの手順 — 互換性の約束、no-op 検証、ロールバック備え
Terraform CLI のバージョンアップグレードは先送りしやすく、先送りするほど差が積もって怖くなる種類の作業です。結論を先に書くと、1.x 内のマイナーアップグレードは互換性の約束のおかげで重くなく、核心は 3 つの順序です。チェンジログの確認、非本番スタックでの no-op plan 検証、そしてチーム・CI のバージョン統一です。 この記事の執筆時点の安定バージョンは 1.15 で、1.16 がベータ段階です(2026-08 基準、導入時点で最新を確認)。CLI とは別軸の provider メジャーアップグレードは次の記事で別に扱います。
前提 — 1.x 互換性の約束 #
Terraform 1.0 からは互換性の約束(v1.x Compatibility Promises)が適用されます。1.x の範囲内では既存の構成ファイルと基本のワークフローが動き続けるよう維持されるという公式の約束で、0.x 時代のようにマイナーバージョンごとに文法が変わりマイグレーションツールを回していた時代とは違います。したがって 1.x のマイナーアップグレードは順番に踏む必要がなく、飛ばしても構いません。 1.11 から 1.15 に直接上げても構成が壊れないのが原則です。
約束がカバーしないものもあります。実験的な機能、文書化されていない挙動、そして deprecated と予告された機能の最終的な削除です。だから手順の最初がチェンジログの確認です。飛ばすバージョンの CHANGELOG で removed と deprecated の項目だけ流し読みしても、地雷は大部分が見つかります。最近のバージョン帯の実質的な変化(ネイティブ S3 ロックの use_lockfile、ephemeral 値など)は基礎 #9 リモート state と協業で扱ったとおり、むしろアップグレードの理由になる機能です。
アップグレードの手順 — no-op が合格基準です #
- チェンジログを確認します: 現在のバージョンから目標バージョンまでの removed・deprecated 項目を見ます。自分のコードで使っている機能が引っかかるかが確認ポイントです。
- 非本番スタックで先に上げます: 開発環境のスタックで新バージョンの
terraform planを実行します。合格基準は「No changes」です。 コードもインフラも変えていないのにバージョンだけ上げて差分が出たら、原因を理解するまで進行を止めます。 - plan 出力の警告を読みます: 新バージョンが出す deprecation 警告は次のアップグレードの宿題リストです。今回直せば次が軽くなります。
- スタックごとに段階適用します: 重要度の低いスタックから本番まで順に上げ、各スタックで同じ基準の no-op plan を確認します。
- required_version を更新します: 完了後、
required_version = "~> 1.15.0"のように下限と範囲を新バージョンに固定し、旧バージョンの CLI がうっかり実行されるのを遮断します。
ここで CLI のアップグレードと provider のアップグレードを混ぜないことが重要です。terraform init -upgrade は provider をロックファイルの範囲内で最新に上げるコマンドなので、CLI のバージョンを上げるついでに習慣で実行すると、2 つの変数が同時に変わって差分の原因を特定できなくなります。 CLI を先に上げて no-op を確認し、provider は別の PR で上げる分離が原則です。
ロールバックの備え — state バージョンの一方通行 #
戻す計画も用意すべき理由は state にあります。新しい CLI が state を書くとファイル内のバージョン情報が更新され、場合によっては旧バージョンの CLI がその state を読めません。 つまりチームの半分が 1.15 で apply を始めた後には、1.13 に自然に戻れない状況が生まれます。
備えは 2 つです。1 つ目は S3 バックエンドのバージョニングの確認です。ロールバックが必要になったら、新バージョンが書く前の state オブジェクトのバージョンに戻すのが復旧の道筋で、これは tfstate 削除からの復旧で扱ったのと同じバージョニング依存です。2 つ目は、アップグレードを短い期間にチーム全体で一緒に渡るイベントにすることです。数週間も新旧バージョンが混ざって動く状態が最も危険です。
チーム・CI の統一 — バージョンはファイルで固定します #
ローカルだけ上げて CI が旧バージョンなら、ローカルで書いた state を CI が読めない事故が、まさに上のシナリオどおりに起きます。バージョンは人の記憶ではなくファイルで固定します。
- required_version: 構成レベルの強制ラインです。範囲を外れた CLI は init から拒否されます。
- tfenv または mise: ローカルのバージョン管理ツールです。リポジトリに
.terraform-versionファイルをコミットしておけば、チェックアウトした全員が同じバージョンを選択します。mise を使えば Terraform 以外のツールのバージョンまで 1 つのファイルで管理できます。 - CI イメージ・セットアップアクションのバージョン明示: GitHub Actions なら setup-terraform のバージョン入力を
.terraform-versionと同じ値に合わせます。latest にしておくと、CI がある日先に上がってしまいます。
アップグレード作業自体も、これらのファイルを変える PR 1 つで表現されます。.terraform-version、required_version、CI 設定が 1 つのコミットで一緒に変われば、レビュアーは範囲を一目で把握できます。
まとめ #
- 1.x 互換性の約束のおかげで、マイナーアップグレードは順番踏みではなく飛ばしが可能です。確認するのはチェンジログの removed・deprecated です。
- 合格基準は no-op plan です。バージョンだけ変えたのに差分が出たら、原因を理解するまで止めます。
- CLI と provider のアップグレードは分離します。init -upgrade を習慣で混ぜると差分の原因を特定できません。
- 新バージョンが書いた state は旧バージョンが読めないことがあります。S3 バージョニングの確認と短い移行期間がロールバックの備えです。
- バージョンは required_version、.terraform-version(tfenv/mise)、CI 設定の 3 つのファイルで固定し、アップグレードはこれらを変える 1 つの PR にします。