- ✅ 前提①: Terraform 基礎 — init/plan/apply の理解
- ✅ 前提②: Terraform 実践 — モジュール・State・CI/CD
- ✅ 第1弾: Git入門 — ローカルで init/commit/branch/merge を習得
- ✅ 第2弾: GitHub入門 — push/pull/PR/Issue/Fork/revert でチーム協業
- ✅ 第3弾(本記事): ブランチ戦略 — GitFlow / GitHub Flow / trunk-based と Terraform モジュール管理
- 📌 第4弾(近日公開): セキュリティ — Secret Scanning / OIDC / 依存関係管理
- 📌 第5弾(近日公開): CI/CD — GitHub Actions × Terraform で自動化パイプライン構築
各記事は独立して読めますが、第1弾→第2弾→第3弾の順に進むと、ローカル Git からチーム協業、本番運用まで一本の学習パスとして体系的に習得できます。
- 1 Section 1: イントロダクション・本記事で得られるもの・前提
- 2 Section 2: ブランチ戦略とは何か — なぜチームで戦略が必要か
- 3 Section 3: 3大ブランチ戦略の比較 — GitFlow / GitHub Flow / trunk-based
- 4 Section 4: [ハンズオン] GitHub Flow で Terraform モジュールを改善する
- 5 Section 5: [ハンズオン] GitFlow で develop/release/hotfix を回す
- 6 Section 6: [ハンズオン] Trunk-based + Feature Flags で Terraform を継続的デプロイ
- 7 Section 7: Terraform 環境別ディレクトリ × ブランチ戦略 の設計
- 8 Section 8: Terraform モジュールのバージョン管理 — git tag と source ref
- 8.1 8-1. モジュールの SemVer 設計
- 8.2 8-2. git tag 打ち方と push
- 8.3 8-3. terraform source で git tag 参照
- 8.4 8-4. プライベートリポジトリ参照時の認証
- 8.5 8-5. タグ更新の運用ルール
- 8.6 8-6. CHANGELOG.md 運用(Conventional Commits 活用)
- 8.7 8-7. モジュールバージョン管理の全体フロー早見表
- 8.8 8-8. バージョン更新の自動化 — Renovate / Dependabot との連携
- 8.9 Section 8 チェックリスト
- 8.10 Section 7〜8 まとめ — 学習の位置づけ
- 9 Section 9: 応用トピック — モノレポ vs ポリレポ・ブランチ保護深掘り・第4/5弾布石
- 10 Section 10: まとめ・スキル習得表・シリーズロードマップ・参考リンク
Section 1: イントロダクション・本記事で得られるもの・前提
1-1. 第2弾からの流れ — ブランチ管理という次の壁
第2弾(GitHub入門)では、リモートリポジトリへの push・Pull Request の作成・Issue によるタスク管理・Fork を使った OSS コントリビューションを習得しました。これで「個人での GitHub 活用」は一通りマスターできた状態です。
しかし、チーム開発の現場に入ると、すぐに新たな課題に直面します。
- 「feature ブランチをいつ、どこに、どのようにマージするか」 がチームによってバラバラ
- 誰かが main に直接 push して本番が壊れる
- 複数の機能開発が並行している状態で、リリースタイミングの調整が難しい
- Terraform の IaC コードを複数人で触ると tfstate の競合が頻発する
これらは「ブランチ戦略」の欠如が引き起こす典型的な問題です。ブランチ戦略とは、「どのブランチをいつ作り、どこにマージし、いつ削除するか」のチームルールです。
本記事では、業界標準として定着した 3 つのブランチ戦略(GitFlow / GitHub Flow / trunk-based development)を比較し、Terraform IaC 運用に最適なものを選択できるスキルを身に付けます。
1-2. 本記事のゴール
本記事を最後まで読むと、以下のことができるようになります。
| ゴール | 説明 |
|---|---|
| 3戦略の理解 | GitFlow / GitHub Flow / trunk-based の仕組み・適用シーンを説明できる |
| 選定判断 | チーム規模・リリース頻度・環境数から最適な戦略を選べる |
| Terraform特化理解 | tfstate 競合・本番 apply 慎重性とブランチ戦略の関係を説明できる |
| ハンズオン実践 | terraform-git-handson/ で各戦略を実際に体験できる |
| モジュール管理 | git tag によるバージョニングとプライベートモジュール参照を理解できる |
1-3. 前提条件
本記事のハンズオンを実施するには、以下の前提条件を満たしている必要があります。
必須: 第1弾・第2弾の完走
| 習得済みスキル | 確認方法 |
|---|---|
| git init / commit / branch / merge | 第1弾チェックリスト参照 |
| GitHub push / pull / PR 作成 | 第2弾チェックリスト参照 |
| gh CLI による PR・Issue 操作 | gh auth status でログイン確認 |
環境要件
| ツール | バージョン | 確認コマンド |
|---|---|---|
| Git | 2.39 以上 | git --version |
| GitHub CLI (gh) | 2.40 以上 | gh --version |
| Terraform | 1.7 以上 | terraform --version |
| GitHub アカウント | 無料プランで可 | ブラウザでログイン確認 |
AWS アカウントは不要です。 ハンズオンはすべてローカル + GitHub の範囲で完結します。Terraform の apply は Section 7 以降でのみ必要ですが、そちらも LocalStack を使用するためクラウド費用は発生しません。
本記事で使用するリポジトリは、第1弾から継続して使用している terraform-git-handson/ です。まだクローンしていない場合は以下で準備してください。
git clone https://github.com/<your-username>/terraform-git-handson.gitcd terraform-git-handson1-4. 習得スキルと推定所要時間
推定所要時間: 約 120 分
| フェーズ | 内容 | 目安時間 |
|---|---|---|
| Section 1-3 | 概念理解・比較 | 30分 |
| Section 4-5 | GitHub Flow / GitFlow ハンズオン | 40分 |
| Section 6-7 | Trunk-based / モジュール git tag | 30分 |
| Section 8-10 | 環境別 tfstate・まとめ | 20分 |
習得スキル一覧(本 Section 1〜3 の範囲)
- ブランチ戦略の定義と選定が必要な理由
- GitFlow の 5 ブランチ構成とライフサイクル
- GitHub Flow のシンプルな 2 層構成とその強み
- trunk-based development の Short-lived branch 原則
- 3 戦略の選定マトリクス(Terraform 運用観点を含む)
1-5. 本記事の進め方
本記事は「比較理解 → ハンズオン → Terraform 特化」の 3 段階で構成されています。
Section 1-3: 理論・比較フェーズ ↓Section 4-6: ハンズオンフェーズ(各戦略を terraform-git-handson/ で体験) ↓Section 7-9: Terraform 特化フェーズ(git tag・モジュール管理・環境別 tfstate) ↓Section 10: まとめ・ロードマップ概念だけ先に把握したい方は Section 1〜3 を通読した後にハンズオンへ進むのが効率的です。「とにかく手を動かしたい」という方は Section 3-4 の比較表だけ確認してから Section 4 のハンズオンに飛んでも構いません。
Section 2: ブランチ戦略とは何か — なぜチームで戦略が必要か
2-1. ブランチ戦略の定義と効果
ブランチ戦略(Branch Strategy) とは、Git リポジトリにおいて「どのブランチをどの目的で使い、いつ作成し、どこにマージし、いつ削除するか」のチームルールです。
ブランチ戦略を導入することで得られる主な効果は以下の 3 つです。
① リリース安定性の向上
本番リリース用のブランチ(main / release)を保護し、未テストの変更が本番に混入しないようにします。誰かの「ちょっとした修正」が本番障害を引き起こす、というケースを構造的に防げます。
② 並行開発の効率化
機能 A の開発チームと機能 B の開発チームが互いに干渉せずに作業できます。feature ブランチを切ることで、未完成のコードが他の開発者の作業に影響を与えません。
③ ロールバック容易性
問題が発生したリリースを明確に識別して切り戻せます。ブランチと git tag の組み合わせにより、「v1.3.2 にロールバック」という操作が安全・迅速に行えます。
2-2. 戦略なしの弊害 — 第2弾の学びと接続
第2弾では main ブランチへのブランチ保護ルールを設定し、「PR 経由でしか main にマージできない」状態を作りました。これは戦略の第一歩です。しかし、保護ルールだけでは解決できない問題があります。
よくある混乱シナリオ
# 状況: メンバー3人が同じリポジトリで作業中メンバーA: feature/add-vpc を develop にマージ(半完成)メンバーB: feature/add-rds を main にマージ(急ぎリリース)メンバーC: feature/add-s3 が develop ベースで作業中→ develop と main が乖離→ メンバーCのブランチが何をベースにすべきか不明→ リリース時に何が含まれているか誰も把握できないこのような状態では「今の main に何が入っているか」「次のリリースに何を含めるか」の管理が崩壊します。
具体的な弊害 ①: コンフリクト地獄
長期間ブランチを持ち続けると、main(または develop)との差分が膨らみ、マージ時に大量のコンフリクトが発生します。
# 30日間放置した feature ブランチをマージしようとした場合git switch maingit merge feature/long-lived-branch# 大量のコンフリクト発生Auto-merging modules/vpc/main.tfCONFLICT (content): Merge conflict in modules/vpc/main.tfAuto-merging modules/rds/variables.tfCONFLICT (content): Merge conflict in modules/rds/variables.tfAuto-merging environments/prod/main.tfCONFLICT (content): Merge conflict in environments/prod/main.tfAutomatic merge failed; fix conflicts and then commit the result.手動でのコンフリクト解消は時間がかかるだけでなく、解消ミスが新たなバグを生むリスクがあります。
具体的な弊害 ②: リリース不能
複数の機能が「中途半端な状態」で main に混在している場合、「機能Aだけリリースしたい(機能Bはまだ準備中)」という状況で詰まります。
| 状況 | 戦略なし | 戦略あり(GitFlow) |
|---|---|---|
| 機能Aのみリリースしたい | 機能Bを手動で revert → リスク大 | release/x.y ブランチに機能Aのみ含める |
| 本番緊急修正が必要 | main が混乱中でどこから修正すべきか不明 | hotfix/* ブランチを main から切る |
| 「今の本番は何の状態か」確認 | git log で手動追跡 → 不確実 | git tag で明確に管理 |
具体的な弊害 ③: Terraform の main 直 apply
IaC では、「コードが main にある = 本番インフラの期待状態」という関係があります。戦略なしで main に未レビューのコードが混入すると、次の apply で意図しないインフラ変更が発生します。
# 危険なケース: 誰かが main に直 push した後の terraform plan$ terraform plan -var-file=prod.tfvars # aws_db_instance.main will be destroyed - resource "aws_db_instance" "main" {- identifier = "prod-mysql-01"- engine = "mysql"... }Plan: 0 to add, 0 to change, 1 to destroy.意図せず本番 RDS が削除される計画が生成されてしまいます。ブランチ戦略 + PR レビューでこの事故を構造的に防ぐことができます。
2-3. 選定軸 — チームの特性で最適解が変わる
ブランチ戦略は「正解が一つ」ではありません。チームの特性に合わせて選ぶ必要があります。主な選定軸は以下の 4 つです。
| 選定軸 | 説明 | 影響する戦略 |
|---|---|---|
| チーム規模 | 小規模(1〜5人)か大規模(10人以上)か | 小→GitHub Flow, 大→GitFlow/trunk-based |
| リリース頻度 | 週1回か、1日に複数回か | 低頻度→GitFlow, 高頻度→trunk-based |
| デプロイ自動化成熟度 | CI/CD が整備されているか | 整備済み→trunk-based 向き |
| 環境数 | dev/staging/prod の分離が必要か | 多環境→GitFlow 向き |
戦略別の特性サマリー
| 特性 | GitFlow | GitHub Flow | trunk-based |
|---|---|---|---|
| ブランチ数 | 多い(5種類) | 少ない(2種類) | 最小限 |
| リリース頻度 | 低〜中(定期リリース) | 高(随時リリース) | 非常に高(1日複数回も可) |
| 学習コスト | 高 | 低 | 中 |
| 複雑な環境管理 | 得意 | 不得意 | 条件付きで可 |
2-4. Terraform 特有の論点
IaC(Infrastructure as Code)である Terraform は、通常のアプリケーションコードと異なる特性を持ちます。ブランチ戦略を選ぶ際には、以下の Terraform 固有の論点を考慮する必要があります。
① tfstate の競合問題
Terraform の State ファイル(tfstate)は、リモートバックエンド(S3 + DynamoDB など)に保存されますが、複数人が同時に terraform apply を実行すると State ロックで衝突します。
# tfstate 競合の典型例(2名が同時に terraform apply した場合)Error: Error acquiring the state lockError message: ConditionalCheckFailedException: The conditional request failedLock Info: ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 Path:s3://my-tfstate-bucket/terraform.tfstate Operation: OperationTypeApply Who: alice@example.com Version:1.7.0 Created:2026-04-15 09:30:00 +0000 UTC Info:Terraform acquires a state lock to protect the state from being writtenby multiple users at the same time.ブランチ戦略でワークフローを整備し、「誰がいつ apply できるか」を明確にすることで、この競合を構造的に防げます。
② 本番 apply の慎重性
アプリケーションコードの場合、誤った変更をデプロイしても「ロールバック → 再デプロイ」が比較的容易です。しかし Terraform の場合、terraform apply でリソースを削除・再作成すると、データの損失や数分〜数時間のダウンタイムが発生することがあります。
そのため、「誰でも main から直接 apply できる」という運用は Terraform では特に危険です。ブランチ戦略と PR レビューを組み合わせ、terraform plan の結果を必ずレビューしてから apply するフローが必要です。
③ モジュール再利用性とバージョン管理
Terraform モジュールを git タグでバージョン管理している場合、「どのタグが本番で使われているか」「新しいタグに切り替えるタイミング」がブランチ戦略と密接に関わります。
# modules/vpc/main.tf でのバージョン参照例module "vpc" { source = "git::https://github.com/myorg/terraform-modules.git//vpc?ref=v1.2.0"}このような参照がある場合、タグの更新はブランチ戦略に従ったレビュープロセスを経るべきです。Section 7 でこの点を詳しくハンズオンします。
Terraform × ブランチ戦略 選定チャート
Q1: 環境が複数ある(dev/staging/prod)? → YES: GitFlow または環境別ブランチ戦略 → NO: 次へQ2: CI/CD が整備されていて plan/apply を自動化できる? → YES: trunk-based も選択肢 → NO: GitHub Flow 推奨Q3: チームが 10 人以上でリリース管理が複雑? → YES: GitFlow 推奨 → NO: GitHub Flow 推奨Terraform モジュールリポジトリと利用リポジトリで戦略を分ける考え方
規模が大きくなると、「Terraform モジュールを管理するリポジトリ」と「実際のインフラを管理するリポジトリ」を分離することがあります。この場合、それぞれ異なる戦略を採用することも有効です。
| リポジトリ種別 | 推奨戦略 | 理由 |
|---|---|---|
| モジュールリポジトリ(共通部品) | GitFlow + セマンティックバージョニング | 利用者への影響範囲が大きいため慎重な管理が必要 |
| 環境別インフラリポジトリ | GitHub Flow | 変更頻度が高く、環境ごとの tfvars で制御できる |
| 実験・検証用リポジトリ | trunk-based | 高速なイテレーションが目的 |
2-5. 3 戦略を選ぶ前の共通準備
どの戦略を選ぶにしても、以下の共通設定は必ず行うべきです。
# ① main ブランチの保護(第2弾で学習済み)# GitHub Settings → Branches → Branch protection rules# "Require a pull request before merging" を有効化# ② ローカルのデフォルトブランチを main に統一git config --global init.defaultBranch main# ③ コミットメッセージの規約(Conventional Commits 推奨)# feat: 新機能追加# fix: バグ修正# docs: ドキュメント変更# chore: ビルド・設定変更# refactor: リファクタリングコミットメッセージの規約を統一することで、どの戦略でも CHANGELOG の自動生成や PR の文脈把握が容易になります。
Section 3: 3大ブランチ戦略の比較 — GitFlow / GitHub Flow / trunk-based
3-1. GitFlow 詳解
GitFlow は 2010 年に Vincent Driessen が提唱したブランチモデルです。明確なブランチ役割分担と厳密なマージルールで構成されており、バージョン管理が必要なソフトウェアや定期リリース製品に向いています。
5 ブランチの構成と役割
| ブランチ名 | 種別 | 役割 | 存在期間 |
|---|---|---|---|
main | 永続 | 本番リリース済みコードのみ | 常時 |
develop | 永続 | 次のリリースに向けた統合ブランチ | 常時 |
feature/* | 一時 | 個別機能の開発 | 開発期間中 |
release/* | 一時 | リリース準備(バグ修正・バージョン更新) | リリース前 |
hotfix/* | 一時 | 本番緊急修正 | 修正期間中 |
ブランチのライフサイクル
main ←──────────────────────────── hotfix/1.0.1 ──┐ │ │ └──→ v1.0.0 tag └── (hotfix: main から分岐、修正後 main と develop にマージ)develop ←── feature/add-vpc ←── feature/add-rds ←── release/1.1.0 ││ └──────────────────────────────────────────────────── v1.1.0 tag → main実際のコマンド操作(feature ブランチのライフサイクル)
# feature ブランチを develop から切るgit switch developgit pull origin developgit switch -c feature/add-rds-module# 開発作業vim modules/rds/main.tfgit add modules/rds/git commit -m "feat: add RDS module with multi-AZ support"# develop にマージ(--no-ff でマージコミットを残す)git switch developgit merge --no-ff feature/add-rds-modulegit push origin develop# feature ブランチを削除git branch -d feature/add-rds-modulegit push origin --delete feature/add-rds-modulerelease ブランチのライフサイクル
# develop が安定したら release ブランチを切るgit switch developgit switch -c release/1.1.0# バージョン番号の更新・最終バグ修正のみ(新機能追加は禁止)echo "1.1.0" > VERSIONgit commit -m "chore: bump version to 1.1.0"# main と develop の両方にマージgit switch maingit merge --no-ff release/1.1.0git tag -a v1.1.0 -m "Release version 1.1.0"git switch developgit merge --no-ff release/1.1.0# release ブランチを削除git branch -d release/1.1.0hotfix ブランチのライフサイクル
# 本番障害発生!main から hotfix を切るgit switch maingit switch -c hotfix/1.0.1# 緊急修正vim modules/vpc/main.tfgit commit -m "fix: resolve security group rule causing outbound block"# main と develop の両方にマージgit switch maingit merge --no-ff hotfix/1.0.1git tag -a v1.0.1 -m "Hotfix: security group fix"git switch developgit merge --no-ff hotfix/1.0.1git branch -d hotfix/1.0.1GitFlow の主なコマンドフロー(全体俯瞰)
# GitFlow フル操作の概観# [1] develop で日常開発git switch developgit switch -c feature/add-cloudwatch-alarms# ... 開発・コミット ...git switch develop && git merge --no-ff feature/add-cloudwatch-alarms# [2] リリース準備(develop が安定したら)git switch -c release/2.0.0 develop# バグ修正・バージョン更新のみgit switch main && git merge --no-ff release/2.0.0 && git tag -a v2.0.0git switch develop && git merge --no-ff release/2.0.0git branch -d release/2.0.0# [3] 本番緊急修正(main が壊れた場合)git switch main && git switch -c hotfix/2.0.1# 緊急修正git switch main && git merge --no-ff hotfix/2.0.1 && git tag -a v2.0.1git switch develop && git merge --no-ff hotfix/2.0.1git branch -d hotfix/2.0.1GitFlow の適用シーン
| 向いているケース | 向いていないケース |
|---|---|
| 定期リリース(月次・週次) | 1日に複数回デプロイする |
| バージョン番号を明確に管理したい | 小規模チーム(1〜3人) |
| QA 期間が必要な製品 | 変化に素早く追従したい |
| 複数バージョンを並行サポートする | CI/CD が未整備 |
| Terraform 多環境(dev/staging/prod) | シンプルさを重視する |
3-2. GitHub Flow 詳解
GitHub Flow は GitHub 社が 2011 年に公開したシンプルなブランチモデルです。ブランチは main と feature/* の 2 種類のみで、継続的デリバリー(CD)を前提としています。
2 層構成のシンプルさ
main ←── PR ←── feature/add-vpc-modulemain ←── PR ←── feature/fix-sg-rulesmain ←── PR ←── feature/update-rds-instance-typeGitFlow のような develop や release ブランチは存在しません。main が常に本番リリース可能な状態であることが原則です。
GitHub Flow の基本フロー
# Step 1: main から feature ブランチを切るgit switch maingit pull origin maingit switch -c feature/add-s3-lifecycle-policy# Step 2: 変更を加えてコミットvim modules/s3/main.tfgit add modules/s3/main.tfgit commit -m "feat: add S3 lifecycle policy for cost optimization"# Step 3: GitHub に pushgit push -u origin feature/add-s3-lifecycle-policy# Step 4: PR を作成(gh CLI 推奨)gh pr create \ --title "feat: Add S3 lifecycle policy for cost optimization" \ --body "## 変更内容- S3 バケットに 30 日後に Infrequent Access、90 日後に Glacier へ移行するライフサイクルポリシーを追加- コスト最適化のため非アクティブオブジェクトの自動アーカイブを実装## terraform plan の結果\`\`\`Plan: 1 to add, 0 to change, 0 to destroy.\`\`\`## レビュー観点- ライフサイクルポリシーの期間設定は適切か- 既存データへの影響はないか" \ --base main# Step 5: レビュー → Squash merge → feature ブランチ削除# (GitHub UI または gh pr merge --squash)継続的デリバリー向きの理由
GitHub Flow が高頻度デプロイに向いている理由は、「main = いつでもリリースできる状態」という制約にあります。この制約を守るためには、CI/CD パイプラインが必須です。
| 仕組み | 効果 |
|---|---|
| PR 作成時に CI が自動実行 | バグを main に混入させない |
| main マージ後に自動デプロイ | マージ = リリース の一本化 |
| Squash merge でコミット整理 | main の履歴を綺麗に保つ |
| ブランチ保護でレビュー必須化 | 1人でも誤 push を防止 |
Squash merge vs Merge commit vs Rebase merge
GitHub Flow では、PR のマージ方法として Squash merge が推奨されることが多いです。
| マージ方法 | main の履歴 | 適用シーン |
|---|---|---|
| Squash merge | 1 PR = 1 commit(綺麗) | GitHub Flow(推奨) |
| Merge commit | ブランチ構造を保持 | GitFlow(推奨) |
| Rebase merge | 線形履歴・ブランチ情報なし | 小規模・個人プロジェクト |
GitHub Flow では「PR 単位で 1 コミット」にすることで、main の履歴が git log --oneline で読みやすくなります。
# gh CLI でのマージ方法指定gh pr merge 42 --squash --delete-branch# → feature ブランチを Squash merge し、ブランチを自動削除Terraform への適用時の注意点
GitHub Flow を Terraform に適用する場合、main へのマージ後に自動 apply するパイプラインを組む必要があります。しかし Terraform の場合、以下の点に注意が必要です。
# GitHub Flow × Terraform の CI/CD フロー(例)# .github/workflows/terraform.yml# PR 作成時: terraform plan を実行して結果を PR コメントに貼り付ける# main マージ時: terraform apply を自動実行# 注意: apply の自動化は本番環境では慎重に# dev 環境は自動 apply、prod 環境は手動承認(GitHub Environment を使用)環境が 1 つだけ(dev のみ)の場合は GitHub Flow が最適です。本番環境が存在する場合は、GitHub Flow を改変して環境別ブランチを設けるか、GitFlow に切り替えることを検討してください。
3-3. Trunk-based Development 詳解
Trunk-based Development(TBD) は、全開発者が単一のブランチ(trunk = main)に頻繁にマージするモデルです。Google・Facebook などの大規模組織で採用されており、DevOps 成熟度が高いチームに向いています。
基本原則: Short-lived Branch
# trunk-based の core rule: ブランチの寿命は原則 24 時間以内# 新機能の開発を開始git switch maingit pull origin maingit switch -c feature/vpc-flow-logs# こまめにコミット・push(大きな塊は作らない)vim modules/vpc/main.tfgit add modules/vpc/main.tfgit commit -m "feat(vpc): enable VPC flow logs to S3"git push origin feature/vpc-flow-logs# 24時間以内に main へ PR + マージgh pr create --title "feat(vpc): enable VPC flow logs" --base main# レビュー通過後すぐマージ24 時間以内にマージする原則により、長期間のブランチが存在しなくなり、大規模マージ時のコンフリクト(「マージ地獄」)を回避できます。
Feature Flags による未完成コードの隠蔽
短期間でマージするためには、未完成の機能であっても main に入れられる仕組みが必要です。その解決策が Feature Flags(機能フラグ) です。
# terraform variables.tf でフラグを定義variable "enable_waf" { description = "Feature flag: enable WAF for ALB" type = bool default = false # 開発中は false のまま main に入れる}# main.tf でフラグを参照resource "aws_wafv2_web_acl_association" "main" { count = var.enable_waf ? 1 : 0 resource_arn = aws_lb.main.arn web_acl_arn = aws_wafv2_web_acl.main[0].arn}enable_waf = false のまま main にマージしておき、機能が完成したタイミングで true に変更する PR を出します。これにより、未完成のリソースが本番に作成されることなく、コードは常に main に蓄積できます。
CI/CD との組み合わせが前提
trunk-based では CI が通らないと main にマージできないルール(ブランチ保護 + Required status checks)が必須です。
# .github/workflows/ci.yml(trunk-based 用)# PR 作成 → CI 実行 → 通過後のみマージ可能# 1日に何度も main にマージされる前提なので、# CI の実行時間を短く保つことが重要(目安: 5分以内)大規模組織・高頻度デプロイ向きの理由
| 課題 | trunk-based での解決策 |
|---|---|
| 大人数でのブランチ競合 | 全員が main に毎日マージするため長期ブランチが存在しない |
| 「マージ地獄」の回避 | Short-lived branch により差分が小さい |
| 機能リリースの柔軟性 | Feature Flags で main とは独立してリリース制御できる |
| デプロイ頻度の最大化 | main = デプロイ可能状態 × CI/CD 自動化 |
trunk-based の難しさと限界
| 難しい点 | 補足 |
|---|---|
| CI/CD の整備が前提 | パイプラインがないと main が壊れ続ける |
| Feature Flags の管理コスト | 古い flags を消さないと技術的負債になる |
| コードレビューの迅速化が必要 | 24時間以内マージには素早いレビューが必要 |
| Terraform の apply 自動化リスク | インフラ変更の自動 apply は慎重な設計が必要 |
3-4. 3 戦略 マスター比較表
以下の比較表は、ブランチ戦略を選定する際の総合的な参照表です。
| 観点 | GitFlow | GitHub Flow | trunk-based |
|---|---|---|---|
| 複雑度 | 高(5種類のブランチ) | 低(2種類のみ) | 中(rules が重要) |
| リリース頻度 | 低〜中(定期リリース) | 高(随時) | 非常に高(1日複数回) |
| ブランチ寿命 | 長(weeks〜months) | 中(days〜weeks) | 短(24h以内が原則) |
| 学習コスト | 高 | 低 | 中 |
| CI/CD 必須度 | 推奨 | 必須 | 絶対必須 |
| コンフリクト発生率 | 高(長期ブランチ) | 中 | 低(短命ブランチ) |
| 向いているチーム規模 | 中〜大 | 小〜中 | 大 |
| リリース管理の明確さ | 非常に高 | 中(main = 本番) | 低(自動化に依存) |
| バージョン管理 | 得意(tag + release) | 可(tag のみ) | 条件付き(自動タグ推奨) |
| マルチ環境対応 | 得意 | 不得意 | 条件付き可 |
OSSプロジェクトでの採用例
| 戦略 | 採用プロジェクト例 |
|---|---|
| GitFlow | 多くのライブラリ(定期バージョンリリース型) |
| GitHub Flow | GitHub 社自身・多くのスタートアップ |
| trunk-based | Google・Facebook・Netflix(内部ツール) |
移行パス — 成長に応じた戦略シフト
チームやプロダクトが成長するにつれてブランチ戦略を移行するケースもあります。
[フェーズ1] 個人開発: 戦略なし(main のみ) ↓ チームが2〜3人になったら[フェーズ2] 小チーム: GitHub Flow 導入 ↓ 環境が複数に増え、QA が必要になったら[フェーズ3] 中規模: GitFlow 導入 ↓ CI/CD が完全自動化され、デプロイ頻度が高まったら[フェーズ4] 成熟チーム: trunk-based development移行時に最も重要なのは「チーム全員への周知と合意」です。一部のメンバーが旧ルールのまま作業すると、ブランチ構成が混乱します。移行時は README や CONTRIBUTING.md にブランチルールを明記することを推奨します。
# CONTRIBUTING.md にブランチルールを記載する例# ブランチ命名規則# feature/<issue-number>-<short-description> 例: feature/42-add-s3-lifecycle# fix/<issue-number>-<short-description> 例: fix/73-sg-outbound-rule# release/x.y.z 例: release/1.2.0# hotfix/<issue-number>-<short-description> 例: hotfix/90-prod-db-lock3-5. Terraform 運用での推奨マトリクス
Terraform IaC 運用に特化した戦略選定マトリクスです。
規模・環境別の推奨戦略
| チーム規模 | 環境数 | CI/CD 成熟度 | 推奨戦略 |
|---|---|---|---|
| 1〜5人 | 1環境(dev のみ) | 未整備〜初期 | GitHub Flow |
| 1〜5人 | 2環境(dev/prod) | 初期〜中級 | GitHub Flow + 環境別 tfvars |
| 5〜15人 | 3環境(dev/staging/prod) | 中級 | GitFlow |
| 15人以上 | 3環境以上 | 成熟(自動化済み) | trunk-based + Feature Flags |
環境別ディレクトリ構成(GitFlow 推奨時)
# GitFlow × Terraform の推奨ディレクトリ構成terraform-git-handson/├── environments/│├── dev/││├── main.tf # develop ブランチで apply││└── terraform.tfvars│├── staging/││├── main.tf # release/* ブランチで apply││└── terraform.tfvars│└── prod/│ ├── main.tf # main ブランチ(タグ付き)で apply│ └── terraform.tfvars└── modules/ ├── vpc/ ├── rds/ └── s3/この構成では、develop ブランチへのマージ = dev 環境への apply、release/* 作成 = staging への apply、main へのマージ + タグ付け = prod への apply というフローが明確に定義できます。
ブランチと apply 環境のマッピング
# GitFlow × Terraform の apply フロー例# ① feature → develop へのマージ後(CI/CD が自動実行)cd environments/dev && terraform apply -auto-approve# ② release/x.y.z 作成後(手動または承認付き自動化)cd environments/staging && terraform plan# レビュー確認後cd environments/staging && terraform apply# ③ release → main へのマージ + タグ付け後(必ず手動承認)git tag -a v1.2.0 -m "Release 1.2.0"cd environments/prod && terraform plan# 2名以上がレビューを承認した後に applycd environments/prod && terraform apply本番環境(prod)への apply は必ず人間が承認するフローにすることが、Terraform 運用のベストプラクティスです。GitHub Actions の Environment 機能(Required reviewers)を使うと、この承認フローを自動化できます。
tfstate バックエンドとブランチ戦略の整合
環境別に tfstate を分離することがブランチ戦略と自然に連動します。
| ブランチ | tfstate 保存先(S3 例) | 用途 |
|---|---|---|
develop | s3://tfstate/dev/terraform.tfstate | 開発環境 |
release/* | s3://tfstate/staging/terraform.tfstate | ステージング環境 |
main | s3://tfstate/prod/terraform.tfstate | 本番環境 |
# environments/prod/backend.tfterraform { backend "s3" { bucket= "myorg-tfstate" key= "prod/terraform.tfstate" region= "ap-northeast-1" dynamodb_table = "terraform-lock" encrypt = true }}# environments/dev/backend.tfterraform { backend "s3" { bucket= "myorg-tfstate" key= "dev/terraform.tfstate" region= "ap-northeast-1" dynamodb_table = "terraform-lock" encrypt = true }}環境ごとに独立した tfstate を持つことで、「dev 環境での terraform apply が prod に影響しない」という安全性が保証されます。ブランチ戦略と tfstate の分離は、Terraform × チーム開発における最も重要な設計判断の一つです。
GitHub Actions での自動 apply 実装イメージ(GitFlow)
# .github/workflows/terraform-apply.yml(概略)name: Terraform Applyon: push: branches:- develop # dev 環境へ自動 apply- main # prod 環境へ(承認後のみ)jobs: apply-dev: if: github.ref == 'refs/heads/develop' environment: dev # GitHub Environment(自動承認) runs-on: ubuntu-latest steps:- uses: actions/checkout@v4- name: Terraform Apply (dev) run: | cd environments/dev terraform init terraform apply -auto-approve -var-file=dev.tfvars apply-prod: if: github.ref == 'refs/heads/main' environment: production# GitHub Environment(Required reviewers 設定済み) runs-on: ubuntu-latest steps:- uses: actions/checkout@v4- name: Terraform Apply (prod) run: | cd environments/prod terraform init terraform apply -var-file=prod.tfvarsenvironment: production の設定で「承認者が approve するまで apply が実行されない」という安全フェンスを設けられます。Section 9 でこの実装を詳しくハンズオンします。
Column: git-flow CLI ツール
GitFlow のブランチ操作を簡略化する git-flow CLI ツールがあります。頻繁に GitFlow 操作を行うチームでは導入を検討してください。
# インストール(macOS)brew install git-flow-avh# リポジトリを GitFlow 用に初期化git flow init# (main / develop のブランチ名などをインタラクティブに設定)# feature ブランチの開始・終了git flow feature start add-rds-modulegit flow feature finish add-rds-module# → develop への --no-ff マージ + ブランチ削除を自動化# release ブランチの開始・終了git flow release start 1.1.0git flow release finish 1.1.0# → main と develop へのマージ + タグ付けを自動化# hotfix の開始・終了git flow hotfix start 1.0.1git flow hotfix finish 1.0.1# → main と develop へのマージ + タグ付けを自動化git flow feature finish は --no-ff マージとブランチ削除を一括実行するため、GitFlow 操作のヒューマンエラーを大幅に減らせます。ただし、PR レビューの自動化はされないため、GitHub の保護ルールとの組み合わせが必要です。
Section 3 まとめ
本セクションでは 3 大ブランチ戦略を詳解しました。
| 戦略 | 一言まとめ | 最初の選択肢として |
|---|---|---|
| GitFlow | 複雑だが多環境・定期リリースに強い | 5人以上 + 複数環境 |
| GitHub Flow | シンプルで即戦力。CI/CD が前提 | 1〜5人の小チーム |
| trunk-based | 高頻度デプロイの最終形。CI/CD 必須 | 成熟チームのゴール |
Terraform 運用では GitHub Flow がエントリーポイントとして最適です。小規模チームで始めて、環境が増えたら GitFlow へ移行し、CI/CD が整備されたら trunk-based を検討する段階的アプローチをおすすめします。
Section 1〜3 で学んだこと チェックリスト
- [ ] ブランチ戦略が必要な理由を 3 つ以上説明できる
- [ ] GitFlow の 5 ブランチの名前と役割を説明できる
- [ ] feature ブランチを develop から切り、マージする操作ができる
- [ ] GitHub Flow の「main = いつでもリリース可能」原則を説明できる
- [ ] trunk-based development の Short-lived branch 原則と Feature Flags を説明できる
- [ ] 3 戦略の比較表からチームに合う戦略を選定できる
- [ ] Terraform の tfstate 競合がブランチ戦略でどう解決されるか説明できる
- [ ] 環境別ディレクトリ構成(dev/staging/prod)と tfstate 分離の関係を説明できる
Section 4 からは実際に terraform-git-handson/ リポジトリで各戦略をハンズオンします。まず Section 4 では GitHub Flow を使って Terraform モジュールを改善する PR ワークフローを体験します。
Section 4: [ハンズオン] GitHub Flow で Terraform モジュールを改善する
4-1. GitHub Flow 選択の判断
Section 3-5 の推奨マトリクスで確認したとおり、GitHub Flow が最適な状況は次のとおりです。
| 条件 | 状態 | GitHub Flow の適合度 |
|---|---|---|
| チーム規模 | 1〜5 名 | ◎ 最適 |
| 環境数 | dev + prod(またはそれ以下) | ◎ 最適 |
| CI/CD 成熟度 | 基礎的でも可 | ○ 問題なし |
| リリース頻度 | 随時〜週次 | ◎ 最適 |
| Terraform 経験 | 第2弾修了レベル | ◎ 最適 |
GitHub Flow のルールは 1 つだけです。main ブランチはいつでもデプロイ可能な状態を保つ。これを守るために、すべての変更を feature ブランチ経由で main にマージします。
本セクションでは、terraform-git-handson/ リポジトリに対して S3 モジュールのタグ変数追加という実用的な改善を GitHub Flow で実施します。
4-2. [ハンズオン] feature/add-s3-tags ブランチで S3 モジュールにタグ変数追加
まず現在の S3 モジュールの構成を確認します。
ls modules/s3/# main.tf outputs.tf variables.tfcat modules/s3/variables.tf現時点では tags 変数が存在しないことを確認したうえで、feature ブランチを作成します。
# main ブランチから最新状態で開始git switch maingit pull origin main# feature ブランチを作成git switch -c feature/add-s3-tagsmodules/s3/variables.tf に tags 変数を追加します。
# modules/s3/variables.tf(既存変数の末尾に追記)variable "tags" { type = map(string) default = {} description = "A map of tags to apply to the S3 bucket and related resources."}次に modules/s3/main.tf を修正して、tags をリソースに適用します。
# modules/s3/main.tf(resource ブロックに tags を追加)resource "aws_s3_bucket" "this" { bucket = var.bucket_name tags = merge( var.tags, {ManagedBy = "terraform" } )}resource "aws_s3_bucket_versioning" "this" { bucket = aws_s3_bucket.this.id versioning_configuration { status = var.enable_versioning ? "Enabled" : "Suspended" }}merge() 関数を使うことで、呼び出し元が渡したタグに加えて ManagedBy = "terraform" を自動付与できます。ルートモジュール側の呼び出しも更新します。
# environments/dev/main.tf(モジュール呼び出し側)module "s3_app" { source= "../../modules/s3" bucket_name = "my-app-dev-${var.env_suffix}" tags = { Environment = "dev" Team = "platform" CostCenter = "cc-1234" }}変更をローカルで検証します。
# dev 環境でプラン確認cd environments/devterraform initterraform plan# 変更点が tags 追加のみであることを確認# Plan: 0 to add, 1 to change, 0 to destroyterraform plan で既存リソースへの影響が tags 追加のみ(インプレース変更)であることを確認します。リソースの再作成(-/+ 表示)が発生する場合は設定を見直してください。
コミット前に Terraform のコード品質を確認するステップも加えておきます。
# フォーマットチェック(インデントや空白を標準形式に統一)terraform fmt -recursive ../../modules/s3/terraform fmt environments/dev/# 構文検証(プロバイダーなしでも実行可能)terraform validate# セキュリティスキャン(オプション:tfsec が入っている場合)tfsec modules/s3/terraform fmt はチームで統一した書式を保つために PR 作成前に必ず実行する習慣をつけましょう。CI で自動チェックする設定は第5弾で行います。
4-3. コミット → PR → Squash merge の一連フロー
変更を stage して意味のあるコミットメッセージでコミットします。
# 変更ファイルを確認git status# modified:modules/s3/variables.tf# modified:modules/s3/main.tf# modified:environments/dev/main.tf# S3 モジュール関連を一括 stagegit add modules/s3/git add environments/dev/main.tf# Conventional Commits 形式でコミットgit commit -m "feat: add tags variable to S3 module- Add tags variable with default empty map- Apply tags via merge() with ManagedBy=terraform label- Update dev environment to pass Environment/Team/CostCenter tags"リモートへプッシュし、PR を作成します。
# リモートへプッシュgit push origin feature/add-s3-tags# GitHub CLI で PR 作成(--fill でコミットメッセージを自動入力)gh pr create --fill# または詳細指定で作成gh pr create \ --title "feat: add tags variable to S3 module" \ --body "## 概要S3 モジュールに tags 変数を追加し、全環境でタグ付けを統一管理できるようにします。## 変更内容- \`modules/s3/variables.tf\`: tags 変数追加- \`modules/s3/main.tf\`: merge() で tags を付与- \`environments/dev/main.tf\`: タグ値を渡すように更新## テスト- \`terraform plan\` で既存リソースの再作成なし(1 to change のみ)を確認"GitHub の PR 画面で Squash and merge を選択してマージします。Squash merge を選ぶ理由を確認しておきましょう。
| マージ方法 | main の履歴 | 適した場面 |
|---|---|---|
| Merge commit | feature ブランチの全コミット + マージコミットが追加 | レビュー履歴を完全に残したい場合 |
| Squash and merge | feature ブランチ全体が 1 コミットに圧縮 | 作業途中のコミットを整理したい場合(GitHub Flow 推奨) |
| Rebase and merge | feature ブランチのコミットをそのまま linear に追加 | 全コミットを残しつつ linear 履歴にしたい場合 |
GitHub Flow では Squash and merge が標準です。feature ブランチ内の細かい作業コミット(「typo fix」「wip」など)を圧縮し、main に 1 つの意味のある変更単位として記録できます。
4-4. main にマージ後の状態確認
PR をマージしたら、ローカルで main を最新化して状態を確認します。
# main へ切り替えて最新を取得git switch maingit pull# マージされたコミットを確認git log --oneline -5# a3f2c1d feat: add tags variable to S3 module (#3)# 9b1e4a2 feat: add tfstate remote backend with S3 and DynamoDB (#2)# 3c08b5f initial: add terraform-git-handson repository structure (#1)Squash merge により、feature ブランチ内の複数コミットが 1 行にまとまっていることがわかります。
不要になった feature ブランチをローカルとリモートから削除します。
# ローカルブランチ削除git branch -d feature/add-s3-tags# リモートブランチ削除(GitHub の PR merge 後に自動削除設定がない場合)git push origin --delete feature/add-s3-tags# ブランチ一覧でクリーンアップ確認git branch -a# * main# remotes/origin/mainこれで GitHub Flow の 1 サイクルが完了です。main ブランチのみが残り、クリーンな状態になっています。
GitHub の Automatically delete head branches 設定(リポジトリ Settings → General → Pull Requests)を有効にすると、PR マージ時にリモートの feature ブランチが自動削除されるため、git push origin --delete の手動実行が不要になります。
# GitHub CLI でリポジトリ設定を確認gh repo view --json deleteBranchOnMerge# { "deleteBranchOnMerge": true }# 有効化する場合gh repo edit --delete-branch-on-mergeマージ後は必ずローカルブランチも掃除する癖をつけましょう。
# マージ済みローカルブランチを一括確認git branch --merged main# feature/add-s3-tags(マージ済み)# マージ済みブランチをまとめて削除(main は除外)git branch --merged main | grep -v "^\* main" | xargs git branch -d4-5. GitHub Flow の弱点:main = 本番前提の危うさ
GitHub Flow はシンプルさが最大の強みですが、main が常に本番デプロイ可能であることを前提とするため、以下の状況では摩擦が生じます。
問題① ステージング環境でのテストができない
GitHub Flow の前提:feature → main → 即時本番デプロイステージング環境を挟む場合:feature → main → staging apply → (QA通過後) → prod apply↑mainが即時デプロイ可能でなくなるTerraform では environments/staging/ と environments/prod/ が別 tfstate を持つため、main へのマージが staging に apply されても prod への apply は手動ゲートが必要になります。この「ステージングは自動、本番は手動」という運用は GitHub Flow の想定外です。
問題② 長期間かかる変更との相性が悪い
大規模なリファクタリング(例: modules の再設計)は feature ブランチが長寿命になりがちです。GitHub Flow は短命ブランチを前提とするため、長期ブランチは main との乖離が広がりマージ時のコンフリクトが増加します。
問題③ 環境別 tfstate との摩擦
# prod 環境への誤 apply リスクcd environments/prodgit pull # main の最新を取得terraform apply # 意図せず prod に適用GitHub Flow では main へのマージ = 全環境への apply 許可を意味しますが、Terraform では環境ごとに apply の意図を明示する必要があります。CI/CD パイプラインでブランチと環境を紐づけることで解決できますが、それは Section 6 および第5弾の内容です。
4-6. GitHub Flow 適用の成功条件チェックリスト
- [ ] チームは 5 名以下か(6 名以上は GitFlow の検討を)
- [ ] 本番環境が 1 つ(または dev + prod の 2 環境のみ)か
- [ ] main へのマージ後、24 時間以内にデプロイできる運用か
- [ ] PR レビューを必須化する Branch Protection Rule が設定済みか
- [ ]
terraform planを PR CI で自動実行できるか(なければ手動でも可) - [ ] feature ブランチの寿命を 1 週間以内に抑えられるか
- [ ] hotfix 手順(main から直接切って即 merge)が全員に共有されているか
1 つでも「×」があれば GitFlow(5 名以上・複数環境)または trunk-based(CI/CD 必須)への移行を検討してください。
Section 5: [ハンズオン] GitFlow で develop/release/hotfix を回す
5-1. GitFlow 適用の判断
GitFlow が適切な状況は以下のとおりです。
| 条件 | GitFlow 推奨 |
|---|---|
| チーム規模 | 3 名以上(コードレビューが分業できる) |
| 環境数 | staging + production の 2 環境以上 |
| リリースサイクル | 週次・月次など定期リリース |
| QA プロセス | ステージングでの受け入れテストが必要 |
| Terraform 運用 | develop = staging apply、main = production apply |
terraform-git-handson/ では environments/dev/(develop 追跡)と environments/prod/(main 追跡)の 2 環境で GitFlow を実演します。
5-2. develop ブランチの初期化
まず main から develop ブランチを作成し、リモートにプッシュします。
# main ブランチを最新化git switch maingit pull origin main# develop ブランチを作成git switch -c develop# リモートへプッシュし、上流として設定git push -u origin developdevelop ブランチを staging 環境の apply ブランチとして位置づけます。GitHub の Branch Protection Rules で develop ブランチにも PR 必須設定を追加しておきましょう。
# GitHub CLI で develop への Branch Protection を設定gh api repos/:owner/:repo/branches/develop/protection \ --method PUT \ --field required_status_checks='{"strict":true,"contexts":[]}' \ --field enforce_admins=false \ --field required_pull_request_reviews='{"required_approving_review_count":1}' \ --field restrictions=nullブランチ構成を確認します。
git branch -a# * develop#main#remotes/origin/develop#remotes/origin/main5-3. feature → develop マージ
RDS モジュールの骨格を feature ブランチで作成し、develop へマージします。
# develop から feature ブランチを切る(main からではない)git switch -c feature/add-rds-module developmodules/rds/ ディレクトリを作成し、最小構成の Terraform ファイルを用意します。
mkdir -p modules/rds# modules/rds/main.tfresource "aws_db_instance" "this" { identifier = var.identifier engine= "mysql" engine_version = "8.0" instance_class = var.instance_class allocated_storage = var.allocated_storage db_name = var.db_name username = var.username password = var.password skip_final_snapshot = true tags = var.tags}# modules/rds/variables.tfvariable "identifier" { type = string description = "The name of the RDS instance."}variable "instance_class" { type = string default = "db.t3.micro"}variable "allocated_storage" { type = number default = 20}variable "db_name" { type = string}variable "username" { type = string}variable "password" { type= string sensitive = true}variable "tags" { type = map(string) default = {}}# modules/rds/outputs.tfoutput "db_endpoint" { value = aws_db_instance.this.endpoint description = "The connection endpoint of the RDS instance."}output "db_name" { value = aws_db_instance.this.db_name}コミットして develop へマージします。
# ファイルを stage してコミットgit add modules/rds/git commit -m "feat: add RDS module skeleton"# develop へ切り替えgit switch develop# --no-ff でマージコミットを明示的に作成git merge --no-ff feature/add-rds-module \ -m "Merge feature/add-rds-module into develop"# feature ブランチを削除git branch -d feature/add-rds-module--no-ff (no fast-forward)フラグが GitFlow では重要です。fast-forward が発生すると feature ブランチが存在したことが履歴から消えてしまいます。--no-ff によってマージコミットが残り、「どの feature をいつ develop に取り込んだか」が明確になります。
# develop のログで --no-ff マージコミットを確認git log --oneline --graph -6# *c4f8a12 Merge feature/add-rds-module into develop# |\# | * b3d7e91 feat: add RDS module skeleton# |/# * a3f2c1d feat: add tags variable to S3 module (#3)develop をリモートへプッシュします。
git push origin develop5-4. release/v1.0.0 切り出し → QA → main + develop へ戻す
develop の変更がテスト可能な状態になったら、release ブランチを切り出して QA を実施します。
# develop から release ブランチを切るgit switch -c release/v1.0.0 developrelease ブランチでは機能追加は行わず、バージョン番号更新・CHANGELOG 整備・バグ修正のみを行います。
# バージョン管理ファイルを更新(例: version.txt)echo "1.0.0" > version.txt# CHANGELOG.md を作成・更新cat > CHANGELOG.md << 'EOF'# Changelog## [v1.0.0] - 2026-04-15### Added- S3 module: tags variable support with merge() pattern- RDS module: skeleton with db.t3.micro configuration### Notes- All modules tested with Terraform 1.7.x- staging apply confirmed without errorsEOFgit add version.txt CHANGELOG.mdgit commit -m "chore: bump version to v1.0.0"release ブランチを staging 環境に apply して QA を実施します。
# staging 環境で terraform apply を実施cd environments/devterraform initterraform apply -auto-approve# QA チェック(実際のプロジェクトではテスト自動化を推奨)# - S3 バケットのタグが正しく付与されているか# - RDS モジュールの plan が期待通りかcd ../..QA が通過したら、release ブランチを main と develop の両方へマージします。
# main へマージgit switch maingit merge --no-ff release/v1.0.0 \ -m "Release v1.0.0"# タグを付与(-a で注釈付きタグを作成)git tag -a v1.0.0 -m "Release v1.0.0"# develop へも戻す(release 中のバグ修正を取り込むため)git switch developgit merge --no-ff release/v1.0.0 \ -m "Back-merge release/v1.0.0 into develop"# release ブランチを削除git branch -d release/v1.0.0# main と develop を一緒にプッシュ(--tags でタグも送信)git push origin main develop --tags両方へのマージが GitFlow で最も重要なステップです。main だけにマージして develop を忘れると、release 中の修正が develop に取り込まれず、次の release で同じバグが再発します。
# main と develop の状態確認git log --oneline --graph main develop -8# *e5a1f23 (HEAD -> develop, origin/develop) Back-merge release/v1.0.0 into develop# *d4c9b12 (tag: v1.0.0, origin/main, main) Release v1.0.05-5. hotfix/v1.0.1 緊急修正
本番に重大なバグが発見された場合、develop を経由せず main から直接 hotfix ブランチを切ります。
# main から hotfix ブランチを作成git switch -c hotfix/v1.0.1 mainRDS モジュールで skip_final_snapshot = true がデフォルトになっていたことに気づき、本番では危険なため修正します。
# modules/rds/variables.tf に skip_final_snapshot を追加variable "skip_final_snapshot" { type = bool default = false description = "Set to true only for non-production environments."}# modules/rds/main.tf の修正resource "aws_db_instance" "this" { identifier = var.identifier engine= "mysql" engine_version = "8.0" instance_class = var.instance_class allocated_storage = var.allocated_storage db_name = var.db_name username = var.username password = var.password skip_final_snapshot = var.skip_final_snapshot # ハードコードから変数化 tags = var.tags}CHANGELOG を更新してコミットします。
cat >> CHANGELOG.md << 'EOF'## [v1.0.1] - 2026-04-15### Fixed- RDS module: skip_final_snapshot defaults to false to prevent accidental data lossEOFgit add modules/rds/ CHANGELOG.mdgit commit -m "fix: set skip_final_snapshot default to false in RDS module"hotfix を main と develop の両方へマージしてタグを付与します。
# main へマージgit switch maingit merge --no-ff hotfix/v1.0.1 \ -m "Hotfix v1.0.1"git tag -a v1.0.1 -m "Hotfix v1.0.1"# develop へも戻すgit switch developgit merge --no-ff hotfix/v1.0.1 \ -m "Back-merge hotfix/v1.0.1 into develop"# hotfix ブランチを削除git branch -d hotfix/v1.0.1# プッシュgit push origin main develop --tags# タグ一覧で確認git tag -l "v*"# v1.0.0# v1.0.15-6. Terraform での GitFlow 運用例(develop=staging apply / main=production apply)
GitFlow のブランチと Terraform 環境を次のように対応付けます。
| ブランチ | Terraform apply 対象 | タイミング |
|---|---|---|
develop | environments/dev/ または environments/staging/ | PR merge 後に CI が自動 apply |
release/* | environments/staging/ | release ブランチ作成時に CI が apply(QA 用) |
main | environments/prod/ | merge 後に CI が apply(または手動承認後) |
hotfix/* | environments/staging/ → 承認後に prod | hotfix マージ前の最終確認 |
この対応付けを実現する GitHub Actions ワークフローの概略は第5弾で実装しますが、運用ルールとして明文化しておきます。
# .github/workflows/terraform-apply.yml(概略)# 第5弾で詳細実装on: push: branches:- develop# staging apply- main# production applyjobs: apply: environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }} steps:- uses: actions/checkout@v4- name: Determine environment directory run: | if [ "${{ github.ref }}" = "refs/heads/main" ]; thenecho "ENV_DIR=environments/prod" >> $GITHUB_ENV elseecho "ENV_DIR=environments/dev" >> $GITHUB_ENV fi- name: Terraform Apply run: | cd $ENV_DIR terraform init terraform apply -auto-approve5-7. GitFlow の注意点(ブランチ数肥大化・マージ地獄の回避策)
GitFlow の最大の課題はブランチ数の増加とマージコンフリクトです。実際の運用で直面する問題と対策を整理します。
問題① feature ブランチの長寿命化
feature ブランチが 2 週間以上生き続けると、develop との乖離が広がり、マージ時に大量のコンフリクトが発生します。
# 危険なサイン: develop との乖離を確認git log develop..feature/long-lived-branch --oneline | wc -l# 50+ コミットの差分は要注意# 定期的に develop の変更を feature に取り込む(rebase または merge)git switch feature/long-lived-branchgit rebase develop# またはgit merge develop問題② release ブランチの両方へのマージ忘れ
# チェックコマンド: release ブランチが develop に取り込まれているか確認git branch --merged develop | grep release# 何も表示されなければ back-merge 忘れ問題③ hotfix の develop 反映忘れ
# hotfix が develop に含まれているか確認git log develop..hotfix/v1.0.1 --oneline# 何も表示されれば OK(空 = 全コミットが develop に含まれている)git-flow CLI ツールによる自動化
これらの手順ミスを防ぐために git-flow CLI ツールが有効です。
# インストール(macOS)brew install git-flow-avh# リポジトリで初期化(デフォルト設定をそのまま使用)git flow init -d# feature ブランチの開始git flow feature start add-alb-module# → develop から feature/add-alb-module を自動作成# feature ブランチの終了git flow feature finish add-alb-module# → develop への --no-ff マージ + ブランチ削除を自動実行# release の開始git flow release start 1.2.0# → develop から release/1.2.0 を作成# release の終了git flow release finish 1.2.0# → main と develop へのマージ + タグ付けを自動実行# hotfix の開始・終了git flow hotfix start 1.1.1git flow hotfix finish 1.1.1# → main と develop へのマージ + タグ付けを自動実行git flow feature finish は --no-ff マージと back-merge を自動化するため、手順ミスを大幅に削減できます。
GitFlow 運用を始める前の Branch Protection 設定
GitFlow では main と develop の両方を保護ブランチとして設定します。
# develop ブランチの保護設定を確認gh api repos/:owner/:repo/branches/develop/protection# main ブランチの保護設定確認gh api repos/:owner/:repo/branches/main/protection最低限設定すべき保護ルールを整理します。
| 設定項目 | main | develop | 理由 |
|---|---|---|---|
| Require PR before merging | ✅ 必須 | ✅ 必須 | 直接 push を防ぐ |
| Required approvals | 1 名以上 | 1 名以上 | コードレビュー担保 |
| Require status checks | ✅ 必須 | ✅ 必須 | CI 通過を保証 |
| Restrict who can push | リリース担当のみ | 全開発者 | main への誤 push 防止 |
| Allow force pushes | ❌ 禁止 | ❌ 禁止 | 履歴書き換え防止 |
# GitHub CLI で main の保護ルールを設定gh api repos/:owner/:repo/branches/main/protection \ --method PUT \ --field required_status_checks='{"strict":true,"contexts":["terraform-plan"]}' \ --field enforce_admins=true \ --field required_pull_request_reviews='{"required_approving_review_count":1,"dismiss_stale_reviews":true}' \ --field restrictions=null \ --field allow_force_pushes=false \ --field allow_deletions=falseこれで main への直接 push が不可能になり、すべての変更が PR 経由となります。GitFlow の「リリースブランチだけが main にマージできる」という原則を技術的に強制できます。
Section 6: [ハンズオン] Trunk-based + Feature Flags で Terraform を継続的デプロイ
6-1. Trunk-based 適用の判断
trunk-based development が適切な状況は次のとおりです。
| 条件 | 状態 |
|---|---|
| CI/CD の成熟度 | 高い(全 PR に自動テスト・plan が必須) |
| チームの規律 | 高い(小さい変更を高頻度で main に merge できる) |
| デプロイ頻度 | 日次〜随時 |
| Feature Flags 基盤 | あり(Terraform では count/for_each で代替可能) |
| ブランチ寿命 | 24 時間以内(または short-lived branches) |
GitHub Flow との違いはデプロイの頻度と CI/CD の自動化度です。GitHub Flow は PR ごとにデプロイするのに対し、trunk-based では main への merge が即時 CI/CD パイプラインをトリガーして本番に反映されます。
Feature Flags が必要な理由
未完成の機能を main に直接マージするために、コードは存在するが機能は無効化する仕組みが必要です。Terraform では count または for_each を使った条件分岐で Feature Flags を実現します。
6-2. Feature Flags の Terraform 実装
Terraform における Feature Flags の基本パターンを実装します。
まず変数定義でフラグを宣言します。
# variables.tf(ルートモジュール)variable "enable_rds" { type = bool default = false description = "Feature flag: enable RDS module deployment. Set to true when ready to provision."}variable "enable_alb" { type = bool default = false description = "Feature flag: enable Application Load Balancer deployment."}variable "enable_cloudfront" { type = bool default = false description = "Feature flag: enable CloudFront distribution."}次に main.tf でフラグを参照します。
# main.tf(ルートモジュール)module "s3" { source= "./modules/s3" bucket_name = "my-app-${var.env}-${var.env_suffix}" tags = local.common_tags}# Feature Flag: RDS は enable_rds = true の場合のみデプロイmodule "rds" { count = var.enable_rds ? 1 : 0 source = "./modules/rds" identifier = "my-app-${var.env}" instance_class = var.rds_instance_class allocated_storage = 20 db_name = "appdb" username = var.db_username password = var.db_password tags = local.common_tags}count = var.enable_rds ? 1 : 0 のパターンで、enable_rds = false のとき count = 0 となりリソースが作成されません。enable_rds = true にすると count = 1 になりリソースが作成されます。
フラグが無効のときの terraform plan 出力を確認します。
# dev 環境の tfvars でフラグを確認cat environments/dev/terraform.tfvars# enable_rds = falsecd environments/devterraform plan# RDS に関するリソースが plan に現れないことを確認# No changes. Your infrastructure matches the configuration.# (S3 のみ存在する場合)複数のフラグを locals で管理すると見通しが良くなります。
# locals.tflocals { # Feature flags 一覧(管理しやすいように集約) features = { rds= var.enable_rds alb= var.enable_alb cloudfront = var.enable_cloudfront }}for_each を使ったより柔軟な Feature Flag パターン
count の代わりに for_each を使うと、複数リージョンへの条件付きデプロイなど、より複雑なユースケースに対応できます。
# variables.tf(for_each パターン)variable "enabled_modules" { type = set(string) default = [] description = "Set of modules to enable. Possible values: rds, alb, cloudfront"}# main.tf(for_each パターン)module "rds" { for_each = contains(var.enabled_modules, "rds") ? toset(["this"]) : toset([]) source= "./modules/rds" identifier = "my-app-${var.env}" instance_class = "db.t3.micro" allocated_storage = 20 db_name = "appdb" username = var.db_username password = var.db_password tags = local.common_tags}# environments/dev/terraform.tfvars(フラグを有効化する例)enabled_modules = ["rds", "alb"]for_each パターンは count パターンに比べて参照構文が module.rds["this"] になる点が異なりますが、将来的に複数インスタンスへの拡張が必要な場合は for_each の方が柔軟です。小規模プロジェクトでは count パターンの方がシンプルで読みやすいため、まずは count から始めることを推奨します。
6-3. 未完成コードを main に直接マージするフロー
trunk-based では、機能が完成していなくても Feature Flag で無効化したうえで main へ merge します。
# main から short-lived ブランチを作成(24 時間以内にマージが目標)git switch maingit pullgit switch -c feat/rds-flagRDS モジュールの Feature Flag を追加してコミットします。
# Feature Flag 付きコードを追加(上記の variables.tf / main.tf の変更)git add variables.tf main.tf locals.tfgit commit -m "feat: add RDS module behind feature flag (disabled)Feature flag: enable_rds defaults to false.RDS module code is present but not deployed until flag is enabled.This allows safe incremental development on trunk."PR を作成してマージします。
git push origin feat/rds-flaggh pr create --fill# CI で terraform plan が自動実行されることを確認# plan の出力に RDS リソースが含まれないことを確認# GitHub UI で Squash and merge# またはgh pr merge --squash --delete-branchマージ後に main を最新化して確認します。
git switch maingit pullgit log --oneline -3# 7f3a21c feat: add RDS module behind feature flag (disabled)# e5a1f23 Back-merge release/v1.0.0 into develop# ...6-4. フラグ有効化のPR(enable_rds = true)
RDS の実装が完成し、ステージングでの検証も終わったら、フラグを有効化する PR を別途作成します。
git switch -c feat/enable-rds# environments/prod/terraform.tfvars(フラグを有効化)enable_rds = true# RDS 固有の設定rds_instance_class = "db.t3.small"db_username = "appuser"git add environments/prod/terraform.tfvarsgit commit -m "feat: enable RDS module in productionFeature flag enable_rds set to true for prod environment.Staging validation completed: db.t3.micro tested for 2 weeks.Upgrading to db.t3.small for production capacity."git push origin feat/enable-rdsgh pr create --fillフラグ有効化 PR のレビューポイントを明確にします。
| レビュー項目 | 確認内容 |
|---|---|
terraform plan の差分 | RDS リソースが新規作成(to add)になっているか |
skip_final_snapshot | 本番では false になっているか |
password の扱い | tfvars にハードコードされていないか(Secrets Manager 参照を推奨) |
| コスト試算 | インスタンスクラスのコストが予算内か |
| バックアップ設定 | backup_retention_period が設定されているか |
PR がレビューを通過したら main へマージし、CI/CD が自動で terraform apply を実行します。
# フラグ有効化後の terraform plan 出力イメージ# Plan: 3 to add, 0 to change, 0 to destroy.## + aws_db_instance.this[0]# + aws_db_subnet_group.this[0]# + aws_security_group.rds[0]6-5. trunk-based の前提条件(CI/CD 必須・第5弾 GHA OIDC 布石)
trunk-based development を安全に運用するには、以下の CI/CD 基盤が必須です。
| 要件 | 内容 | 第5弾での実装 |
|---|---|---|
PR 時 terraform plan | main への影響を事前確認 | GHA + OIDC 認証 |
main push 時 terraform apply | 自動デプロイ(または手動承認後) | GHA Environment + OIDC |
| tfstate ロック | 並列 apply の衝突防止 | S3 + DynamoDB(第2弾実装済み) |
| ブランチ保護 | PR 必須・CI 通過必須 | GitHub Branch Protection |
| Secrets 管理 | tfvars に機密情報を書かない | GitHub Actions Secrets / AWS Secrets Manager |
第5弾では GitHub Actions + OIDC(OpenID Connect) で AWS に対して一時認証情報を使った安全な Terraform apply を実装します。OIDC を使うと AWS アクセスキーを GitHub Secrets に保管する必要がなく、セキュリティが大幅に向上します。
# 第5弾で実装する OIDC 認証の概略# .github/workflows/terraform.ymlpermissions: id-token: write# OIDC トークン取得に必要 contents: readsteps: - name: Configure AWS credentials via OIDC uses: aws-actions/configure-aws-credentials@v4 with:role-to-assume: arn:aws:iam::123456789012:role/github-actions-terraformaws-region: ap-northeast-1# アクセスキー不要。GitHub が発行した JWT を AWS が検証trunk-based の導入ステップとして、まず GitHub Flow を安定運用し、CI/CD を整備してから trunk-based へ移行することを強く推奨します。
6-6. 3 戦略ハンズオン完了 — Terraform 運用での選定フローチャート
Section 4〜6 で 3 つのブランチ戦略をハンズオンで体験しました。実際のプロジェクトでどの戦略を選ぶかの判断フローをテーブルで整理します。
ステップ① チーム規模で絞り込む
| チーム規模 | 第一候補 | 理由 |
|---|---|---|
| 1〜2 名 | GitHub Flow | シンプルさが最優先。develop ブランチの維持コストが無駄 |
| 3〜5 名 | GitHub Flow または GitFlow | 環境数と QA プロセスで判断(下記 ② へ) |
| 6 名以上 | GitFlow | PR レビューの並列化、feature 作業の独立性が必要 |
ステップ② 環境数で絞り込む
| 環境数 | 推奨戦略 |
|---|---|
| 1 環境(dev のみ) | GitHub Flow 確定 |
| 2 環境(dev + prod) | GitHub Flow(ステージングレスなら) |
| 3 環境以上(dev + staging + prod) | GitFlow(staging での QA が必要) |
ステップ③ CI/CD 成熟度で絞り込む
| CI/CD の状態 | 推奨戦略 |
|---|---|
| CI なし | GitHub Flow(手動 plan/apply) |
| PR に terraform plan CI あり | GitHub Flow または GitFlow |
| main push で自動 apply あり + テスト充実 | trunk-based 検討可 |
総合判断マトリクス
| チーム規模 | 環境数 | CI/CD | 推奨戦略 |
|---|---|---|---|
| 1〜2 名 | 1〜2 | なし〜基礎 | GitHub Flow |
| 3〜5 名 | 2 | 基礎〜中級 | GitHub Flow |
| 3〜5 名 | 3+ | 中級 | GitFlow |
| 6 名以上 | 3+ | 中級〜高度 | GitFlow |
| 任意 | 任意 | 高度(自動 apply + Feature Flags 基盤) | trunk-based |
Terraform 運用では「GitHub Flow から始めて、チームと環境が成長したら GitFlow へ移行、CI/CD が整ったら trunk-based を検討」という段階的アプローチが最も現実的です。
- [ ] feature/add-s3-tags ブランチを作成し、Squash merge で main に取り込める
- [ ] GitHub Flow の「main = 常にデプロイ可能」原則と、その制約を説明できる
- [ ] GitFlow で develop ブランチを初期化し、feature → develop の –no-ff マージができる
- [ ] release ブランチを main と develop の両方へマージし、タグを付与できる
- [ ] hotfix ブランチを main から切り、main と develop に戻せる
- [ ] Terraform の Feature Flag パターン(count = var.enable_xxx ? 1 : 0)を実装できる
- [ ] trunk-based development に必要な CI/CD 要件を 3 つ以上挙げられる
- [ ] 3 戦略の選定マトリクスを参照して、自分のプロジェクトに適した戦略を選べる
Section 7 からは環境別 tfstate の設計とモジュールの git tag バージョニングに進みます。複数ブランチ・複数環境での tfstate 競合を根本から解決する設計パターンを学びます。
Section 7: Terraform 環境別ディレクトリ × ブランチ戦略 の設計
7-1. 環境分離の 3 パターン比較
Terraform で複数環境(dev / stg / prod)を管理するアプローチは主に 3 つ存在します。ブランチ戦略と組み合わせる前に、まず各パターンの特徴を把握しましょう。
| パターン | ディレクトリ構成 | 特徴 | 推奨シーン |
|---|---|---|---|
| ディレクトリ分離 | envs/{dev,stg,prod}/ | 明示的・安全・tfstate が独立 | 推奨(チーム規模問わず) |
| Terraform Workspace | 単一ディレクトリ + terraform workspace | シンプルだが state 管理が複雑化しやすい | 小規模・単純構成のみ |
| ブランチ分離 | dev / stg / prod ブランチ | 危険 — アンチパターン | 非推奨 |
本記事では ディレクトリ分離 × GitHub Flow の組み合わせを推奨構成として解説します。
各パターンの Terraform 運用観点での詳細な比較は以下の通りです。
| 評価軸 | ディレクトリ分離 | Workspace | ブランチ分離 |
|---|---|---|---|
| tfstate の独立性 | ◎ 完全分離 | △ workspace 単位 | ✕ ブランチ間で混在 |
| CI/CD との相性 | ◎ paths フィルタで制御可 | △ workspace 切り替えが必要 | ✕ ブランチ切り替えが複雑 |
| マージ競合リスク | ◎ なし(main 一本) | ◎ なし | ✕ 環境間コンフリクト多発 |
| 学習コスト | ◎ 低い(直感的) | △ 中程度 | △ 高い(危険さを理解要) |
| 大規模チーム適性 | ◎ | △ | ✕ |
7-1-b. Terraform Workspace の落とし穴
Workspace は小規模プロジェクトでは便利ですが、以下の制限を理解した上で採用してください。
# Workspace を使った環境切り替え(小規模のみ推奨)terraform workspace list# * default#dev#stg#prod# workspace 作成terraform workspace new devterraform workspace new stgterraform workspace new prod# workspace 切り替えterraform workspace select devterraform plan # dev 環境の plan# 現在の workspace を確認terraform workspace show# dev# main.tf: workspace 名を使って環境を切り替える例locals { env = terraform.workspace # "dev" / "stg" / "prod" # 環境ごとの設定をマップで定義 config = { dev = { instance_type = "t3.micro", min_size = 1 } stg = { instance_type = "t3.small", min_size = 1 } prod = { instance_type = "t3.medium", min_size = 2 } }}resource "aws_instance" "app" { instance_type = local.config[local.env].instance_type # ...}Workspace の問題点は、すべての環境が同一ディレクトリの同一コードを共有するため、環境ごとに異なるバックエンド設定(S3 バケット、DynamoDB テーブル)や異なる .tf ファイルを持てないことです。本番環境だけリソース構成を大きく変えたいケースには対応できません。
# Workspace の state ファイルは S3 上で以下のように格納される# s3://my-tfstate-bucket/env:/dev/terraform.tfstate# s3://my-tfstate-bucket/env:/stg/terraform.tfstate# s3://my-tfstate-bucket/env:/prod/terraform.tfstate# ディレクトリ分離の場合(推奨)# s3://my-tfstate-bucket/envs/dev/terraform.tfstate# s3://my-tfstate-bucket/envs/stg/terraform.tfstate# s3://my-tfstate-bucket/envs/prod/terraform.tfstate# どちらも独立しているが、ディレクトリ分離のほうが# backend 設定・providers・変数ファイルを環境ごとに完全に分離できる7-2. ブランチ分離アンチパターン — なぜ危険か
「環境ごとにブランチを切る」アプローチは一見わかりやすいですが、Terraform 運用では深刻な問題を引き起こします。
# アンチパターン: 環境ブランチに直接 push して applygit switch prod-branchterraform apply# → main との差分が不明なまま本番に適用してしまう# 問題① — tfstate drift の発生# dev-branch と prod-branch の .tf ファイルに差異が蓄積し、# どちらが "正" の構成か判別不能になる# 問題② — マージ地獄# 3 環境分のコンフリクトを手動解消し続けることになるgit merge dev-branch # stg-branch 上で実施# Auto-merging main.tf# CONFLICT (content): Merge conflict in main.tf# Automatic merge failed; fix conflicts and then commit the result.# 問題③ — 本番誤 apply のリスクgit switch prod-branch # ← dev 作業中に誤ってスイッチterraform apply# ← 未検証変更が本番に当たるブランチ分離は tfstate の一貫性を破壊します。各環境の state ファイルがブランチをまたいで整合性を保つ仕組みがなく、「どのブランチの terraform.tfstate が正しいか」が誰にもわからない状態に陥ります。Terraform では コードとステートは常に 1:1 で対応していなければなりません。
7-3. 推奨構成: ディレクトリ分離 + GitHub Flow
推奨するのは「すべての環境コードを main ブランチ上で管理し、ディレクトリで環境を分離する」構成です。
terraform-git-handson/├── envs/│├── dev/││├── main.tf││├── variables.tf││├── outputs.tf││└── terraform.tfvars│├── stg/││├── main.tf││├── variables.tf││├── outputs.tf││└── terraform.tfvars│└── prod/│ ├── main.tf│ ├── variables.tf│ ├── outputs.tf│ └── terraform.tfvars└── modules/ ├── vpc/ │├── main.tf │├── variables.tf │└── outputs.tf └── s3/ ├── main.tf ├── variables.tf └── outputs.tf# ディレクトリ構成を実際に作成cd terraform-git-handsonmkdir -p envs/{dev,stg,prod} modules/{vpc,s3}# 各環境に main.tf を配置(dev の例)cat > envs/dev/main.tf << 'EOF'terraform { required_version = ">= 1.7" required_providers { aws = {source = "hashicorp/aws"version = "~> 5.0" } } backend "s3" { bucket = "my-tfstate-bucket" key = "envs/dev/terraform.tfstate" region = "ap-northeast-1" }}module "vpc" { source = "../../modules/vpc" env = "dev"}EOF# terraform.tfvars で環境固有値を管理cat > envs/dev/terraform.tfvars << 'EOF'env = "dev"vpc_cidr = "10.0.0.0/16"instance_type= "t3.micro"EOFこの構造により、各環境の tfstate は独立した S3 パス(envs/dev/terraform.tfstate / envs/stg/terraform.tfstate / envs/prod/terraform.tfstate)に保存され、競合が発生しません。
# 確認: 現在のディレクトリ構成find . -name "*.tf" | sort# ./envs/dev/main.tf# ./envs/dev/variables.tf# ./envs/prod/main.tf# ./envs/prod/variables.tf# ./envs/stg/main.tf# ./envs/stg/variables.tf# ./modules/s3/main.tf# ./modules/vpc/main.tf7-4. Gated Promotion フロー
環境昇格(dev → stg → prod)は段階的に、かつ必ず PR を経由して実施します。
feature ブランチ ↓ PR → maindev apply(terraform plan → approve → apply) ↓ 動作確認 OKstg PR → main へのマージ後 stg apply ↓ ステージング検証 OKprod PR → main へのマージ後 prod apply# Step 1: feature ブランチで開発git switch -c feature/add-s3-lifecycle# main.tf を編集して S3 ライフサイクルルールを追加# ... 編集 ...git add envs/dev/main.tfgit commit -m "feat(dev): add S3 lifecycle rule for log rotation"# Step 2: dev 環境で plan → applycd envs/devterraform initterraform plan -out=dev.tfplanterraform apply dev.tfplan# Step 3: PR 作成 → レビュー → main マージgh pr create \ --title "feat: add S3 lifecycle rule" \ --body "dev 確認済み。tfplan 結果を添付します。" \ --base main# Step 4: stg 昇格(PR マージ後)cd envs/stgterraform initterraform plan -out=stg.tfplan# ← レビュアーが plan 結果を確認terraform apply stg.tfplan# Step 5: prod 昇格(stg 検証完了後)cd envs/prodterraform initterraform plan -out=prod.tfplan# ← シニアエンジニアによる最終承認terraform apply prod.tfplan各段階で terraform plan の出力をレビュアーが確認してから apply を実行します。これにより意図しないリソース変更が本番に届くリスクを排除できます。
各環境昇格時のレビューポイント
| 確認項目 | dev → stg | stg → prod |
|---|---|---|
Plan: N to add, M to change, K to destroy の K | 1 以下推奨 | 0 を原則とする |
| destroy 対象リソースの種別 | データリソース(RDS・S3)は要注意 | 如何なるデータリソースも禁止 |
| 変更量(M の数) | 20 以下推奨 | 10 以下推奨 |
| 実行タイミング | 平日日中 | 低トラフィック時間帯 |
# plan 結果の変更サマリーのみを抽出して確認terraform plan 2>&1 | grep "^Plan:"# Plan: 2 to add, 1 to change, 0 to destroy.# destroy が含まれている場合は詳細を確認terraform plan 2>&1 | grep -A 5 "will be destroyed"7-5. モノレポでの GitHub Flow 運用
terraform-git-handson/ のようなモノレポ(IaC コード + アプリコードが同一リポジトリ)では、変更対象を絞り込む工夫が必要です。
# モノレポのディレクトリ構成例terraform-git-handson/├── envs/ # Terraform IaC コード├── modules/ # 共通モジュール├── apps/ # アプリケーションコード(例)│├── api/│└── frontend/└── .github/ └── workflows/ ├── terraform-dev.yml# envs/dev/** 変更時のみ起動 ├── terraform-stg.yml# envs/stg/** 変更時のみ起動 └── terraform-prod.yml # envs/prod/** 変更時のみ起動# .github/workflows/terraform-dev.yml の paths フィルタ例name: Terraform Devon: push: branches: [main] paths:- 'envs/dev/**'- 'modules/**' pull_request: paths:- 'envs/dev/**'- 'modules/**'paths フィルタを活用することで、envs/dev/ に関係しない変更では dev の CI が起動しないよう制御できます。これにより、アプリコードの PR が誤って Terraform ワークフローをトリガーするといった誤作動を防止できます。
# PR に変更ファイルの一覧を表示して確認gh pr diff --name-only# Terraform 変更が含まれているかを grep で確認gh pr diff --name-only | grep "^envs/"# envs/dev/main.tfSection 7 チェックリスト
- ☐ ブランチ分離アンチパターンが危険な理由(tfstate drift・マージ地獄・誤 apply)を説明できる
- ☐
envs/{dev,stg,prod}/+modules/のディレクトリ構成を作成できる - ☐ Gated Promotion フロー(dev → stg → prod)を PR ベースで実施できる
- ☐ モノレポでの
pathsフィルタによる CI スコープ制御を設定できる - ☐ 各環境の tfstate が独立した S3 パスに分離されていることを確認できる
Section 8 では、Terraform モジュールを git tag でバージョン管理する手法を習得します。環境ディレクトリからモジュールを参照する際に、特定のバージョンを固定することでインフラの再現性を確保します。
Section 8: Terraform モジュールのバージョン管理 — git tag と source ref
8-1. モジュールの SemVer 設計
Terraform モジュールは Semantic Versioning(SemVer) でバージョン管理するのがベストプラクティスです。変更の種別によってバージョン番号を使い分けることで、利用者が安全にアップグレードできるかを判断できるようになります。
| バージョン変更 | 変更種別 | 具体例 |
|---|---|---|
v1.0.0 → v1.0.1 | patch: バグ修正・後方互換あり | タグ変数のデフォルト値修正 |
v1.0.0 → v1.1.0 | minor: 後方互換の機能追加 | 新オプション変数の追加 |
v1.0.0 → v2.0.0 | major: 破壊的変更(後方互換なし) | 変数名変更・出力削除 |
# 現在のモジュールバージョンを確認cd terraform-git-handsongit tag -l# v1.0.0# v1.0.1# v1.1.0# 最新タグを確認git describe --tags --abbrev=0# v1.1.08-2. git tag 打ち方と push
モジュールのリリースは 注釈付きタグ(annotated tag) を使います。軽量タグ(git tag v1.0.0)と異なり、注釈付きタグはタグ作成者・日時・メッセージを保持するため、いつ・誰が・何のためにリリースしたかを追跡できます。
# 注釈付きタグを作成(推奨)git tag -a v1.0.0 -m "Initial release: VPC module with basic configuration"# タグをリモートに pushgit push origin v1.0.0# 全タグの一覧確認git tag -l# v1.0.0# タグの詳細確認(タグ作成者・日時・メッセージを表示)git show v1.0.0# tag v1.0.0# Tagger: Your Name <you@example.com># Date:2026-04-15 09:00:00 +0900## Initial release: VPC module with basic configuration## commit abc1234 (HEAD -> main, tag: v1.0.0, origin/main)# Author: Your Name <you@example.com># ...# 複数タグをまとめて push する場合git push origin --tags# 特定タグのみ push(推奨: 意図したタグだけを明示的に push)git push origin v1.1.08-3. terraform source で git tag 参照
環境ディレクトリからモジュールを参照する際に ?ref=<tag> を付与することで、特定バージョンのモジュールをバージョン固定で参照できます。
# envs/dev/main.tf — HTTPS 形式でプライベートリポジトリのモジュールを参照module "vpc" { source = "git::https://github.com/YOUR_ORG/terraform-modules.git//modules/vpc?ref=v1.0.0" env= var.env vpc_cidr = var.vpc_cidr}# SSH 形式(CI/CD での deploy key 運用に推奨)module "s3" { source = "git::git@github.com:YOUR_ORG/terraform-modules.git//modules/s3?ref=v1.1.0" env = var.env bucket_prefix = var.bucket_prefix}# terraform init で git tag を参照してモジュールをダウンロードcd envs/devterraform init# 初期化後の .terraform/modules/ 構成を確認ls .terraform/modules/# modules.json vpc/ s3/# どのバージョンが使われているか確認cat .terraform/modules/modules.json | python3 -m json.tool | grep -A 3 '"Key"'# "Key": "vpc",# "Source": "git::https://github.com/YOUR_ORG/terraform-modules.git//modules/vpc?ref=v1.0.0",# "Version": "",# "Dir": ".terraform/modules/vpc"ローカルのモジュール(同一リポジトリ内)を参照する場合は ../../modules/vpc のような相対パスを使用します。外部リポジトリのモジュールをバージョン固定で参照する場合にのみ git:: プレフィックスと ?ref= を使います。
# 同一リポジトリ内モジュール(バージョン指定不要)module "vpc" { source = "../../modules/vpc" env = var.env}# 外部リポジトリのモジュール(バージョン固定必須)module "shared_vpc" { source = "git::git@github.com:YOUR_ORG/shared-modules.git//modules/vpc?ref=v2.1.0" env = var.env}8-4. プライベートリポジトリ参照時の認証
外部のプライベートリポジトリからモジュールを参照する際は、認証設定が必要です。
方法 A: SSH Deploy Key(リポジトリ単位・推奨)
# deploy key 用の SSH キーペアを生成ssh-keygen -t ed25519 -C "terraform-modules-deploy-key" -f ~/.ssh/tf_deploy_key -N ""# 公開鍵を GitHub リポジトリの Settings > Deploy keys に登録cat ~/.ssh/tf_deploy_key.pub# SSH 接続テストssh -T git@github.com# Hi YOUR_ORG/terraform-modules! You've successfully authenticated, but GitHub# does not provide shell access.# ~/.ssh/config に設定を追加cat >> ~/.ssh/config << 'EOF'Host github.com IdentityFile ~/.ssh/tf_deploy_key StrictHostKeyChecking accept-newEOF方法 B: Fine-grained Personal Access Token(PAT)
第2弾 Section 3-4 で作成した PAT を再利用できます。Contents: Read-only 権限のみで十分です。
# PAT を環境変数に設定(~/.bashrc や CI の Secrets に登録)export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"# git credential helper に PAT を渡す方法git config --global credential.helper storeecho "https://YOUR_USERNAME:${GITHUB_TOKEN}@github.com" >> ~/.git-credentials# 動作確認: プライベートリポジトリに git ls-remote でアクセスgit ls-remote https://github.com/YOUR_ORG/terraform-modules.git refs/tags/v1.0.0# abc1234... refs/tags/v1.0.0- Deploy Key: 単一リポジトリへの読み取り専用アクセスに最適。鍵が漏洩しても影響範囲が 1 リポジトリに限定される
- Fine-grained PAT: 複数リポジトリへのアクセスが必要な場合に適する。ただし PAT の有効期限管理が必要
- CI/CD(GitHub Actions)では Secrets に格納し、環境変数として渡す。PAT をハードコードしない
8-5. タグ更新の運用ルール
一度 push したタグを強制上書きすることは 禁止です。すでにそのタグを ?ref= で参照しているユーザーのロックが解除不能になり、予期しないバージョンのコードが適用されるリスクがあります。
# ❌ 禁止: 既存タグの強制上書きgit tag -f v1.0.0 # ローカルのタグを書き換えgit push --force origin v1.0.0 # リモートのタグも強制更新# → すでに v1.0.0 を参照しているユーザーのインフラに影響する# ✅ 正しい運用: 新しいパッチバージョンとして対応git tag -a v1.0.1 -m "Fix: correct S3 bucket name variable default value"git push origin v1.0.1# ✅ 利用者側の対応: source の ref を更新# envs/dev/main.tf の ref=v1.0.0 を ref=v1.0.1 に変更し PR を出す# タグの削除が必要な場合(ローカルのみ、push 前)git tag -d v1.0.0-rc1 # ローカルの未 push タグを削除# リモートのタグを削除する場合(やむを得ない場合のみ、必ず全利用者に周知)git push origin :refs/tags/v1.0.0-rc1# またはgit push origin --delete v1.0.0-rc1git push –force で既存タグを上書きすることは、チームに混乱をもたらします。 同じタグ名が異なるコミットを指すようになると、
terraform init のキャッシュとリモートの実体が乖離し、「同じコードのはずなのに plan 結果が変わる」という不可解なバグを生みます。タグは イミュータブル(不変) なものとして扱い、修正は必ず新しいバージョン番号で対応してください。8-6. CHANGELOG.md 運用(Conventional Commits 活用)
モジュールのリリースには、変更内容を記録した CHANGELOG.md の更新を必ずセットで行います。Conventional Commits 形式(feat: / fix: / docs: / chore:)を使うと、変更種別が一目でわかります。
# CHANGELOG## [1.1.0] - 2026-04-15### Added- feat: add `tags` variable to S3 module for resource tagging### Fixed- fix: correct default value for `bucket_prefix` (was empty string, now `"myapp"`)## [1.0.1] - 2026-04-10### Fixed- fix: correct S3 bucket name variable reference in outputs.tf## [1.0.0] - 2026-04-01### Added- feat: initial release of VPC module with basic configuration- feat: initial release of S3 module with versioning support# CHANGELOG 更新 → コミット → タグ → push を一連の手順で実施# Step 1: CHANGELOG.md を編集(上記の内容を追記)vim modules/CHANGELOG.md# Step 2: 変更をコミットgit add modules/CHANGELOG.mdgit commit -m "docs: update CHANGELOG for v1.1.0"# Step 3: 注釈付きタグを作成git tag -a v1.1.0 -m "Add tags variable to S3 module + fix bucket_prefix default"# Step 4: main ブランチとタグをまとめて pushgit push origin main v1.1.0# Step 5: タグが正しく push されたことを確認git ls-remote --tags origin# abc1234 refs/tags/v1.0.0# def5678 refs/tags/v1.0.1# ghi9012 refs/tags/v1.1.08-7. モジュールバージョン管理の全体フロー早見表
モジュールの開発からリリース・利用者への適用までの一連の流れを早見表にまとめます。
| フェーズ | 担当 | 操作 | コマンド |
|---|---|---|---|
| 1. 開発 | モジュール開発者 | feature ブランチで開発 | git switch -c feat/add-tags-variable |
| 2. PR | モジュール開発者 | PR 作成・レビュー依頼 | gh pr create --base main |
| 3. マージ | レビュアー | main にマージ | PR の Merge ボタン |
| 4. CHANGELOG | モジュール開発者 | 変更内容を記録 | vim CHANGELOG.md && git commit |
| 5. タグ付け | モジュール開発者 | SemVer タグを作成 | git tag -a v1.1.0 -m "..." |
| 6. Push | モジュール開発者 | main + タグを push | git push origin main v1.1.0 |
| 7. 利用者通知 | モジュール開発者 | アップグレード案内 | PR / Slack でアナウンス |
| 8. 利用者更新 | IaC 利用者 | source の ref を更新 | ref=v1.0.0 → ref=v1.1.0 |
| 9. 計画確認 | IaC 利用者 | terraform plan で差分確認 | terraform plan -out=dev.tfplan |
| 10. 適用 | IaC 利用者 | 環境ごとに段階適用 | terraform apply dev.tfplan |
# 利用者側: モジュールバージョン更新の典型的なワークフロー# Step 1: 更新ブランチを作成git switch -c chore/upgrade-vpc-module-v1.1.0# Step 2: envs/dev/main.tf の source ref を更新# ref=v1.0.0 → ref=v1.1.0sed -i 's/?ref=v1.0.0/?ref=v1.1.0/g' envs/dev/main.tf# Step 3: terraform init で新バージョンを取得cd envs/devterraform init -upgrade# Step 4: plan で差分確認terraform plan# 変更内容が想定通りかレビュー# Step 5: dev で apply → stg → prod と段階昇格terraform apply8-8. バージョン更新の自動化 — Renovate / Dependabot との連携
モジュールの新バージョンが公開されるたびに手動で ref= を更新するのは手間がかかります。Renovate や GitHub Dependabot を使うと、新バージョンのタグが push されたときに自動で PR を作成できます。
# renovate.json — Terraform git source の更新を自動検出{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:base"], "terraform": { "enabled": true }, "packageRules": [ {"matchManagers": ["terraform"],"matchUpdateTypes": ["minor", "patch"],"automerge": false,"labels": ["terraform", "dependencies"] } ]}# Renovate PR の例: 自動生成されるタイトルと内容# タイトル: chore(deps): update terraform module vpc to v1.1.0# 本文:# This PR updates the following dependency:# - git::git@github.com:YOUR_ORG/terraform-modules.git//modules/vpc#from v1.0.1 to v1.1.0## Release notes: (CHANGELOG.md から自動抽出)# ## [1.1.0] - 2026-04-15# ### Added# - feat: add tags variable to S3 module# PR をレビューして計画を確認gh pr checkout 42cd envs/dev && terraform plan# Plan: 0 to add, 1 to change, 0 to destroy.# ~ resource "aws_s3_bucket" "app" { tags = {} -> { ... } }手動更新と自動 PR 作成の使い分けは以下の通りです。
| 状況 | 推奨アプローチ |
|---|---|
| patch バージョン(バグ修正のみ) | Renovate 自動 PR → レビュー → マージ |
| minor バージョン(新機能追加) | Renovate 自動 PR → 変更内容確認 → 段階昇格 |
| major バージョン(破壊的変更) | 手動対応必須 → マイグレーションガイド参照 |
# モジュール利用者がバージョン状況を一覧確認するスクリプト例grep -rh '?ref=' envs/ | sort -u# git::git@github.com:YOUR_ORG/terraform-modules.git//modules/s3?ref=v1.0.1# git::git@github.com:YOUR_ORG/terraform-modules.git//modules/vpc?ref=v1.0.0# 最新タグと比較して古いバージョンを特定git ls-remote --tags git@github.com:YOUR_ORG/terraform-modules.git \ | grep -o 'v[0-9]*\.[0-9]*\.[0-9]*$' | sort -V | tail -5# v1.0.0# v1.0.1# v1.1.0# → modules/vpc の ref=v1.0.0 が古い(v1.1.0 に更新推奨)Section 8 チェックリスト
- ☐ SemVer の 3 段階(major / minor / patch)と Terraform モジュールへの適用を説明できる
- ☐
git tag -aで注釈付きタグを作成しgit push origin <tag>で push できる - ☐
source = "git::...?ref=v1.0.0"形式でバージョン固定参照を設定できる - ☐ deploy key と Fine-grained PAT の使い分けを説明できる
- ☐ タグの強制上書き(
git tag -f+ force push)が禁止である理由を説明できる - ☐ CHANGELOG.md を更新してからタグを打つ手順(Section 8-6)を実施できる
- ☐ モジュールバージョン更新の全体フロー 10 ステップを理解している
Section 9 では、ここまで学んだ「ブランチ戦略 × 環境別 tfstate × モジュール git tag」を組み合わせた統合ハンズオンを実施します。terraform-git-handson/ リポジトリで一連のフローを端から端まで体験することで、本番運用に直結するスキルを定着させます。
Section 7〜8 まとめ — 学習の位置づけ
本記事の Section 7〜8 では、Terraform IaC 運用を「ブランチ戦略」という視点から再整理しました。
| 学習テーマ | 習得内容 | Section |
|---|---|---|
| 環境分離パターン | ディレクトリ分離 / Workspace / ブランチ分離の比較 | 7-1 |
| アンチパターン | ブランチ分離が引き起こす tfstate drift と誤 apply | 7-2 |
| 推奨ディレクトリ構成 | envs/ + modules/ の標準レイアウト | 7-3 |
| 昇格フロー | dev → stg → prod の Gated Promotion | 7-4 |
| モノレポ設計 | paths フィルタで CI スコープを制御 | 7-5 |
| SemVer 管理 | Terraform モジュールへの semantic versioning 適用 | 8-1 |
| git tag 操作 | 注釈付きタグの作成・push・確認 | 8-2 |
| source ref 参照 | git::...?ref=vX.Y.Z によるバージョン固定 | 8-3 |
| 認証設定 | deploy key と Fine-grained PAT の使い分け | 8-4 |
| タグ運用ルール | 強制上書き禁止・新バージョンで対応 | 8-5 |
| CHANGELOG 管理 | Conventional Commits 形式での変更記録 | 8-6 |
| 自動化 | Renovate による自動バージョン更新 PR | 8-8 |
次の Section 9 では、これら全要素を組み合わせた本番さながらのハンズオンを実施します。ここまでの概念的理解を「動く手順」として体験することで、チーム開発でそのまま使えるスキルとして定着させましょう。
Section 9: 応用トピック — モノレポ vs ポリレポ・ブランチ保護深掘り・第4/5弾布石
Section 1〜8 で「3大ブランチ戦略の選定と実践」「Terraform 環境別 tfstate 設計」「モジュール git tag バージョン管理」を習得しました。Section 9 では、チーム規模が拡大したときに直面する応用課題を扱います。モノレポ vs ポリレポの設計選択・ブランチ保護ルールの深掘り・CODEOWNERS による自動レビュアー割り当て、そして第4弾(セキュリティ)・第5弾(CI/CD)への布石を紹介します。
9-1. モノレポ vs ポリレポ — Terraform モジュール観点での比較
チームが成長し、Terraform モジュールが増えてくると「全モジュールを 1 つのリポジトリに集約(モノレポ)するか、モジュールごとに独立リポジトリを持つ(ポリレポ)か」という設計判断が必要になります。
観点別比較表
| 観点 | モノレポ | ポリレポ |
|---|---|---|
| モジュール参照方法 | 相対パス ../modules/vpc | git ref git::https://github.com/...?ref=v1.2.0 |
| CI のスコープ | paths フィルタで変更ディレクトリのみ実行 | リポジトリ単位で自動スコープ |
| バージョン管理 | モノレポ全体に 1 つの git tag | モジュールごとに独立した SemVer tag |
| 依存更新の連携 | 1 PR で複数モジュールを同時変更可 | Renovate 等で自動 PR が各リポジトリに発行 |
| アクセス制御 | CODEOWNERS でディレクトリ単位に設定 | リポジトリ単位で完結(シンプル) |
| 推奨チーム規模 | 小〜中規模チーム(共通基盤の共有が容易) | 大規模チーム / 独立性の高いモジュール |
| onboarding コスト | リポジトリが 1 つで把握しやすい | リポジトリ数が増えると見通しが悪くなる |
| ブランチ戦略との相性 | trunk-based / GitHub Flow が相性◎ | 各リポジトリで独立した戦略を選択できる |
Terraform モジュールへの影響
モノレポの場合、modules/ ディレクトリに複数モジュールを集約し、相対パスで参照します。
terraform-git-handson/├── envs/│├── dev/← modules/vpc を相対パスで参照│├── stg/│└── prod/└── modules/ ├── vpc/ ├── rds/ └── ecs/各環境の main.tf は以下のように参照します。
# envs/dev/main.tf(モノレポ:相対パス参照)module "vpc" { source = "../../modules/vpc" # バージョン固定なし(同リポジトリのため常に最新)}ポリレポの場合、Section 8 で学んだ git::...?ref=vX.Y.Z 形式で外部リポジトリを参照します。モジュールの更新は別 PR・別タグとして独立して管理されるため、利用側は Renovate による自動更新 PR でバージョンを追従します。
選択指針
チームが 5 名以下・モジュール数が 10 以下であればモノレポが運用しやすい出発点です。モジュール数が増え、チームが分かれ、独立したリリースサイクルが必要になった段階でポリレポへの移行を検討してください。Section 7-5 で紹介した paths フィルタを活用すれば、モノレポでも CI コストを最小化できます。
モノレポ構成のセットアップ(ハンズオン)
terraform-git-handson/ リポジトリでモノレポ構成を実際に作成してみましょう。
# terraform-git-handson/ がすでにある前提(Section 7-3 で作成済み)cd terraform-git-handson# modules/ ディレクトリに vpc / rds / ecs モジュールを追加mkdir -p modules/vpc modules/rds modules/ecs# 各モジュールの最小構成ファイルを作成for mod in vpc rds ecs; do cat > modules/${mod}/main.tf <<'EOF'# modules/${mod}/main.tf — プレースホルダー(本番実装は第4弾以降)variable "env" { type = string description = "環境名(dev / stg / prod)"}output "module_name" { value = "${var.env}-${path.module}"}EOFdone# envs/dev/main.tf を相対パス参照に更新cat > envs/dev/main.tf <<'EOF'# envs/dev/main.tf — モノレポ:相対パスでモジュール参照module "vpc" { source = "../../modules/vpc" env = "dev"}module "rds" { source = "../../modules/rds" env = "dev"}EOF# 変更をコミットgit add modules/ envs/dev/main.tfgit commit -m "feat: add modules/vpc,rds,ecs for monorepo structure"# ディレクトリ構成確認find . -name "*.tf" | sortfind の出力で modules/vpc/main.tf・modules/rds/main.tf・envs/dev/main.tf が表示されれば、モノレポ構成の完成です。次のステップでは、Section 7-5 の paths フィルタを .github/workflows/ に追加し、変更したモジュールのみ CI が走る設定を行います(第5弾で実装)。
9-2. ブランチ保護 Required status checks — 深掘り
第2弾 Section 9-1 では Required status checks の概要を紹介しました。ここでは GitHub CLI を使った API 直接操作と、第5弾(GitHub Actions × Terraform)への布石となる具体的な設定方法を解説します。
GitHub CLI で保護ルールを API 設定
# main ブランチに Required status checks を設定# "terraform-plan" は GitHub Actions の job 名(第5弾で作成)gh api repos/YOUR_ORG/terraform-git-handson/branches/main/protection \ --method PUT \ --field required_status_checks='{"strict":true,"contexts":["terraform-plan"]}' \ --field enforce_admins=true \ --field required_pull_request_reviews='{"required_approving_review_count":1,"dismiss_stale_reviews":true}' \ --field restrictions=null| フィールド | 設定値 | 意味 |
|---|---|---|
required_status_checks.strict | true | ベースブランチが最新でなければマージ不可 |
required_status_checks.contexts | ["terraform-plan"] | CI で terraform plan が通過必須 |
enforce_admins | true | 管理者にも同ルールを適用 |
required_approving_review_count | 1 | 最低 1 名のレビュー承認が必要 |
dismiss_stale_reviews | true | 新コミット後はレビュー承認を自動取り消し |
status check 名の確認方法
# 直近の PR で通過した status check 名を一覧表示gh pr checks <PR番号> --repo YOUR_ORG/terraform-git-handson第5弾では、この terraform-plan という job name を持つ GitHub Actions workflow を作成します。main ブランチへの PR 時に terraform plan を自動実行し、その結果をコメントとして PR に投稿するパイプラインを構築します。OIDC 認証により AWS クレデンシャルをリポジトリに保存せずに実行できるため、セキュリティと自動化を両立できます(第4弾・第5弾で詳解)。
9-3. CODEOWNERS × ブランチ戦略 — ディレクトリ別レビュアー自動割当
モノレポ運用でチームが分かれている場合、CODEOWNERS を活用することで「誰が何をレビューするか」を自動化できます。
CODEOWNERS の配置と書式
.github/CODEOWNERS ファイルを作成し、ディレクトリパターンと担当チームを記載します。
# .github/CODEOWNERS# 書式: <パターン> <GitHub チームまたはユーザー># 本番環境の変更は SRE チームが必須レビューenvs/prod/ @your-org/sre-team# Terraform モジュールの変更はプラットフォームチームが必須レビューmodules/ @your-org/platform-team# ステージング環境はアプリチームが担当envs/stg/@your-org/app-team# GitHub Actions workflow の変更は全員レビュー必須.github/workflows/ @your-org/sre-team @your-org/platform-teamブランチ戦略との連携
CODEOWNERS と Required status checks・Required reviewers を組み合わせると、以下の自動ガバナンスが実現します。
| フロー | 動作 |
|---|---|
envs/prod/ を変更する PR を作成 | @your-org/sre-team が自動でレビュアーに追加される |
modules/vpc/ を変更する PR | @your-org/platform-team のレビューなしにマージ不可 |
| ブランチ保護 + CODEOWNERS の組合せ | PR 作成者がシニアエンジニアでも必ず担当チームのレビューが必要 |
GitFlow 運用でリリースブランチに release/* 保護を追加する場合も、CODEOWNERS で envs/prod/ に SRE チームを指定しておくことで、本番適用前の必須レビューを強制できます。
9-4. 第4弾予告 — セキュリティ: Secret Scanning / OIDC / 依存関係管理
Section 1〜9 で「ブランチ戦略の選定と実践」「Terraform IaC 運用」「モジュール管理」を習得しました。しかし、チーム開発で避けられない課題がセキュリティです。第4弾では以下を扱います。
| テーマ | 内容 | 本記事との関連 |
|---|---|---|
.gitignore による機密ファイル除外 | terraform.tfstate / *.tfvars / .env を絶対コミットしない | Section 7-3 の推奨構成を前提に設定 |
| GitHub Secret Scanning | うっかりコミットした AWS キーを自動検出・無効化 | ブランチ保護と連動したセキュリティゲート |
| pre-commit hook による自動チェック | tflint / tfsec をコミット前に強制実行 | GitFlow / GitHub Flow のどちらにも適用可 |
| GitHub OIDC | AWS クレデンシャル不要の安全な認証(第5弾の前提) | Section 9-2 のブランチ保護設定と連動 |
| Dependabot / Renovate | モジュール・Action バージョンの自動更新 PR | Section 8-8 の Renovate 設定を発展 |
terraform.tfstate には AWS リソースの設定値(場合によっては機密情報)が含まれます。これを誤ってコミットすると、git の履歴から完全削除することは困難です。第4弾では、この事故を防ぐための設計パターンを完全解説します。
先行準備 — .gitignore 設定(第4弾前の最重要ステップ)
第4弾を待たずに今すぐ実施すべき設定として、.gitignore に Terraform の機密ファイルを追加してください。これは「手順を読んで理解する」ではなく「今すぐコミットする」ステップです。
# terraform-git-handson/.gitignore に Terraform セキュリティパターンを追記cat >> .gitignore <<'EOF'# Terraform — 機密・自動生成ファイル(第4弾セキュリティ編の前提設定)*.tfstate*.tfstate.backup*.tfvars!terraform.tfvars.example# サンプルファイルは例外としてコミット可.terraform/crash.logoverride.tfoverride.tf.json*_override.tf*_override.tf.jsonEOF# .gitignore が既存の追跡ファイルに影響しないか確認git status# 問題なければコミットgit add .gitignoregit commit -m "chore: add terraform security patterns to .gitignore"この設定により、terraform init や terraform apply が生成するファイルが誤ってコミットされる事故を防ぎます。.tfvars には AWS アカウント ID やシークレット値が含まれる場合があるため、サンプルファイル(.tfvars.example)のみをコミットし、実際の値は環境変数や AWS Secrets Manager で管理するパターンを第4弾で詳解します。
git rm --cached terraform.tfstate でインデックスから除外し、.gitignore に追加後コミットします。しかし git の履歴に一度コミットした機密情報は完全削除が極めて困難です。git filter-repo や GitHub サポートへの依頼が必要になります。第4弾では事前防止策(pre-commit hook / Secret Scanning)を詳解します。9-5. 第5弾予告 — CI/CD: GitHub Actions × Terraform で自動化パイプライン
第5弾では、Section 9-2 で設定した Required status checks を実際に機能させる GitHub Actions workflow を構築します。
| ワークフロー | トリガー | 動作 |
|---|---|---|
terraform-plan.yml | PR 作成 / 更新 | terraform plan を実行し結果を PR コメントに投稿 |
terraform-apply.yml | main ブランチへのマージ | terraform apply を自動実行(本番適用) |
| OIDC 認証 | 全ワークフロー共通 | AWS クレデンシャルをリポジトリに保存せず安全に認証 |
OIDC(OpenID Connect)認証を使うと、GitHub Actions から AWS へのアクセスに長期クレデンシャル(Access Key / Secret Key)が不要になります。代わりに、ジョブ実行時に一時トークンを自動取得します。これにより「クレデンシャルが漏洩する」という最大のリスクを構造的に排除できます。
第4弾(セキュリティ設定)→ 第5弾(CI/CD 自動化)の順で学ぶことで、「手動 apply」から「安全な自動化パイプライン」への移行を完成させます。
Section 9 チェックリスト
- ☐ モノレポ vs ポリレポの 8 観点比較表を参照し、自分のチームに適した選択を説明できる
- ☐
gh apiで Required status checks を設定するコマンドを理解している - ☐
.github/CODEOWNERSでディレクトリ別のレビュアーを自動割当できる - ☐ 第4弾(セキュリティ)の主要テーマ(Secret Scanning / pre-commit / OIDC)を説明できる
- ☐ 第5弾(CI/CD)で構築する terraform-plan / apply ワークフローの全体像を把握している
Section 10: まとめ・スキル習得表・シリーズロードマップ・参考リンク
本記事では、Git/GitHub × Terraform 実践シリーズ第3弾として「ブランチ戦略の選定と Terraform IaC 運用への応用」を全10セクションで解説しました。Section 10 では学習の総括・習得スキルの整理・シリーズ全体のロードマップを提示します。
10-1. 本記事で習得したスキル表
| # | スキル | 習得内容 | Section |
|---|---|---|---|
| 1 | ブランチ戦略の選定 | チーム規模・リリース頻度・Terraform 環境数から 3 戦略を評価軸で選定できる | 3-4, 3-5 |
| 2 | GitHub Flow ハンズオン | feature ブランチ → PR 作成 → Squash merge → ローカル同期の一連フローを実践 | 4-1〜4-5 |
| 3 | GitFlow ハンズオン | develop / release / hotfix ブランチを操作し、git flow CLI で自動化 | 5-1〜5-5 |
| 4 | trunk-based + Feature Flags | Terraform 変数でフラグを実装し、main への直接コミット運用を体験 | 6-1〜6-5 |
| 5 | Terraform 環境分離設計 | ディレクトリ分離 / Workspace / ブランチ分離の 3 パターンを比較・評価 | 7-1 |
| 6 | Gated Promotion フロー | dev → stg → prod の段階的昇格と plan + destroy チェックを実践 | 7-4 |
| 7 | SemVer × Terraform モジュール | major / minor / patch の使い分けとモジュールバージョニングポリシーを設計 | 8-1 |
| 8 | git tag バージョン管理 | 注釈付きタグの作成・push・削除禁止ルール・CHANGELOG 連動を実践 | 8-2, 8-5, 8-6 |
| 9 | プライベートモジュール参照 | git::https://...?ref=vX.Y.Z 形式 + deploy key / PAT 認証を設定 | 8-3, 8-4 |
| 10 | モノレポ vs ポリレポ設計 | 8 観点の比較表をもとにチームに適したリポジトリ構造を選択 | 9-1 |
| 11 | ブランチ保護 API 操作 | gh api で Required status checks / Required reviewers を設定 | 9-2 |
| 12 | CODEOWNERS 設定 | ディレクトリ別レビュアーの自動割当でコードオーナーシップを制度化 | 9-3 |
| 13 | 第4弾セキュリティ準備 | .gitignore での機密除外・Secret Scanning・pre-commit の概念を把握 | 9-4 |
| 14 | 第5弾 CI/CD 設計理解 | OIDC 認証 + terraform-plan/apply ワークフローの全体像を把握 | 9-5 |
10-2. 3 戦略選定フローチャート(テキスト版総括)
本記事で解説した 3 戦略の選定基準を最終的にまとめます。
【ブランチ戦略 選定フローチャート】チームの状況を確認してください:Q1. リリースに「計画的なバージョン管理」が必要か? ├── Yes → Q2 へ └── No → Q3 へQ2. hotfix(本番緊急修正)の発生頻度は? ├── 頻繁にある → GitFlow を採用 │└── develop / release / hotfix ブランチで厳格管理 └── ほとんどない → GitHub Flow を採用 └── feature → PR → main の軽量フローQ3. デプロイ頻度は週に何回か? ├── 毎日〜複数回 → trunk-based development を採用 │└── Feature Flags で未完成機能を隠しながら直接コミット └── 週1〜月数回 → GitHub Flow を採用(シンプルが最善)Terraform IaC 運用の追加考慮: - 環境が 3 つ以上(dev/stg/prod)→ ディレクトリ分離 + GitHub Flow / GitFlow - 環境が 1〜2 つ → Workspace + trunk-based も選択肢 - モジュール数 10 以上 → ポリレポ + Renovate 自動更新を検討最初の一手: チームが 5 名以下・モジュールが少ない段階では GitHub Flow から始めることを強く推奨します。複雑な戦略は「複雑さを管理するコスト」が発生します。シンプルに始め、課題が出てから戦略を見直してください。
戦略移行のタイミング
チームが成長するにつれて、ブランチ戦略を変更する必要が生じることがあります。以下の兆候が現れたら移行を検討してください。
| 現在の戦略 | 移行を検討すべき兆候 | 次の戦略候補 |
|---|---|---|
| GitHub Flow | リリース日程を管理したい・hotfix が頻発 | GitFlow |
| GitHub Flow | デプロイ頻度が週5回以上・チームが成熟 | trunk-based |
| GitFlow | ブランチが複雑すぎてマージコストが高い | GitHub Flow(シンプル化) |
| trunk-based | Feature Flags の管理コストが高い・品質問題 | GitHub Flow(段階的 PR レビュー) |
移行は段階的に行うことが鍵です。GitFlow から GitHub Flow へ移行する場合、まず develop ブランチを廃止して main への直接マージに切り替え、release ブランチは段階的に削減する手順が安全です。Section 3-4 の「移行パス4フェーズ図」(L688)を参考にしてください。
本記事の学習パスを振り返る
| フェーズ | Section | 習得内容 | 前提知識 |
|---|---|---|---|
| 基礎理解 | 1〜3 | 3 戦略の概念・比較・Terraform との関係 | 第1弾 Git 入門 |
| GitHub Flow 実践 | 4 | feature ブランチ → PR → Squash merge | Section 1〜3 |
| GitFlow 実践 | 5 | develop / release / hotfix 操作 | Section 1〜3 |
| trunk-based 実践 | 6 | Feature Flags + 直接コミット | Section 1〜3 |
| Terraform 環境設計 | 7 | tfstate 分離・Gated Promotion | Section 4〜6 |
| モジュール管理 | 8 | git tag SemVer・プライベート参照 | Section 7 |
| 応用・布石 | 9 | モノレポ・ブランチ保護・セキュリティ準備 | Section 7〜8 |
| 総括 | 10 | スキル整理・ロードマップ確認 | 全 Section |
10-3. 第4弾予告 — セキュリティ編
- 🔒
.gitignoreでterraform.tfstate/*.tfvarsを除外し、機密情報の流出を防ぐ - 🔍 GitHub Secret Scanning で誤コミットした AWS キーを即時検出・無効化する
- ⚙️ pre-commit hook に
tflint/tfsecを組み込み、コミット前に自動チェックを強制 - 🔑 GitHub OIDC で AWS 認証を行い、長期クレデンシャルをリポジトリから完全排除
- 🤖 Dependabot / Renovate で Terraform モジュール・GitHub Actions のバージョンを自動更新
第4弾は準備中です。公開をお楽しみに。
10-4. シリーズロードマップ
- ✅ 前提①: Terraform 基礎 — init/plan/apply の理解
- ✅ 前提②: Terraform 実践 — モジュール・State・CI/CD
- ✅ 第1弾: Git入門 — ローカルで init/commit/branch/merge を習得
- ✅ 第2弾: GitHub入門 — push/pull/PR/Issue/Fork/revert でチーム協業
- ✅ 第3弾(本記事): ブランチ戦略 — GitFlow / GitHub Flow / trunk-based と Terraform モジュール管理
- 📌 第4弾(近日公開): セキュリティ — Secret Scanning / OIDC / 依存関係管理
- 📌 第5弾(近日公開): CI/CD — GitHub Actions × Terraform で自動化パイプライン構築
各記事は独立して読めますが、第1弾→第2弾→第3弾の順に進むと、ローカル Git からチーム協業、本番運用まで一本の学習パスとして体系的に習得できます。第4弾・第5弾でセキュリティと自動化を追加することで、エンタープライズグレードの Terraform 運用体制が完成します。
10-5. 参考リンク
本記事の作成にあたり参照した公式ドキュメントおよび主要リソースを紹介します。
| リソース | URL | 内容 |
|---|---|---|
| GitFlow 原典(Vincent Driessen) | https://nvie.com/posts/a-successful-git-branching-model/ | 2010 年に発表された GitFlow オリジナル論文 |
| GitHub Flow(GitHub Docs) | https://docs.github.com/ja/get-started/quickstart/github-flow | GitHub 公式の GitHub Flow 解説 |
| Trunk Based Development | https://trunkbaseddevelopment.com/ | trunk-based development のコミュニティサイト |
| Terraform モジュール Registry | https://registry.terraform.io/ | 公式モジュールの検索・参照 |
| Terraform Backend 設定 | https://developer.hashicorp.com/terraform/language/settings/backends/s3 | S3 バックエンドと State Lock 設定 |
| GitHub CODEOWNERS | https://docs.github.com/ja/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners | CODEOWNERS 書式の公式リファレンス |
| Renovate 公式ドキュメント | https://docs.renovatebot.com/ | Renovate による自動バージョン更新設定 |
| Conventional Commits | https://www.conventionalcommits.org/ja/v1.0.0/ | CHANGELOG 連動コミットメッセージ規約 |
本記事の総括
Git/GitHub × Terraform 実践シリーズ第3弾では、3 大ブランチ戦略の理解と選択から始まり、Terraform IaC 運用に最適化した環境別設計・モジュールの git tag バージョン管理・チーム規模拡大に備えた応用設定まで、一気通貫で習得しました。
- GitFlow: リリースサイクルが明確・バージョン管理が必要なプロダクトに最適
- GitHub Flow: シンプルさを武器にした軽量フロー。最初の選択肢として強く推奨
- trunk-based: 高速デプロイ・Feature Flags による柔軟な機能制御
Terraform 運用では「ブランチ戦略とディレクトリ分離の組合せ」が鍵です。Section 7-8 で学んだ envs/dev|stg|prod 構成と Gated Promotion フロー・モジュールの SemVer tag 管理を組み合わせることで、チームが安全に本番インフラを管理できる基盤が整います。
第4弾(セキュリティ)・第5弾(CI/CD 自動化)で、この基盤にセキュリティレイヤーと自動化パイプラインを追加し、エンタープライズグレードの Terraform 運用体制を完成させましょう。
- ☐ 3 戦略(GitFlow / GitHub Flow / trunk-based)の特徴と適用場面を説明できる
- ☐ チーム規模・リリース頻度・Terraform 環境数から最適な戦略を選定できる
- ☐ GitHub Flow / GitFlow / trunk-based の各ハンズオンを terraform-git-handson/ で実施した
- ☐ Feature Flags を Terraform 変数で実装し、trunk-based 運用を体験した
- ☐ Terraform 環境別ディレクトリ構成(envs/dev|stg|prod)を設計・構築できる
- ☐ Gated Promotion フロー(dev → stg → prod 昇格)の手順を実施できる
- ☐ SemVer に基づく git tag を作成し、モジュールをバージョン固定で参照できる
- ☐ プライベートリポジトリモジュールを deploy key / PAT で認証して参照できる
- ☐ CODEOWNERS でディレクトリ別レビュアーを設定し、自動割当を確認した
- ☐ 第4弾(セキュリティ)・第5弾(CI/CD)の学習計画を立てた