★ 初級

02. 標準リポジトリ構成

Terraform プロジェクトのフォルダ構成は、後から環境を増やす時/モジュールを共有する時 に大きく効きます。最初に「公式が推奨している型」に合わせておきましょう。

なぜ構成にこだわるのか

Terraform は .tf ファイルが置かれた ディレクトリ単位 で 1 つの構成(= state)を作ります。dev と prod を同じディレクトリに置くと、片方を直したつもりで両方を壊します。 逆に最初から 環境ごとにディレクトリを分ける 設計にしておくと、dev で試して prod に持っていく流れが自然になります。

最小構成(学習・1 環境用)

my-infra/
├── .gitignore
├── README.md
├── terraform.tf      # terraform { required_version, required_providers, backend }
├── providers.tf      # provider "aws" { ... }
├── variables.tf      # variable "..." { ... }
├── locals.tf         # locals { ... }
├── main.tf           # resource "..." { ... }
└── outputs.tf        # output "..." { ... }

ファイル名は機能ではなく 役割 で分けるのが慣例です。Terraform は同じディレクトリの .tf をすべてマージして読むので、ファイルを分けても動作は同じ。読む人にやさしい分け方を選びます。

公式推奨ファイル名 terraform.tf / providers.tf / variables.tf / locals.tf / main.tf / outputs.tf / backend.tf。コードベースが大きくなったら network.tf / compute.tf 等に機能分割する。

標準構成(modules + envs)

my-infra/
├── .github/
│   ├── workflows/
│   │   ├── plan.yml         # PR で plan を回す
│   │   └── apply.yml        # main マージで apply
│   ├── CODEOWNERS           # レビュー必須者
│   └── dependabot.yml       # 依存更新(任意)
├── .gitignore
├── .pre-commit-config.yaml  # commit 前に fmt/validate
├── README.md
│
├── modules/                 # 再利用可能な部品
│   ├── network/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── README.md        # ← README があるものだけ「外部公開可」
│   ├── compute/
│   └── storage/
│
└── envs/                    # 環境ごと(= state ごと)
    ├── dev/
    │   ├── main.tf          # module "network" { source = "../../modules/network" }
    │   ├── terraform.tfvars # この環境固有の変数値
    │   └── backend.tf       # この環境用 state の保存先
    ├── stg/
    └── prd/

envs ディレクトリのねらい

環境分離に Workspace は使わない

公式が明記 Terraform の Workspace 機能は「同じ構成で複数 state を持つ」ためのものですが、本番/検証の分離には不適 と公式が明言しています。理由は「同じ backend に複数 state を相乗りさせるため、認証情報・権限の分離ができない」から。環境分離は必ずディレクトリ分割

モジュール内の標準ファイル

各モジュールは「最低限これだけ」のセットが推奨です。

modules/network/
├── main.tf       # resource ブロック群(このモジュールが作るもの)
├── variables.tf  # 入力(呼び出し元が渡す値)
├── outputs.tf    # 出力(呼び出し元に返す値)
└── README.md     # 何のためのモジュールか・使い方

呼び出し側はこうなります:

module "network" {
  source = "../../modules/network"

  vpc_cidr           = "10.10.0.0/16"
  public_subnet_cidrs = ["10.10.1.0/24", "10.10.2.0/24"]
}

# モジュールの output は module.<name>.<output_name> で参照
resource "aws_instance" "web" {
  subnet_id = module.network.public_subnet_ids[0]
  # ...
}

よくあるアンチパターン

やりがちなこと何が起きるか正しい型
1 つのディレクトリに dev/prd 両方を書くdev の変更で prd を破壊envs/ で分割
Workspace で dev/prd 分離state は分かれるが認証情報を分離できないenvs/ で分割(公式推奨)
tfstate を Git にコミット機密漏洩 + 競合事故S3+DynamoDB のリモート backend
tfvars に AWS アクセスキーを書くGit 履歴に永久に残る環境変数 / Secrets Manager / OIDC
1 モジュールで全 AWS 構成変更のたびに全部 plan、衝突多発機能ごとにモジュール分割