発展LESSON 06
この教材で学ぶこと 複数環境とチーム運用のIaC設計を学びます。Terraform 応用 — Workspace・Terragrunt・Atlantis
前提知識: TerraformとHCLの基礎。本教材ではチーム開発・複数環境・CI/CD統合を扱います。
1. Terraform Workspace — 環境分離
# Workspace = state ファイルを分離する仕組み
# 同じコードベースで dev/stg/prod を管理
terraform workspace list
# * default
# dev
# staging
# production
terraform workspace new staging
terraform workspace select staging
terraform plan
# workspace によって設定を切り替え
locals {
env = terraform.workspace # "dev", "staging", "production"
config = {
dev = {
instance_type = "t3.micro"
min_size = 1
max_size = 2
}
staging = {
instance_type = "t3.small"
min_size = 1
max_size = 3
}
production = {
instance_type = "t3.medium"
min_size = 2
max_size = 10
}
}
current = local.config[local.env]
}
resource "aws_instance" "app" {
instance_type = local.current.instance_type
# ...
}
Workspace の制限: 同じ backend(S3 バケット等)を共有するため、IAM 設定が複雑。大規模では Terragrunt が有効。
2. Terragrunt — DRY な多環境管理
ディレクトリ構成(Terragrunt 推奨):
infra/
├── modules/ # 再利用可能なモジュール
│ ├── vpc/
│ ├── ecs/
│ └── rds/
├── live/
│ ├── terragrunt.hcl # 共通設定(backend・provider)
│ ├── dev/
│ │ ├── terragrunt.hcl # dev 用の変数
│ │ ├── vpc/
│ │ │ └── terragrunt.hcl
│ │ └── ecs/
│ │ └── terragrunt.hcl
│ └── production/
│ ├── terragrunt.hcl
│ └── ...
# live/terragrunt.hcl(ルート:共通設定)
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "mycompany-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-lock"
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite"
contents = <<EOF
provider "aws" {
region = "ap-northeast-1"
}
EOF
}
# live/production/vpc/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../../modules/vpc"
}
inputs = {
vpc_cidr = "10.0.0.0/16"
env = "production"
az_count = 3
}
# 全環境を一括でプランしたい場合(--all オプション)
cd live/production
terragrunt run-all plan
# 依存関係も自動的に処理(vpc → ecs の順でデプロイ)
terragrunt run-all apply --terragrunt-non-interactive
3. Atlantis — Pull Request ベースのレビューフロー
Atlantis の仕組み:
PR 作成
↓
GitHub Webhook → Atlantis サーバー
↓
Atlantis が自動で `terraform plan` を実行
↓
PR にプランの結果をコメント
↓
レビュアーが確認してコメントで承認
`atlantis apply`
↓
`terraform apply` が実行される → PR マージ
# atlantis.yaml(リポジトリルートに置く)
version: 3
automerge: false
parallel_plan: true
parallel_apply: false
projects:
- name: production-vpc
dir: live/production/vpc
workspace: default
autoplan:
when_modified:
- "*.tf"
- "../../../modules/vpc/**/*.tf"
enabled: true
- name: production-ecs
dir: live/production/ecs
workspace: default
depends_on:
- production-vpc
autoplan:
enabled: true
4. tfstate の管理とロック
# backend.tf — state を S3 に保存・DynamoDB でロック
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/main/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
# State Locking: 同時実行を防ぐ
# DynamoDB テーブルに LockID という項目でロックを取得
}
}
# state の操作(緊急時)
terraform state list # 管理対象リソースの一覧
terraform state show aws_instance.app # 特定リソースの詳細
terraform state mv aws_instance.old aws_instance.new # リソース名変更
terraform state rm aws_instance.to_delete # state から削除(実リソースは残る)
# state が壊れた・ロックが残った場合
terraform force-unlock LOCK_ID
5. モジュール設計のベストプラクティス
# modules/ecs/main.tf — 再利用可能なモジュール
variable "env" {
description = "環境名(dev/staging/production)"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.env)
error_message = "env は dev, staging, production のいずれかである必要があります。"
}
}
variable "container_image" {
description = "ECR イメージ URI"
type = string
}
variable "desired_count" {
description = "ECS タスクの希望数"
type = number
default = 2
}
# 出力値(他のモジュールから参照できる)
output "service_name" {
description = "ECS サービス名"
value = aws_ecs_service.main.name
}
output "cluster_arn" {
description = "ECS クラスター ARN"
value = aws_ecs_cluster.main.arn
}
6. CI/CD パイプラインでの Terraform
# .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
branches: [main]
paths: ["infra/**"]
push:
branches: [main]
paths: ["infra/**"]
permissions:
id-token: write # OIDC 認証に必要
contents: read
pull-requests: write
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/terraform-plan-role
aws-region: ap-northeast-1
- name: Terraform Plan
id: plan
working-directory: infra/live/production
run: |
terraform init
terraform plan -no-color -out=tfplan 2>&1 | tee plan.txt
- name: Comment PR with Plan
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('infra/live/production/plan.txt', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `\`\`\`\n${plan.slice(0, 65000)}\n\`\`\``
});
apply:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production # GitHub Environment で承認フローを設定
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/terraform-apply-role
aws-region: ap-northeast-1
- name: Terraform Apply
working-directory: infra/live/production
run: |
terraform init
terraform apply -auto-approve
🔗 次のステップ
- 01_Terraform.md — 基礎・EC2+ALB+RDS の例
- ../CICD/01_GitHub_Actions.md — OIDC 認証・ECS デプロイ
- ../../03_Cloud/AWS/01_SAA以上の設計.md — マルチアカウント設計