TERRAFORM IMPORT COURSE / CHAPTER 04

Terraformのmodule戦略
フォルダ整理ではなく、再利用とstate管理の設計

Cloud Praticaのmodule戦略を、stg/prdの振り分け、TFTUIのstate表示、三項演算子、unit moduleまで一本につなげる。いま何をどこへ置き、なぜそうするのかを説明できる状態を目指す。

TL;DR

  1. stgとprdは別root module・別state。共通コードだけをmodulesから呼ぶ。
  2. AWSサービス単位・フラット・ベタ書きが基本。unit moduleは繰り返しが見えてから。
  3. module化はファイル移動だけでは未完了。stateアドレスもmodule配下へ移す。

01まず全体像 — 4つの登場人物を分ける

root module、child module、state、AWS実体は、それぞれ別の役割を持つ。

Terraform module、state、AWS実体の関係図
moduleは共有設計図、stateは環境ごとの管理台帳、AWSは現物。
登場人物置き場所・例役割
root modulestg/aws.tf
prd/aws.tf
環境ごとの入口。引数とbackendを決める
child modulemodules/aws/ecr共通のresource定義を提供する
state環境別S3 backendTerraformアドレスとAWS実体の対応を記録する
AWS実体ECR・VPCなど実際に稼働するクラウド資産

重要:同じディレクトリにある.tfファイル群は、Terraformからは1つのmoduleとしてまとめて読まれる。main.tfという名前自体に特別な魔法はない。

02環境の振り分け — 同じmoduleへ違う引数を渡す

環境を自動判定するのではなく、実行するroot moduleとstateを明示的に分ける。

stgとprdが同じTerraform moduleを再利用する図
共有するのはresource定義。環境値・実行場所・stateは共有しない。
DIRECTORY
cloud-pratica-terraform/
├── modules/aws/ecr/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
├── stg/aws.tf
└── prd/aws.tf
CALL SHARED MODULE
# stg/aws.tf
module "ecr" {
  source = "../modules/aws/ecr"
  env    = "stg"
}

# prd/aws.tf
module "ecr" {
  source = "../modules/aws/ecr"
  env    = "prd"
}

誤解しやすい点:stgディレクトリでterraform applyしてもprdは動かない。ただし共有moduleを変更すると、次回のstg・prdそれぞれのplanで差分候補になるため、各環境で確認して順番に適用する。

03TFTUIが平坦な理由 — stateの住所は自動では変わらない

ファイルをmodulesへ移す作業と、stateアドレスを移す作業は別物。

Terraform stateアドレスをmodule配下へ移す図
コードの住所とstateの住所が一致して、初めてplanがNo changesになる。
状態TFTUI表示planの傾向
コードだけmodule化aws_ecr_repository.sample旧resource削除+module内resource作成
stateも移動済みmodule.ecr...設定が同じならNo changes
moved {
  from = aws_ecr_repository.sample
  to   = module.ecr.aws_ecr_repository.sample
}

TFTUIが見ているもの:フォルダ構造ではなくstateアドレス。modules/を作っただけでmodule.ecr表示にはならない。

04粒度の原則 — AWSサービス単位で平たく切る

巨大moduleも深いネストも避け、探す場所と責任範囲を固定する。

Terraform moduleをAWSサービス単位でフラットに配置する図
まずサービス単位でベタ書き。繰り返しが確認できた箇所だけunit化する。
設計評価理由
巨大main module避けるtargetが効きにくく、引数と環境分岐が増える
マイクロサービス単位避ける共有VPCやRDSの置き場が曖昧になる
AWSサービス単位基本責任範囲と探す場所が明確
unit module必要時のみ同じresource一式の反復をまとめる
modules/
├── vpc/
├── subnet/
├── ec2/
├── ecr/
├── ecr_unit/  # ECR+policyの一式を反復するとき
├── rds/
└── rds_unit/
抽象化は目的ではない。module内でresourceを読みやすくベタ書きする勇気も、運用設計の一部。

05分岐と依存 — 賢く見えるコードより、入力が明確なコード

三項演算子は禁止ではない。環境差をどこに置くかが重要。

Terraform module内の環境分岐と引数方式の比較図
環境名から設定値を推測させず、呼び出し元に意思決定を置く。
# 避けたい: 3環境目で意味が崩れる
cpu = var.env == "stg" ? 300 : 1000

# 推奨: root moduleで意図を明示
module "ecs" {
  source = "../modules/aws/ecs"
  cpu    = 300
}

# 許容例: optional resourceの有無
count = var.enabled ? 1 : 0
選択肢使いどころ注意点
自作moduleVPC・ECR・EC2などチーム固有の文脈を読みやすく保持
public moduleS3・CloudFrontなど設定量が多い場合version固定・互換性・更新追従が必要

最短の判断基準:まず自作のサービス単位module。反復が痛くなったらunit module。設定量が膨大ならpublic moduleを検討する。

結論

  1. stg/prdは別root・別stateにし、共有moduleへ環境値を引数で渡す。
  2. AWSサービス単位・フラット・ベタ書きを基本にし、必要なunit化だけを足す。
  3. module化の完了条件は、コードとstateの住所が一致し、planがNo changesになること。