Terraform と Infrastructure as Code
この部の 3 / 5 章 ・ 全体で 72 / 76 章 ・ 読了目安 45 分
- state を安全に扱える
- plan の destroy と replace を見逃さない
- 手で変えた設定をコードへ戻せる
前章でクラウドの構成要素を見ました。 では、その設定をどうやって管理するか。
□ 誰が、いつ、何のためにこの設定を変えたのか分からない
□ 開発環境と本番環境で設定が微妙に違う(そして誰も理由を知らない)
□ 障害で作り直そうとしたが、同じものを再現できない
□ 手順書はあるが、実際の設定とずれている
画面をクリックして作ったインフラは、こうなります。
かつてサーバーは、物理的に調達して、手で設定するものでした。 数が少なく、寿命が長かったので、それで足りていました。
クラウドの登場で状況が変わります。サーバーは数分で作れて、 数十・数百に増え、頻繁に作り直されるものになりました。
そうなると、手作業には限界が来ます。
□ 100台に同じ設定を手で入れる → 必ずどこかで間違える
□ 「本番と同じ環境」を作れない → 検証の意味が薄れる
□ 変更履歴が残らない → 障害時に何が変わったか分からない
そこで、インフラの構成をコードとして書き、 git で管理し、レビューして、自動で適用するという発想が生まれました。 これが Infrastructure as Code(IaC)です。
アプリのコードに対してやってきたこと(Gitの Git、CI/CDの CI)を、 インフラにも適用する——それだけの話です。
Terraform の考え方
Terraform は、「あるべき状態」を書くツールです。
手順を書く(命令的): 「VM を作れ」「ディスクを付けろ」「IP を割り当てろ」
状態を書く(宣言的): 「VM が1台、ディスク付き、この IP で存在している」
宣言的だと、今の状態との差分をツールが計算してくれます。
今の状態: VM が 2台
書いた状態: VM が 3台
→ Terraform が「1台追加する」と判断する
同じファイルを何度適用しても結果は同じ(冪等。非同期処理とメッセージング・間違えられない処理を作る)です。
最小の構成
# provider: どのクラウドを操作するか
provider "google" {
project = "my-project"
region = "asia-northeast1"
}
# variable: 外から渡す値
variable "environment" {
type = string
description = "dev / stg / prod"
}
# resource: 作るもの
resource "google_storage_bucket" "assets" {
name = "my-app-assets-${var.environment}"
location = "ASIA-NORTHEAST1"
uniform_bucket_level_access = true # ファイルとストレージの「既定は非公開」
lifecycle_rule {
condition { age = 90 }
action { type = "Delete" }
}
}
# output: 他から参照したい値
output "bucket_url" {
value = google_storage_bucket.assets.url
}provider 操作対象(GCP / AWS / GitHub / Datadog など)
resource 作成・管理する対象
variable 入力
output 出力
data 既存のものを参照する(作らない)
state — Terraform の本体
ここが最も重要で、最も事故が起きる部分です。
コード(.tf) 「こうあるべき」という宣言
state(.tfstate)「今、実際にこうなっている」という記録
実際のクラウド 現実
Terraform は、この3つを突き合わせて差分を出します。
plan = コード と state と現実 を比べて、何が変わるかを表示する
apply = その差分を実際に適用し、state を更新する
state がローカルにあると、
□ 他の人が実行すると「何も無い」と判断され、**同じリソースを二重に作る**
□ PC が壊れたら、管理対象を見失う(クラウド上には残るが、Terraform から辿れない)
□ 同時に2人が実行すると、state が壊れる
必ずリモートに置き、ロックを有効にしてください。
terraform {
backend "gcs" {
bucket = "my-tfstate"
prefix = "prod"
}
}GCS / S3 バックエンドはロックにも対応しています。 2人が同時に apply しようとすると、後の人は待たされます。
DB のパスワードや生成された鍵が、state ファイルに平文で記録されます。
□ state を git にコミットしない(.gitignore に入れる)
□ state を置くバケットは非公開・暗号化・アクセス制限
□ state の閲覧権限は、本番の全権限に等しいと考える
□ 秘密情報は Terraform で作らず、Secret Manager 等で別管理するのが安全
「Terraform のコードには秘密を書いていないから大丈夫」ではありません。 出力される state が問題です(セキュリティ)。
plan を読む
apply する前に、必ず plan を読みます。 記号の意味は3つだけです。
+ create 作る
~ update その場で変更する
- destroy 消す
-/+ replace 消してから作り直す ← 最も危険
Plan: 1 to add, 2 to change, 1 to destroy.
一部の属性はその場で変更できないため、Terraform は 削除してから作り直そうとします。
□ DB インスタンスの replace → データが消える
□ ディスクの replace → データが消える
□ IP アドレスの replace → 通信断
Plan: ... to destroy の数字が 0 でない時は、必ず何が消えるかを確認してください。
これはルールを守る — 情報を扱う者の日常の「本番を壊さない作法」と同じ重みの話です。
意図しない replace が出たら、lifecycle { prevent_destroy = true } で
保護をかけるか、変更方法を見直します。
既存リソースを取り込む
手で作ったリソースを、後から Terraform 管理下に入れられます。
# 既存のバケットを、コード上の定義に紐づける
terraform import google_storage_bucket.assets my-app-assets-prod□ import しても、コードは自動生成されない(自分で書く)
□ import 後に plan を打ち、差分が出ないところまでコードを合わせる
□ 差分が出たまま apply すると、既存の設定が上書きされる ← 危険
「plan で差分ゼロ」になって初めて、取り込みが完了です。
module — 繰り返しをまとめる
module "api_service" {
source = "./modules/cloud-run-service"
name = "api"
image = "asia-northeast1-docker.pkg.dev/proj/api:v1.2.3"
env = var.environment
min_scale = 1
}設計の基礎の関数と同じで、同じ構成を複数の環境で使い回すために切り出します。
□ 最初から module にしない。2〜3回繰り返してから切り出す(設計の基礎)
□ module の中で環境ごとの分岐を増やしすぎない(フラグ引数の罠)
□ 公開 module を使う時は、中身を読んでから(ライセンスと依存ライブラリ)
環境の分け方
方式A: ディレクトリで分ける envs/dev/ envs/stg/ envs/prod/
方式B: workspace で切り替える terraform workspace select prod
実務では方式A(ディレクトリ分割)が多いです。
□ 環境ごとに state が完全に分かれる
□ 本番だけ設定を変えたい時に、コードで明示できる
□ 「今どの環境を操作しているか」がパスで分かる ← 事故防止
クラウドの基礎の kubectl と同じ問題です。
□ ディレクトリを間違えて本番に apply
□ workspace を切り替え忘れて本番に apply
□ プロンプトに環境名を出す(starship などのツール)
□ 本番の apply は CI 経由のみにし、手元からは実行できないようにする
□ 本番の state バックエンドに、別の認証を要求するCI での運用
理想の形は、これです。
1. コードを変更して PR を出す
2. CI が terraform plan を実行し、**結果を PR にコメントする**
3. レビュアーが plan の差分を読む ← ここがレビューの本体
4. マージされたら CI が apply する
□ 手元から本番に apply しない
□ plan の結果を人が読む(自動 apply だけにしない)
□ apply の実行者は、専用のサービスアカウント(認証と認可の実装)
□ 実行ログを残す
「インフラの変更もコードレビューを通す」 ——これが IaC の最大の価値です。
ドリフト(手で変えられた状態)
Terraform の state: マシンタイプ = e2-medium
実際のクラウド: マシンタイプ = e2-standard-4 ← 障害対応で手で変えた
この状態で apply すると、手の変更が元に戻されます。
□ 定期的に plan を実行し、差分が出ていないか監視する
□ 緊急で手で変えた場合は、必ずコードに反映する(その日のうちに)
□ 「Terraform 管理下のものは手で変えない」をチームのルールにする
障害時にスケールアップする、といった対応は正しい判断です。
問題は、その後コードに戻さないことです。
1. 手で変える(障害を止める)
2. その日のうちにコードへ反映し、PR を出す
3. plan で差分ゼロを確認する
設計の基礎の「意図的な技術的負債」と同じで、 記録して返せば問題ありません。
他の選択肢
Terraform だけが IaC ではありません。
| ツール | 特徴 |
|---|---|
| Terraform / OpenTofu | 複数クラウドに対応。事実上の標準 |
| Pulumi | 汎用言語(TypeScript / Go)で書ける |
| AWS CDK | 同上(AWS 中心) |
| Cloud Deployment Manager 等 | 各クラウド純正 |
| Ansible | サーバー内部の設定(構成管理)に強い |
**Terraform は「クラウド上に何を作るか」、 Ansible は「作ったサーバーの中をどう設定するか」**と、役割が違います。
実務の落とし穴まとめ
- state をローカルに置く — 二重作成、破損、共有できない
- state を git にコミット — 秘密情報が平文で漏れる
- plan を読まずに apply — 意図しない destroy / replace
-/+ replaceを見逃す — DB やディスクが作り直され、データが消える- import 後に差分を残したまま apply — 既存設定を上書きする
- 手元から本番に apply — 環境の取り違え。CI 経由にする
- 手で変えた設定をコードに戻さない — 次の apply で巻き戻る
- 最初から module 化 — 早すぎる抽象化(設計の基礎)
- 秘密情報を Terraform で作る — state に残る
まとめ
- IaC は、Git・レビュー・CI という開発の作法を、インフラにも適用するもの
- Terraform はあるべき状態を書く。差分はツールが計算する
- state が本体。リモートに置き、ロックし、git にコミットしない。 秘密情報が平文で入ることを忘れない
- apply の前に必ず plan を読む。
特に destroy と
-/+ replaceは、データ消失につながる - 既存リソースは import → 差分ゼロになるまでコードを合わせる
- 環境はディレクトリで分けるのが実務では一般的
- 本番の apply は CI 経由。plan を PR に出してレビューする
- 手で変えたら、その日のうちにコードへ戻す
公式ドキュメント
| 対象 | リンク |
|---|---|
| Terraform 公式ドキュメント | https://developer.hashicorp.com/terraform/docs |
| チュートリアル(入門) | https://developer.hashicorp.com/terraform/tutorials |
| Google Cloud プロバイダ | https://registry.terraform.io/providers/hashicorp/google/latest/docs |
| AWS プロバイダ | https://registry.terraform.io/providers/hashicorp/aws/latest/docs |
| OpenTofu(フォーク) | https://opentofu.org/docs/ |
プロバイダのドキュメントは「引く」ものです。 書き方を覚える必要はなく、リソース名で検索して例をコピーするのが普通の使い方です。
terraform plan の結果に `Plan: 1 to add, 0 to change, 1 to destroy.` と出ました。変更したのは DB インスタンスの設定1箇所だけです。どうしますか。
次の章では、書いたコードとインフラを本番へ届ける仕組みを扱います。