TerraformのS3バックエンド設定をした後、次に直面する設計課題の1つが「stateをどのように分割するか」という問題です。stateを1つにまとめ続けると、terraform planの実行時間が伸び、複数人が同時にapplyするとDynamoDBのlockが競合します。一方で分割しすぎると、state間の参照(remote_state)が増えてHCLが複雑になります。
この記事では、Terraformのstateをどのようなポリシーで分割するかという設計判断を、モノリシック・サービス別・レイヤー別の3つのアプローチを比較しながら解説します。Terraform 1.5以降を想定環境として、各アプローチの特徴・適した規模・移行コストまで整理します。
この記事のポイント
・1つのstateに集約すると変更の影響範囲が大きく、大規模になると分割が必要になる
・サービス別分割は独立デプロイしやすいが、共通インフラのremote_state参照設計が必要
・レイヤー別分割はlock競合を防ぎやすいが、レイヤー間のremote_state依存が増える
・「planの実行60秒超・lock競合が頻発」が分割に踏み切る目安の1つ
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
Terraformのstateをなぜ分割するのかとプロジェクト規模ごとの目安
Terraformのstateは、Terraformが管理するリソースの「現在の状態」を記録するファイルです。1つのstateに含まれるリソースが増えると、次のような問題が起きやすくなります。・terraform planが遅くなる:すべてのリソースをAWS APIで差分確認するため、リソース数に比例して時間がかかる
・lockの競合が起きる:複数人が同時にterraform planやapplyを実行すると、DynamoDBのstateロックが競合してブロックされる
・変更の影響範囲が広がる:ネットワーク設定だけを変更したのに、アプリサービスの全リソースがplan差分として表示される
stateの分割を検討するタイミングの目安はこちらです。
・terraform planの実行が60秒を超え始めた
・チームの開発者が3名以上でlock競合が頻発している
・共通インフラ(VPC・DNS)とアプリサービスの変更を独立させたい
逆に、小規模なプロジェクトや1人で管理している場合はモノリシックstateのシンプルさを活かすほうが管理コストを低く保てます。
モノリシックstate(全リソースを1つのstateで管理)の特徴と限界
最もシンプルなアプローチは全リソースを1つのtfstateで管理することです。| メリット | デメリット |
|---|---|
| リソース間の参照がシンプル(ARNやIDを直接参照できる) | リソース数が増えるとplanが遅くなる |
| terraform planで全体の差分が一画面で確認できる | lockの競合でチームの開発速度が落ちる |
| 初期設定が少なく始めやすい | ネットワーク変更がアプリ層のplan差分に影響する |
サービス別state分割の設計パターン
「サービス別分割」は、デプロイの単位(マイクロサービス・アプリケーション)ごとにstateを分ける設計です。共通インフラ(VPC・サブネット・DNS)は専用のstateで管理し、アプリサービスA・BはそれぞれのstateでECS・ALB・RDSを管理します。# ディレクトリ構成例(サービス別分割) environments/ └── production/ ├── shared-infra/ # VPC・サブネット・Route53 │ ├── main.tf │ └── backend.tf # key: prod/shared-infra/terraform.tfstate ├── service-a/ # ECS・ALB・RDS for service-a │ ├── main.tf │ └── backend.tf # key: prod/service-a/terraform.tfstate └── service-b/ ├── main.tf └── backend.tf # key: prod/service-b/terraform.tfstate
1. remote_stateで共通インフラのIDを参照する
service-a側からshared-infraのVPC IDやサブネットIDを参照する場合、terraform_remote_stateデータソースを使います。# service-a/main.tf data "terraform_remote_state" "shared" { backend = "s3" config = { bucket = "my-terraform-state" key = "prod/shared-infra/terraform.tfstate" region = "ap-northeast-1" } } # 参照例(サービスAのサブネット指定) locals { private_subnet_ids = data.terraform_remote_state.shared.outputs.private_subnet_ids }
2. shared-infra側でoutputを公開する
参照される側のstateは、参照されるリソースをoutputとして公開しておく必要があります。# shared-infra/outputs.tf output "private_subnet_ids" { value = aws_subnet.private[*].id } output "vpc_id" { value = aws_vpc.main.id }
TerraformでLinuxサーバーインフラの設計を体系的に学びたい場合は、Terraform実践講座の詳細ページでも確認できます。
レイヤー別state分割(ネットワーク・コンピュート・データ)の設計
「レイヤー別分割」は、インフラのレイヤー(ネットワーク層・コンピュート層・データ層)でstateを分ける設計です。変更頻度の低いネットワーク層と高いコンピュート層を分離することで、変更の影響範囲を小さくします。# ディレクトリ構成例(レイヤー別分割) environments/production/ ├── 01-network/ # VPC・サブネット・NAT Gateway(変更頻度: 低) ├── 02-security/ # SG・IAMポリシー・KMS(変更頻度: 低~中) ├── 03-data/ # RDS・ElastiCache・S3(変更頻度: 中) └── 04-compute/ # ECS・ALB・Lambda(変更頻度: 高)
| 比較項目 | サービス別分割 | レイヤー別分割 |
|---|---|---|
| チームの分担方法 | サービス(機能)ごとに担当を分けやすい | インフラ専門チームが全レイヤーを管理しやすい |
| デプロイの独立性 | サービス単位で独立してリリース可能 | レイヤー間の依存順序を守る必要がある |
| remote_state参照の数 | shared-infra→各サービスで1~2参照 | 上位レイヤー→下位レイヤーへの複数参照 |
| 適した組織 | マイクロサービス型の開発組織 | インフラ専門チームがある組織 |
remote_stateを使ったstate間のデータ参照設計
どの分割パターンでも、state間でデータを参照するためにremote_stateを使います。設計上の注意点を整理します。循環参照を作らない:state Aがstate Bを参照し、state BもAを参照する循環は禁止です。参照は常に一方向(例:04-computeが01-networkを参照)にします。
outputの後方互換を守る:state Aのoutputを削除すると、それを参照しているstate Bのterraform planが失敗します。outputは「API」として扱い、削除前にすべての参照元から外します。
terraform_remote_stateはstateファイルに直アクセスする:remote_stateはS3のstateファイルをそのまま読むため、backend設定(bucket・key・region)が正しく指定されていないと「object has no attribute」エラーが出ます。stateファイルのパスはコードレビューで確認する習慣が重要です。
state分割設計の選択基準
どのアプローチを選ぶかの判断フローをまとめます。1. リソース数が50個以下かつ1~2人管理:モノリシックstateのまま維持する
2. 複数チームが独立してデプロイする必要がある:サービス別分割
3. 1つのインフラチームが全体を管理しつつplanを高速化したい:レイヤー別分割
4. 大規模でTerraformのDRY化も課題:Terragruntの
dependencyブロックを使ったstate管理も検討する初期設計でレイヤー別かサービス別かに迷ったときは、将来のチーム構成から逆算するのが有効です。1つのインフラチームで管理するならレイヤー別、開発チームごとにインフラを持つなら早期からサービス別を採用します。
state分割後のよくある失敗とトラブルシュート
1. terraform planで「Error: Invalid data source argument」が出る
remote_stateを参照しているときに、参照先stateのoutputが存在しない場合に発生します。参照先stateのディレクトリでterraform outputコマンドを実行してoutputが公開されているか確認します。# 参照先stateのoutputを確認(出力例(環境により異なります)) $ cd environments/production/01-network $ terraform output private_subnet_ids = [ "subnet-0a1b2c3d", "subnet-0e4f5a6b", ] vpc_id = "vpc-0123456789abcdef0"
2. terraform planが「Error acquiring the state lock」で止まる
前のapplyが途中で失敗してDynamoDBのlockが残った場合です。terraform force-unlock LOCK_IDでlockを手動解除します。LOCK_IDはエラーメッセージ内に表示されます。force-unlockは他のapplyが実際に動いていないことを確認してから実行します。3. 分割後に既存リソースが「deleted and recreated」としてplanに出る
state分割の際にリソースを別stateへ移動させるとき、正しくstate mvしないと旧stateでの「delete」と新stateでの「create」として計画されてしまいます。既存リソースを壊さずに移動するにはterraform state mv(コマンド)かmovedブロック(HCL宣言)を正しく使います。本記事のまとめ
Terraformのstateをどのポリシーで分割するかは、チームの規模・組織構造・デプロイ頻度によって変わります。| 分割方針 | 適した場面 | 主な注意点 |
|---|---|---|
| モノリシック | 小規模・1人管理・50リソース以下 | 規模が大きくなると管理限界が来る |
| サービス別分割 | マイクロサービス・複数デプロイチーム | shared-infraのoutput互換を守る必要がある |
| レイヤー別分割 | インフラ専門チームがある・planの高速化 | レイヤー間参照が増えると複雑になる |
movedブロックを活用してリソースを再作成せずにstate間を移動させます。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:TerraformとAnsibleを役割分担させる設計|プロビジョナーを使わずにterraform applyとAnsible Playbookを連携するCI/CDパターン
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます