約1,600リソースのTerraformモノリスを、22個のstateに分割しました。terraform state mv ではなくゼロからコードを書き直し、import blockで既存リソースを引き取る「フルリライト + import」方式です。AIコーディングエージェント(Claude Code)の活用で、当初見積もり8週間の作業が1週間で完了しました。
「ビズリーチ・キャンパス」のインフラ開発を担当しているyoshidaです。本記事ではその移行戦略を共有します。肥大化したIaCのリソースの構成を見直したい方、AIをインフラ領域で活用する事例を探している方の参考になれば幸いです。
移行前の構成と課題
移行前は1つのstateに全リソースが入っており、環境切り替えは terraform workspace で検証用 (staging) と本番用 (prd) を管理していました。main.tf には54個のmoduleブロックが並び、互いのoutputsを直接参照することで密結合した状態になっていました。
|
|
この構成は以下の課題を抱えていました。
planが全リソースを毎回評価するため遅い- 1サービスの変更でも全体のstate lockを取るため、他チームの変更と衝突してロック待ちが発生する
- モジュール間の暗黙的な依存が
main.tf内に閉じており、AIコーディングエージェントを含めた他チームメンバーが触る際に影響範囲を把握しにくい
なぜ state mv ではなくフルリライトか
state mv 自体に問題があるわけではありません。今回フルリライトを選んだのは、主にチーム事情によるところが大きいです。
- 各チームの開発を止めたくなかった — 各チームがインフラまでを含めた機能を自律的に開発するようになり、加えてAIの活用も進んだことで、Terraformを触る機会が増えていました。移行のために開発を長期間止めるリスクをなるべく小さくしたかったのです。
- 段階的に進めにくい —
state mvは移動した瞬間から旧stateのplanに差分が出るため、中間状態が不安定で長期の段階移行には向きません。 - やりながら理想をブラッシュアップしたい — 例えばmonitoring層は、走り出した後で「独立させた方が良い」と気づいて切り出しました。フルリライトなら新stateを捨ててやり直せるので、構成を柔軟に変えられます。
そこで採用したのは、次の4ステップを各スタックで繰り返すアプローチです。
- 新しいディレクトリに理想の構成でゼロからコードを書く
importblockで既存のAWSリソースを新しいstateに取り込むterraform planが importのみ・add/change/destroyゼロ(以降「変更差分ゼロ」と呼びます)であることを確認してapply- 旧stateは一切触らず並行運用し、全て完了後にまとめて廃止
最大のメリットは旧stateに一切触れないことです。移行作業中に本番環境が壊れるリスクがありません。
補足すると、このアプローチが成立するのはTerraformに特有の事情があるからです。一般的な機能開発では「正しい仕様 = コード」で、書き直したコードが正しいかを確認する手段は限られます。しかしIaCでは「正しい状態 = 既に動いているAWSリソース」で、新コードが現状と一致するかは plan で機械的に照合できます。機能開発で言うと本番挙動を完全に再現するE2Eテストが全て揃っている状態に近く、これがフルリライトを現実的な選択肢にしている理由です。
移行後の構成
レイヤー構成
新構成は4層のレイヤーに分かれ、各レイヤーは独立したstateを持ち、terraform_remote_state で下位レイヤーのoutputsを参照します。
|
|
依存はすべて一方向で、循環依存はありません。
環境別ディレクトリ方式
terraform.workspace をやめ、staging と prd を物理的に別ディレクトリに分けました。
|
|
これにより、terraform workspace select prd のような切り替え操作が不要になります。実行前に今いるディレクトリを確認する必要は残りますが、環境の誤操作リスクは大きく下がります。
層間のデータ連携
各サービスは terraform_remote_state で上位レイヤーのoutputsを参照します。
|
|
旧コードで module.shared_base.vpc_id のように暗黙的だった依存が、terraform_remote_state を経由することで明示的になります。どのスタックが何に依存しているかが、データソースの定義一覧から一目で分かるようになりました。
import blockによる移行の流れ
各スタックで以下の4ステップを繰り返します。
Step 1: 新コードを書く(stateは空)
まず新しいディレクトリで理想の構成を書き、terraform init を実行します。この時点ではstateが空なので、plan は全リソースを「新規作成」として表示します。AWS上にリソースは既に存在していますが、新しいstateがそれを知らないためです。
Step 2: import blockを書く
Terraform 1.5で導入された import blockを使い、既存リソースとTerraformコードの対応を宣言します。
|
|
Step 3: planで変更差分ゼロを確認し、apply
|
|
「plan の変更差分ゼロ」がこのアプローチの肝です。これが達成できれば、新しいコードが既存リソースと完全に一致していることをTerraform自身が保証してくれます。
Step 4: import blockを削除して最終確認
import blockは一度applyすれば不要になります。削除してもstateにはリソースが残るので、最後に imports.tf を消し、再度 terraform plan で No changes. となることを確認して完了です。
段階的な移行(フェーズ分割と並行運用)
レイヤー構成のおかげで、移行を段階的に進められます。各フェーズは独立して変更差分ゼロを検証でき、前のフェーズが完了してから次に進みます。失敗しても影響範囲はそのフェーズの新stateだけで、旧環境には一切影響しません。
| Phase | 対象 | リソース数 | 備考 |
|---|---|---|---|
| 0 | modulesコピー + CI整備 | - | 準備作業 |
| 1 | foundation | 約30 | VPC, Subnet等 |
| 2 | foundationのみに依存するservices(7個) | 約140 | coreを待たずに進められる |
| 3 | core | 約840 | 最大の作業 |
| 4 | coreにも依存するservices(12個) | 約470 | ECSサービス中心 |
| 5 | monitoring | 約160 | 全servicesのoutputsを参照 |
| 6 | 旧state削除 + クリーンアップ | - | 36,000行削除 |
移行期間中、チームメンバーは旧構成で plan / apply を自由に実行できます。新stateはAWS上の同じリソースを参照しているだけで、旧stateにも引き続き同じリソースが記録されているためです。移行作業がチームの日常業務をブロックしません。
最終切替時には、移行中に旧側で行われた変更を新コードに反映し、再度変更差分ゼロを確認します(追いつきステップ)。
AIコーディングエージェントの活用
このアプローチの最大のトレードオフは、コード量が多いことです。今回は約12,000行のTerraformコードを新規に書きました。これを現実的なコストに収めるため、Claude Codeを活用して人間とAIで役割を分担しました。
| 役割 | 担当 | 具体的な作業 |
|---|---|---|
| コード生成 | AI | 旧コードを参考に新構成の .tf ファイルを生成 |
terraform.workspace → var.env 変換 |
AI | 機械的な置換 |
| ディレクトリ構成・定型ファイル生成 | AI | environments/ の backend.tf, provider.tf 等 |
| AWSリソースIDの特定 + import block生成 | AI | AWS CLIや terraform state list で実IDを確認しimport blockを生成 |
plan 差分の解消 |
AI | 差分が出た場合のコード修正 |
apply の実行判断 |
人間 | import実行の最終判断、import時changeの確認 |
作業量の約9割はAIが担当し、人間はapplyの最終判断と、後述するimport時changeの確認に集中する形になりました。state mv での段階移行は、フェーズごとに他チームのマージ(apply)待ちが発生し、その積み上げで見積もりは8週間でした。一方、フルリライト + importは旧stateに触れず待ち時間が発生しないため、AI併用で検証込み1週間で完了しています。
短いプロンプトを連発するのではなく、移行手順そのものをClaude Codeとあらかじめ計画ドキュメントとして詰めておき、「このドキュメントに従って実装してください」と作業させるスタイルが安定しました。計画ドキュメントを蓄積していくことで、セッションをまたいでも文脈を引き継げます。
plan 変更差分ゼロの難しさ
このアプローチで最も注意が必要なのは、plan の変更差分をゼロにする作業です。新コードが既存リソースの設定と1つでも食い違うと差分が出ます。
よく遭遇したケースは2つあります。
- outputsの定義漏れ —
module.xxx.outputをterraform_remote_state経由に書き換える際、依存先のoutputs定義が不足していると差分になります - import時のchange — importした結果、Terraform providerが新しい属性を認識してchangeが出るケースがありました。コード側の問題ではなく、import時にstateに取り込まれた属性とproviderの期待値との差異が原因です
outputsの定義漏れは、AIに plan 実行と結果からの修正をループさせることで解消できました。人間が手動で対応したのはimport時のchangeが既存リソースに影響しないかを確認する判断のみです。
まとめ
フルリライト + importによる移行は、次の状況で特に有効です。
- 設計ごと見直したい場合 — workspace分岐の廃止やディレクトリ構成の刷新を同時に行えます
- 本番を止められない場合 — 旧stateに一切触れないため安全に並行運用できます
- 段階的な移行をより安全に進めたい場合 — フェーズ分割で各ステップを独立して検証でき、失敗しても旧stateには影響しません
コード量が多いというトレードオフは、AIコーディングエージェントによって現実的なコストに収まる時代になりました。これまで選択肢になりにくかった「書き直す」が、改めて有力な選択肢として浮上しています。state分割やIaCリファクタリングを検討中の方の参考になれば幸いです。