Terraformの全能感とIaCの幻想
「楽になるはずだったのに、なぜ楽にならないのか」
これは、ぼくがTerraformを2年ほど使った後に抱いた疑問だ。
全能感の始まり
terraform apply
このコマンドを初めて実行したとき、ぼくは全能感に包まれた。
数十行のコードを書いただけで、VPCが立ち上がり、サブネットが切られ、EC2インスタンスが起動する。AWSのコンソールを開くことなく、インフラが「生成」される。まるで魔法のようだった。
「これでインフラ管理は楽になる」
そう確信した。コードでインフラを管理する。変更履歴はGitに残る。環境の複製も簡単。レビュープロセスに乗せられる。Infrastructure as Code(IaC)という思想は、まさに銀の弾丸に見えた。
しかし、数ヶ月後、ぼくはその幻想から醒めることになる。
「全能感」の正体
Terraformが与える全能感には理由がある。
まず、宣言的記述の心地よさ。「こうあるべき」を書くだけで、Terraformがその状態を実現してくれる。手順書は不要。AWSコンソールでポチポチする必要もない。コードを書いて、コマンドを叩く。それだけだ。
次に、terraform planの安心感。実行前に「これからこう変更しますよ」とプレビューが出る。何が作られ、何が削除されるか、事前にわかる。「何が起きるかわかっている」という感覚は、強い安心感をもたらす。
そして、再現性への期待。同じコードなら同じインフラができる。開発環境と本番環境を同じ構成にできる。災害時にもコードから復旧できる。そんな期待を抱かせる。
けれども、これらは全て「期待」に過ぎなかった。
state fileという爆弾
Terraformには「state file」というものがある。
これは、Terraformが管理しているリソースの現在状態を記録するJSONファイルだ。terraform.tfstateという名前で、ローカルまたはS3などのリモートストレージに保存される。Terraformは、このファイルを見て「自分が何を作ったか」を知る。
逆に言えば、このファイルがなければ、Terraformは何も知らない。
ここに最初の問題がある。
チームで開発していると、複数の人が同時にterraform applyを実行することがある。state fileが競合する。S3 + DynamoDBでロック機構を構築しても、完璧ではない。タイミングによっては、整合性が崩れることがある。
ある開発チームで実際に起きた話を聞いた。
金曜日の夕方、あるエンジニアがステージング環境を更新しようとした。同時に別のエンジニアが本番環境を更新していた。ディレクトリを間違えていた。state fileが混線した。
月曜日の朝、本番のRDSインスタンスが「Terraformの管理外」になっていることが判明した。次のterraform applyで、Terraformは「このRDSは知らないから削除して新しく作ろう」と判断しかけた。寸前で気づいて事なきを得たが、チーム全員の顔が青ざめた。
state fileが破損すると、Terraformは既存のリソースを「知らない」状態になる。次にterraform applyを実行すると、既存のリソースを無視して新しいリソースを作ろうとする。あるいは、既存のリソースを「削除して作り直す」と判断する。
本番環境のデータベースが突然削除される。そんな悪夢が、現実に起こりうる。
二重管理という現実
もっと根本的な問題がある。
TerraformでRDSインスタンスを作ったとしよう。
resource "aws_db_instance" "main" {
identifier = "production-db"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.medium"
allocated_storage = 100
db_name = "myapp"
username = "admin"
password = var.db_password
}
これでRDSインスタンスは作れる。インスタンスタイプ、ストレージサイズ、エンジンバージョン。これらはTerraformで管理できる。
しかし、データベースの「中身」はどうか。
テーブル定義は?インデックスは?ストアドプロシージャは?そしてデータそのものは?
答えは「Terraformの管轄外」だ。
テーブル定義はFlywayやLiquibaseで管理する。データそのものは当然別管理。バックアップはAWSの自動バックアップ機能を使い、リストア手順は別途ドキュメント化する。
結果として、「インフラ管理」は二重になる。
-
インスタンス設定 → Terraform
-
スキーマ定義 → マイグレーションツール
-
データ → バックアップ/リストア手順
-
監視設定 → CloudWatch / Datadog
-
アラート → PagerDuty連携
「Infrastructure as Code」と言いながら、実際にコード化できているのはインフラの一部に過ぎない。
S3バケットも同様だ。
resource "aws_s3_bucket" "assets" {
bucket = "my-company-assets"
}
resource "aws_s3_bucket_versioning" "assets" {
bucket = aws_s3_bucket.assets.id
versioning_configuration {
status = "Enabled"
}
}
バケットの作成、バージョニングの設定はTerraformでできる。しかし中身のファイルは?ライフサイクルで削除されたオブジェクトの管理は?バージョニングで増え続ける過去バージョンは?
全てがTerraformの外にある。
「Infrastructure as Code」という名前は、実態より大きな期待を抱かせる。正確には「Infrastructure Configuration as Code」だ。インフラの「設定」はコード化できるが、インフラの「状態」はコード化できない。
小さな変更、大きなコスト
Terraformを使い込むと、モジュール化したくなる。
VPCの設定をmodules/vpcに。ECSの設定をmodules/ecsに。RDSをmodules/rdsに。そうやって整理すると、見通しが良くなる気がする。
しかし、モジュール間には依存関係がある。
modules/
├── vpc/ # ネットワーク基盤
├── ecs/ # コンテナ実行環境(VPCに依存)
├── rds/ # データベース(VPCに依存)
└── app/ # アプリケーション(ECS, RDSに依存)
ECSはVPCのサブネットIDを参照する。RDSもVPCのセキュリティグループを参照する。アプリケーションはECSクラスターとRDSのエンドポイントを参照する。
ここで、VPCのCIDRを変更したいとする。10.0.0.0/16を10.1.0.0/16に変えたい。
影響範囲はどうなるか。
VPCに依存する全てのモジュールに影響する。サブネットのCIDRも変わる。セキュリティグループのルールも見直しが必要かもしれない。ECSのタスク定義は?RDSのサブネットグループは?
「VPCのCIDRを変える」という一見シンプルな変更が、全モジュールの見直しを要求する。しかも、多くの場合terraform apply一発では済まない。依存関係の順序を考えながら、段階的に適用する必要がある。
Redditでこんな投稿を見た。
“We spent 3 days refactoring Terraform modules just to add one new environment. The ‘infrastructure as code’ promise feels hollow when code changes take longer than clicking in the console.”
新しい環境を1つ追加するだけで3日。コンソールでポチポチした方が早い。「Infrastructure as Code」の恩恵とは何だったのか。
8年間、同じ議論
興味深いことがある。
2017年のHacker Newsで、こんな議論があった。
“Terraform state management is a nightmare for teams” (チーム開発でのTerraformのstate管理は悪夢だ)
2019年、Mediumにこんな記事が載った。
“The Hidden Costs of Infrastructure as Code” (IaCの隠れたコスト)
2021年、Dev.toにこんな体験談が投稿された。
“Why I stopped using Terraform” (なぜぼくはTerraformを使うのをやめたか)
2023年、Twitter(現X)でこんなツイートがバズった。
“Hot take: Terraform creates more problems than it solves for 80% of teams” (過激な意見:Terraformは80%のチームにとって、解決する問題より生む問題の方が多い)
8年間、同じ議論が繰り返されている。
state file管理の難しさ。二重管理の発生。変更コストの高さ。これらの問題は、8年前から指摘されている。そして、8年経った今も解決されていない。
これは「まだ解決策が見つかっていない」のではない。おそらく「構造的に解決困難な問題」なのだ。
なぜ解決しないのか
問題の本質はどこにあるのか。
ぼくはこう考えている。「宣言的なコードと、ステートフルな現実世界の間には、埋められない溝がある」と。
Terraformは「あるべき状態」を宣言する。「このVPCが存在すべき」「このRDSインスタンスが存在すべき」。Terraformはその状態を実現する。
しかし、インフラの多くは「今ある状態」の蓄積で成り立っている。
データベースのデータは、過去の書き込みの蓄積だ。S3のオブジェクトは、過去のアップロードの蓄積だ。ログは、過去のイベントの蓄積だ。これらは「あるべき状態」として宣言できない。「こういうデータがあるべき」とは書けない。
言い換えれば、ステートレスなリソースとステートフルなリソースで、Terraformとの相性が異なる。
Terraformと相性が良いリソース - VPC、サブネット、ルートテーブル - セキュリティグループ、NACLs - IAMロール、ポリシー - ALB、CloudFront、API Gateway
Terraformと相性が悪いリソース - RDS(データ、スキーマ) - S3(オブジェクトの中身) - ElastiCache(キャッシュされたデータ) - Elasticsearch(インデックスされたドキュメント)
多くのシステムは、ステートレスとステートフルが混在している。だから、Terraformだけで「完全なインフラ管理」は原理的に不可能なのだ。
K8s + GitOpsという別解
ここで、Kubernetesの話をしたい。
「Terraformがダメなら代わりに何を使うのか」という問いへの、一つの答えだ。
Kubernetesは「すべてをYAMLマニフェストで管理する」という点ではTerraformに似ている。しかし、決定的な違いがある。
Kubernetesには「Reconciliation Loop(調整ループ)」がある。定義された状態と実際の状態を常に比較し、差分があれば自動で修正する。Deploymentを定義すれば、Kubernetesは常にその状態を維持しようとする。Podが落ちれば再起動する。ノードが死ねば別のノードにスケジュールする。
Terraformは「apply時に一度だけ状態を合わせる」ツールだ。Kubernetesは「常に状態を維持し続ける」仕組みだ。この違いは大きい。
さらに、StatefulSetを使えばステートフルなアプリケーションも管理できる。PersistentVolumeとPersistentVolumeClaimでストレージも宣言的に扱える。Operatorパターンを使えば、データベースのスキーマ管理すら宣言的に行える。
GitOpsを組み合わせると、さらに一貫性が高まる。ArgoCDやFluxが、Gitリポジトリの状態とクラスタの状態を常に同期する。「Gitにあるもの = クラスタにあるもの」という等式が成り立つ。
もちろん、Kubernetesにも問題はある。学習コストは高い。運用の複雑さもある。全てのワークロードに適しているわけでもない。
しかし、少なくとも「宣言的な状態管理」という点では、Terraformより一貫性がある。ぼくがK8sを持ち出したのは、「ステートフルなものまで含めて宣言的に管理したいなら、そこまでやる覚悟が必要だ」ということを伝えたかったからだ。
「使い方が悪い」への反論
ここで想定される反論がある。
「それはTerraformの使い方が悪いだけだ。モジュール設計を見直せ。state管理のベストプラクティスに従え。正しく使えば問題ない」
ぼくはこの反論に同意しない。
「正しく使えば問題ない」は、ツールの複雑さを証明している。
8年間、Terraformの「ベストプラクティス」は変わり続けてきた。Workspacesを使え。いや、ディレクトリで環境を分けろ。Terragruntを使え。いや、素のTerraformで十分だ。モジュールは小さく。いや、大きすぎると依存が複雑になる。
毎年のように新しい「正解」が提唱される。これは「使い方の問題」ではなく「ツールの設計の限界」だ。
そもそも、「ベストプラクティス」は組織によって異なる。チームの規模、インフラの複雑さ、変更頻度、スキルセット。これらによって「正解」は変わる。普遍的なベストプラクティスが存在しないこと自体が、問題の根深さを示している。
「手動管理よりマシ」への反論
別の反論もある。
「それでも手動でコンソールをポチポチするよりはマシだろう。少なくともコードとして残る。レビューもできる」
これも一理ある。しかし、問いを取り違えている。
問いは「Terraformで楽になるか」であり、「手動より良いか」ではない。
「手動より良い」としても、「楽になる」とは限らない。Terraformを導入して、学習コスト、state管理のオーバーヘッド、モジュール設計の複雑さ、ドリフト対応の手間が増える。その総コストが、手動管理のコストを上回っていないか。
また、手動管理でも手順書をGit管理することは可能だ。変更履歴を残すこともできる。レビュープロセスに乗せることもできる。これらはTerraformの専売特許ではない。
Terraformの真の優位性は「自動適用」にある。しかし、その自動適用こそがterraform destroyという悪夢を可能にする。自動化にはリスクがある。そのリスクに見合うメリットがあるかどうか、冷静に判断する必要がある。
30年のインフラ自動化史
ここで、もう少し長い視点で考えてみたい。
人類はいつからインフラの自動化に取り組んできたのか。
1990年代、シェルスクリプトの時代があった。サーバー構築手順をシェルスクリプトにまとめる。再現性はあるが、冪等性がない。同じスクリプトを2回実行すると壊れる。
2005年、Puppetが登場した。「あるべき状態」を定義するアプローチの始まりだ。2009年にはChef、2012年にはAnsibleが続いた。サーバー内部の構成をコード化できるようになった。しかし、「サーバーそのものを作る」のは依然として手動だった。
2011年、AWS CloudFormationが登場した。2014年にはTerraformが続いた。「インフラそのもの」をコード化するパラダイムが生まれた。
そして2020年代、ぼくたちは再び問い直している。「IaCは本当に正しかったのか」と。
30年間、人類はインフラ管理の自動化に取り組んできた。しかし「完全な自動化」は達成されていない。
これは技術の問題なのか。それとも、本質的に不可能なのか。
おそらく答えは後者だ。インフラには本質的にステートフルな部分があり、それは宣言的に管理できない。データは「あるべき状態」ではなく「今ある状態」の蓄積だからだ。
この限界を認識した上で、ツールを使う。それが成熟した姿勢ではないだろうか。
ビジネス視点での判断基準
技術的な議論はここまでにして、もう少し実務的な話をしよう。
「Terraformを導入すべきか」を判断するための、ビジネス視点の基準だ。
導入コスト(初期投資) - Terraform学習: エンジニア1人あたり40〜80時間 - 既存インフラのコード化: リソース数 × 2〜4時間 - CI/CDパイプライン構築: 40〜80時間 - 合計: 中規模プロジェクトで200〜400時間程度
運用コスト(継続的に発生) - state file管理: 月4〜8時間 - ドリフト検出・対応: 月2〜4時間 - Terraformバージョンアップ対応: 四半期ごとに8〜16時間 - トラブルシューティング: インシデントあたり4〜16時間
期待されるメリット - 環境構築時間の短縮: 1環境あたり8時間 → 1時間 - 設定ミスの削減: 月1〜2件 → 月0〜1件 - 監査対応: 変更履歴がGitに残る
損益分岐点の目安 - 環境の複製が年3回以上ある → メリットが上回る可能性が高い - 環境の複製が年1〜2回 → トントンか微妙 - 環境の複製がほぼない → コストが上回る可能性が高い
この計算は大雑把だが、「Terraformを入れれば必ず楽になる」わけではないことは伝わるだろう。
現実的な落とし所
では、どうすればいいのか。
ぼくの結論はシンプルだ。IaCは銀の弾丸ではない。適用範囲を見極めよ。
Terraformを使うべき場面 - ステートレスなリソースが中心(VPC、IAM、ALBなど) - 環境の複製が頻繁(開発/ステージング/本番が同構成) - チームにインフラ専任がいて、メンテナンスコストを吸収できる
Terraformを避ける/慎重になるべき場面 - ステートフルなリソースが主体(RDS、S3の中身など) - インフラ変更が稀(年に数回程度) - 小規模チームで学習コストが見合わない
そして最も重要なのは、「IaCをすること自体を目的にしない」ことだ。
目的は「インフラ管理の効率化」であり、「Terraformを使うこと」ではない。Terraformを使った結果、運用負荷が増えているなら、それは本末転倒だ。
「うちはTerraform使ってます」と言いたいがためにTerraformを導入する。そんなケースを何度も見てきた。IaCは手段であって目的ではない。
まとめ
terraform applyの全能感。あれは幻想だった。
Terraformは特定の問題を解決する。ステートレスなリソースの管理、環境の複製、変更履歴の追跡。これらには確かに有効だ。
しかし、新たな問題も生む。state fileの管理、二重管理の発生、小さな変更の大きなコスト。8年間、これらの問題は解決されていない。
根本的な原因は、宣言的なコードとステートフルな現実世界の乖離にある。これは技術的に解決困難な、構造的な問題だ。30年のインフラ自動化の歴史を振り返っても、この溝は埋まっていない。
だからといって、IaCを全否定するわけではない。適用範囲を見極め、期待値を適切に設定することが重要だ。
「Terraformを入れればインフラ管理が楽になる」
その幻想から醒める時が来ている。楽になるかどうかは、チームの状況とインフラの特性による。銀の弾丸は存在しない。それを認めた上で、自分たちに合ったアプローチを選ぶ。それがぼくたちにできることだ。