Terraform × AWS完全ガイド2026:Infrastructure as Codeで本番環境を自動化する
TerraformでAWSを管理する理由
手動管理の問題:
- 「何を作ったか」の記録が残らない
- 本番と検証環境で設定が微妙に違う
- 削除・再作成が怖くてゴミが溜まる
Terraformのメリット:
- インフラの状態がコードで管理される
- git diffで変更点が明確
- planで事前に変更内容を確認できる
- 環境の完全な複製が1コマンド
セットアップ
# インストール(Homebrew)
brew tap hashicorp/tap && brew install hashicorp/tap/terraform
# AWSクレデンシャル設定
aws configure
# または
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_DEFAULT_REGION="ap-northeast-1"
プロジェクト構造
infra/
├── main.tf # メイン設定
├── variables.tf # 変数定義
├── outputs.tf # 出力値
├── versions.tf # プロバイダーバージョン
├── terraform.tfvars # 変数値(gitignore推奨)
└── modules/
├── ecs/ # ECSクラスター
├── rds/ # データベース
└── alb/ # ロードバランサー
実践:ECS + ALB + RDSの本番構成
# versions.tf
terraform {
required_version = ">= 1.8"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# Stateをs3で管理(チーム開発必須)
backend "s3" {
bucket = "my-company-terraform-state"
key = "production/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "terraform"
}
}
}
# modules/ecs/main.tf
resource "aws_ecs_cluster" "main" {
name = "${var.project}-${var.env}"
setting {
name = "containerInsights"
value = "enabled"
}
}
resource "aws_ecs_task_definition" "app" {
family = "${var.project}-app"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.cpu
memory = var.memory
execution_role_arn = aws_iam_role.ecs_execution.arn
task_role_arn = aws_iam_role.ecs_task.arn
container_definitions = jsonencode([{
name = "app"
image = var.ecr_image_uri
essential = true
portMappings = [{
containerPort = var.app_port
protocol = "tcp"
}]
secrets = [
{ name = "DATABASE_URL", valueFrom = aws_ssm_parameter.db_url.arn },
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/${var.project}"
"awslogs-region" = var.aws_region
"awslogs-stream-prefix" = "app"
}
}
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:${var.app_port}/health || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 60
}
}])
}
resource "aws_ecs_service" "app" {
name = "${var.project}-app"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = var.desired_count
launch_type = "FARGATE"
deployment_circuit_breaker {
enable = true
rollback = true # 失敗時に自動ロールバック
}
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.app.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = var.alb_target_group_arn
container_name = "app"
container_port = var.app_port
}
lifecycle {
ignore_changes = [desired_count] # Auto Scalingが管理
}
}
GitHub Actionsでのデプロイ自動化
# .github/workflows/deploy.yml
name: Deploy to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Build and push to ECR
run: |
aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REGISTRY
docker build -t app .
docker tag app:latest $ECR_REGISTRY/$ECR_REPO:$GITHUB_SHA
docker push $ECR_REGISTRY/$ECR_REPO:$GITHUB_SHA
- name: Terraform apply
run: |
cd infra
terraform init
terraform plan -var="ecr_image_tag=$GITHUB_SHA" -out=tfplan
terraform apply tfplan
コスト最適化のTerraformパターン
# ECS Auto Scaling(夜間はタスク数を削減)
resource "aws_appautoscaling_scheduled_action" "scale_down_night" {
name = "scale-down-night"
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.app.name}"
scalable_dimension = "ecs:service:DesiredCount"
schedule = "cron(0 21 * * ? *)" # 毎日21:00
scalable_target_action {
min_capacity = 0
max_capacity = 1
}
}
resource "aws_appautoscaling_scheduled_action" "scale_up_morning" {
name = "scale-up-morning"
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.app.name}"
scalable_dimension = "ecs:service:DesiredCount"
schedule = "cron(0 8 * * ? *)" # 毎日8:00
scalable_target_action {
min_capacity = 2
max_capacity = 10
}
}
State管理のベストプラクティス
TerraformのState(状態ファイル)管理は、チーム開発で最も重要なポイントです。
リモートStateの設定
# backend.tf — S3 + DynamoDBによるState管理
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "production/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-locks" # ロック用
}
}
S3バケットにはバージョニングを有効化し、DynamoDBテーブルでState lockingを実現します。これにより、複数人が同時にterraform applyを実行しても競合を防げます。
State分割の戦略
大規模プロジェクトでは、変更頻度に応じてStateを分割します。
| ディレクトリ | 管理対象 | 変更頻度 |
|---|---|---|
global/ | IAM, Route53 | 低 |
network/ | VPC, Subnet, NAT | 低 |
database/ | RDS, ElastiCache | 中 |
application/ | ECS, ALB, Auto Scaling | 高 |
State間でデータを参照するには terraform_remote_state を使い、VPC IDやサブネットIDなどを取得します。
モジュール構造の設計パターン
再利用可能なモジュールの設計は、Terraformプロジェクトの保守性を大きく左右します。
各モジュールは main.tf / variables.tf / outputs.tf の3ファイル構成が基本です。
モジュールの呼び出し例
# 環境ごとに変数を変えるだけで同一構成を複製
# environments/production/main.tf
module "api_service" {
source = "../../modules/ecs-service"
project = "my-app"
environment = "production"
cpu = 1024
memory = 2048
desired_count = 3
health_check_path = "/api/health"
}
# environments/staging/main.tf
module "api_service" {
source = "../../modules/ecs-service"
project = "my-app"
environment = "staging"
cpu = 256
memory = 512
desired_count = 1
}
CI/CDパイプラインとの統合
GitHub ActionsでTerraformのplan/applyを自動化するのが本番運用の前提です。PR作成時に terraform plan を実行して差分を確認し、mainマージ時のみ terraform apply を自動実行する構成が安全です。
OIDC認証を使えばAWSアクセスキーのシークレット管理が不要になり、セキュリティも向上します。
まとめ:Terraform導入の3ステップ
- State管理をS3に移行(チーム開発の前提)
- モジュール化(再利用可能なパーツを作る)
- GitHub Actions統合(PRでplan確認、mainマージでapply)