プログラマのための IT 教科書

Terraform と Infrastructure as Code

この部の 3 / 5 章 ・ 全体で 72 / 76 章 ・ 読了目安 45 分

この章を読むとできるようになること
  • state を安全に扱える
  • plan の destroy と replace を見逃さない
  • 手で変えた設定をコードへ戻せる

前章でクラウドの構成要素を見ました。 では、その設定をどうやって管理するか。

□ 誰が、いつ、何のためにこの設定を変えたのか分からない
□ 開発環境と本番環境で設定が微妙に違う(そして誰も理由を知らない)
□ 障害で作り直そうとしたが、同じものを再現できない
□ 手順書はあるが、実際の設定とずれている

画面をクリックして作ったインフラは、こうなります。

なぜ Infrastructure as Code が生まれたか

かつてサーバーは、物理的に調達して、手で設定するものでした。 数が少なく、寿命が長かったので、それで足りていました。

クラウドの登場で状況が変わります。サーバーは数分で作れて、 数十・数百に増え、頻繁に作り直されるものになりました。

そうなると、手作業には限界が来ます。

□ 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 をローカルに置かない

state がローカルにあると、

□ 他の人が実行すると「何も無い」と判断され、**同じリソースを二重に作る**
□ PC が壊れたら、管理対象を見失う(クラウド上には残るが、Terraform から辿れない)
□ 同時に2人が実行すると、state が壊れる

必ずリモートに置き、ロックを有効にしてください。

terraform {
  backend "gcs" {
    bucket = "my-tfstate"
    prefix = "prod"
  }
}

GCS / S3 バックエンドはロックにも対応しています。 2人が同時に apply しようとすると、後の人は待たされます。

state には秘密情報が平文で入る

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.
`-/+ replace` を見逃さない

一部の属性はその場で変更できないため、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 は「作ったサーバーの中をどう設定するか」**と、役割が違います。

実務の落とし穴まとめ

  1. state をローカルに置く — 二重作成、破損、共有できない
  2. state を git にコミット — 秘密情報が平文で漏れる
  3. plan を読まずに apply — 意図しない destroy / replace
  4. -/+ replace を見逃す — DB やディスクが作り直され、データが消える
  5. import 後に差分を残したまま apply — 既存設定を上書きする
  6. 手元から本番に apply — 環境の取り違え。CI 経由にする
  7. 手で変えた設定をコードに戻さない — 次の apply で巻き戻る
  8. 最初から module 化 — 早すぎる抽象化(設計の基礎)
  9. 秘密情報を Terraform で作る — state に残る

まとめ

  • IaC は、Git・レビュー・CI という開発の作法を、インフラにも適用するもの
  • Terraform はあるべき状態を書く。差分はツールが計算する
  • state が本体。リモートに置き、ロックし、git にコミットしない。 秘密情報が平文で入ることを忘れない
  • apply の前に必ず plan を読む。 特に destroy と -/+ replace は、データ消失につながる
  • 既存リソースは import → 差分ゼロになるまでコードを合わせる
  • 環境はディレクトリで分けるのが実務では一般的
  • 本番の apply は CI 経由。plan を PR に出してレビューする
  • 手で変えたら、その日のうちにコードへ戻す

公式ドキュメント

プロバイダのドキュメントは「引く」ものです。 書き方を覚える必要はなく、リソース名で検索して例をコピーするのが普通の使い方です。

terraform plan の結果に `Plan: 1 to add, 0 to change, 1 to destroy.` と出ました。変更したのは DB インスタンスの設定1箇所だけです。どうしますか。

次の章では、書いたコードとインフラを本番へ届ける仕組みを扱います。

読み終わったら記録しておくと、目次で進み具合が分かります。