前提条件
- 第1回から第12回までの構築が完了していること
- 複数環境(dev/staging/prod)の管理を想定していること
- こちらからDockerコンテナでの実行環境が準備できていること
Terraform実行環境の起動
前回と同様に、Dockerコンテナ内で作業を進めます。
# コンテナを起動してbashに入る
docker-compose run --rm terraform
これ以降のコマンドは、すべてこのコンテナ内で実行します。
基礎知識
環境別管理
本番環境では、開発環境と異なる設定が必要になります。
環境 | 特徴 | 設定例 |
---|---|---|
開発(dev) | コスト優先、頻繁な変更 | t3.micro、Single-AZ、最小タスク数 |
ステージング(stg) | 本番に近い構成 | t3.small、Multi-AZ、中規模 |
本番(prod) | 可用性・性能優先 | m5.large、Multi-AZ、Auto Scaling |
Auto Scalingの仕組み
Auto Scalingは、負荷に応じてリソースを自動的に増減させる機能です。
WEBアプリ開発者の視点から見ると
ローカル開発では1つのサーバープロセスで処理しますが、本番環境では負荷に応じて複数のプロセスが必要になります。Auto Scalingは、CPU使用率やメモリ使用率を監視し、閾値を超えたら自動的にサーバーを追加、負荷が下がったら削減します。
スケーリングタイプ | 説明 | 適用例 |
---|---|---|
Target Tracking | 指定メトリクスを目標値に維持 | CPU使用率を50%に維持 |
Step Scaling | 段階的にスケール | 70%で+1、90%で+2タスク |
Scheduled Scaling | 時間帯でスケール | 営業時間中は多め、夜間は少なめ |
今回作成するリソース
リソース | 説明 | 数量 |
---|---|---|
Terraformモジュール | VPC、ECS、RDS等の再利用可能モジュール | 複数 |
環境別設定ファイル | dev/stg/prod用tfvars | 3個 |
ECS Auto Scaling | CPU/メモリベースの自動スケーリング | 1個 |
Application Auto Scaling Target | スケーリング対象の定義 | 1個 |
Target Tracking Scaling Policy | スケーリングポリシー | 2個 |
RDS Performance Insights | データベース性能監視 | 1個 |
CloudWatch Dashboards | 環境別ダッシュボード | 環境数分 |
プロジェクト構成
以下のような構成でTerraformの環境別&モジュール化を実装します。
terraform/
├── environments/ # 環境別設定
│ ├── dev/
│ │ ├── main.tf # モジュール呼び出し
│ │ ├── variables.tf # 環境固有変数定義
│ │ ├── terraform.tfvars # 環境固有値
│ │ └── backend.hcl # S3バックエンド設定
│ ├── stg/
│ └── prod/
└── modules/ # 再利用可能モジュール
├── network/ # VPC・サブネット
├── security/ # セキュリティグループ
├── compute/ # ECS・Auto Scaling
├── database/ # RDS・Performance Insights
├── storage/ # S3
├── loadbalancer/ # ALB
├── cdn/ # CloudFront
├── monitoring/ # CloudWatch
├── cicd/ # GitHub Actions
└── vpc_endpoints/ # VPCエンドポイント
モジュールの基本構造
各モジュールは以下の3つのファイルで構成されます。
modules/[モジュール名]/
├── main.tf # リソース定義
├── variables.tf # 入力変数
└── outputs.tf # 出力値
実装の考え方
原則 | 説明 | 実装例 |
---|---|---|
単一責任 | 1モジュール1責務 | networkモジュールはVPC関連のみ |
疎結合 | 依存を最小化 | 出力値を通じて連携 |
環境非依存 | 環境固有値は外部から注入 | 変数で環境差分を吸収 |
条件分岐 | 機能の有効/無効を制御 | count/for_eachで条件付きリソース作成 |
環境別設定の実装
開発環境
# environments/dev/terraform.tfvars
# プロジェクト設定
# project_name = "" 環境変数(TF_VAR_project_name)で設定
# environment = "dev"
# 環境別機能有効化設定
enable_rds = true # RDSデータベースを有効化(学習目的)
enable_nat_gateway = false # NAT Gatewayを無効化(コスト削減)
# セキュリティ・監視
enable_waf = false # WAFを無効化(開発環境)
enable_vpc_endpoints = true # VPCエンドポイントを有効化(NAT Gateway無効のため必須)
enable_vpc_flow_logs = false # VPC Flow Logsを無効化(コスト削減)
enable_alb_access_logs = false # ALBアクセスログを無効化(コスト削減)
enable_xray_tracing = false # X-Rayトレーシングを無効化(コスト削減)
enable_cloudfront_logging = false # CloudFrontログを無効化(コスト削減)
# アラーム・監視
enable_cpu_alarm = false # CPUアラームを無効化(開発環境)
enable_memory_alarm = false # メモリアラームを無効化(開発環境)
enable_unhealthy_host_alarm = false # 異常ホストアラームを無効化(開発環境)
# その他の環境固有設定
allowed_origins = [] # CORS許可オリジン(実際の値を設定)
# SSL証明書(オプション)
alb_certificate_arn = ""
frontend_certificate_arn = ""
frontend_domain_name = ""
環境別 main.tf
environments/dev/main.tfを例に、モジュールを呼び出し環境固有の設定を適用します。
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
# backend.hclで設定
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "terraform"
}
}
}
# 共通タグの定義
locals {
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "terraform"
}
}
# Network Module
module "network" {
source = "../../modules/network"
project_name = var.project_name
environment = var.environment
vpc_cidr = var.vpc_cidr
enable_nat_gateway = var.enable_nat_gateway
}
# Security Module
module "security" {
source = "../../modules/security"
project_name = var.project_name
environment = var.environment
vpc_id = module.network.vpc_id
vpc_cidr_block = var.vpc_cidr
enable_vpc_endpoints = false
}
# Database Module(開発環境では条件付き)
module "database" {
count = var.enable_rds ? 1 : 0 # RDS有効化変数で制御
source = "../../modules/database"
project_name = var.project_name
environment = var.environment
private_subnet_ids = module.network.private_subnet_ids
rds_security_group_id = module.security.database_security_group_id
db_engine_version = "15.8"
db_instance_class = "db.t3.micro"
db_allocated_storage = 20
db_username = "postgres"
multi_az = false
backup_retention_period = 1
enable_performance_insights = false
enable_rds_proxy = false
}
# Compute Module
module "compute" {
source = "../../modules/compute"
project_name = var.project_name
environment = var.environment
aws_region = var.aws_region
task_cpu = "256"
task_memory = "512"
desired_count = 1
container_port = 3000
min_capacity = 1
max_capacity = 2
private_subnet_ids = module.network.private_subnet_ids
ecs_security_group_id = module.security.ecs_security_group_id
target_group_arn = module.loadbalancer.target_group_arn
db_secret_arn = var.enable_rds && length(module.database) > 0 ? module.database[0].db_secret_arn : ""
allowed_origins = var.allowed_origins
enable_security_headers = var.enable_security_headers
enable_xray_tracing = var.enable_xray_tracing
}
Auto Scalingの実装
Auto Scaling(computeモジュール)
# modules/compute/main.tf
# ECSサービス(既存のリソース)
resource "aws_ecs_service" "app" {
name = "${var.project_name}-${var.environment}-app-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = var.desired_count
launch_type = "FARGATE"
# 以下、既存の設定...
}
# Auto Scaling Target
resource "aws_appautoscaling_target" "ecs" {
max_capacity = var.max_capacity
min_capacity = var.min_capacity
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.app.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
depends_on = [aws_ecs_service.app]
}
# CPU使用率によるスケーリングポリシー
resource "aws_appautoscaling_policy" "ecs_cpu" {
name = "${var.project_name}-${var.environment}-cpu-scaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs.resource_id
scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = var.cpu_target_value
scale_in_cooldown = var.scale_in_cooldown
scale_out_cooldown = var.scale_out_cooldown
}
}
# メモリ使用率によるスケーリングポリシー
resource "aws_appautoscaling_policy" "ecs_memory" {
name = "${var.project_name}-${var.environment}-memory-scaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs.resource_id
scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageMemoryUtilization"
}
target_value = var.memory_target_value
scale_in_cooldown = var.scale_in_cooldown
scale_out_cooldown = var.scale_out_cooldown
}
}
スケジュールベースのスケーリング
# modules/compute/scheduled_scaling.tf
# 営業時間中のスケールアップ
resource "aws_appautoscaling_scheduled_action" "scale_up_morning" {
count = var.enable_scheduled_scaling ? 1 : 0
name = "${var.project_name}-${var.environment}-scale-up-morning"
service_namespace = aws_appautoscaling_target.ecs.service_namespace
resource_id = aws_appautoscaling_target.ecs.resource_id
scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
schedule = "cron(0 0 * * MON-FRI *)" # 平日9時(JST)
timezone = "Asia/Tokyo"
scalable_target_action {
min_capacity = var.business_hours_min_capacity
max_capacity = var.business_hours_max_capacity
}
}
# 営業時間外のスケールダウン
resource "aws_appautoscaling_scheduled_action" "scale_down_evening" {
count = var.enable_scheduled_scaling ? 1 : 0
name = "${var.project_name}-${var.environment}-scale-down-evening"
service_namespace = aws_appautoscaling_target.ecs.service_namespace
resource_id = aws_appautoscaling_target.ecs.resource_id
scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
schedule = "cron(0 10 * * MON-FRI *)" # 平日19時(JST)
timezone = "Asia/Tokyo"
scalable_target_action {
min_capacity = var.off_hours_min_capacity
max_capacity = var.off_hours_max_capacity
}
}
Auto Scalingパラメータ
パラメータ | 型 | 説明 | 設定内容 | 環境別推奨値 |
---|---|---|---|---|
min_capacity |
number | 最小タスク数 | 常時稼働させる最小数 | dev:1、stg:2、prod:3 |
max_capacity |
number | 最大タスク数 | スケールアウトの上限 | dev:2、stg:4、prod:10 |
cpu_target_value |
number | CPU使用率の目標値(%) | 平均CPU使用率をこの値に維持 | 50 |
memory_target_value |
number | メモリ使用率の目標値(%) | 平均メモリ使用率をこの値に維持 | 70 |
scale_in_cooldown |
number | スケールイン後の待機時間(秒) | 頻繁なスケール変更を防止 | 300 |
scale_out_cooldown |
number | スケールアウト後の待機時間(秒) | 急激な負荷増加への対応 | 60 |
RDS Performance Insightsの実装
Performance Insights(databaseモジュール)
# modules/database/main.tf
# Performance Insights用KMSキー
resource "aws_kms_key" "rds" {
count = var.enable_performance_insights ? 1 : 0
description = "${var.project_name}-${var.environment}-rds-pi-key"
deletion_window_in_days = 10
tags = {
Name = "${var.project_name}-${var.environment}-rds-pi-key"
}
}
# Enhanced Monitoring用IAMロール
resource "aws_iam_role" "rds_monitoring" {
count = var.enable_performance_insights ? 1 : 0
name = "${var.project_name}-${var.environment}-rds-monitoring"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "monitoring.rds.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "rds_monitoring" {
count = var.enable_performance_insights ? 1 : 0
role = aws_iam_role.rds_monitoring[0].name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
}
# RDSインスタンス(Performance Insights設定含む)
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-${var.environment}-db"
# 既存の設定...
# Performance Insights設定
enabled_cloudwatch_logs_exports = ["postgresql"]
performance_insights_enabled = var.enable_performance_insights
performance_insights_retention_period = var.enable_performance_insights ? var.performance_insights_retention : null
performance_insights_kms_key_id = var.enable_performance_insights ? aws_kms_key.rds[0].arn : null
# Enhanced Monitoring
monitoring_interval = var.enable_performance_insights ? 60 : 0
monitoring_role_arn = var.enable_performance_insights ? aws_iam_role.rds_monitoring[0].arn : null
# その他の設定
skip_final_snapshot = var.skip_final_snapshot
deletion_protection = var.enable_deletion_protection
}
# DB Parameter Group(Performance Insights用設定含む)
resource "aws_db_parameter_group" "main" {
name = "${var.project_name}-${var.environment}-pg15"
family = "postgres15"
parameter {
name = "shared_preload_libraries"
value = "pg_stat_statements"
}
parameter {
name = "log_statement"
value = var.enable_performance_insights ? "all" : "none"
}
parameter {
name = "log_min_duration_statement"
value = var.enable_performance_insights ? "1000" : "-1" # 1秒以上のクエリをログ出力
}
}
Performance Insightsパラメータ
パラメータ | 型 | 説明 | 設定内容 | 環境別推奨値 |
---|---|---|---|---|
performance_insights_enabled |
bool | Performance Insights有効化 | SQLの実行時間とDB負荷を可視化 | dev:false、stg/prod:true |
performance_insights_retention_period |
number | データ保持期間(日) | 分析データの保存期間 | 7(無料)または731 |
monitoring_interval |
number | メトリクス収集間隔(秒) | Enhanced Monitoring頻度 | 0(無効)または60 |
log_statement |
string | SQLログ記録レベル | 実行されるSQL文のログ記録 | dev:“all”、prod:“none” |
log_min_duration_statement |
number | スロークエリ閾値(ミリ秒) | この時間以上のクエリをログ記録 | 1000(1秒) |
リソースの作成
環境別にTerraformを実行します。
# 開発環境
cd terraform/environments/dev
terraform init -backend-config=backend.hcl
terraform plan
terraform apply
# ステージング環境
cd terraform/environments/stg
terraform init -backend-config=backend.hcl
terraform plan
terraform apply
# 本番環境(承認フロー推奨)
cd terraform/environments/prod
terraform init -backend-config=backend.hcl
terraform plan -out=prod.tfplan
# レビュー後
terraform apply prod.tfplan
動作確認
Auto Scalingの動作確認
# スケーリングターゲットの確認
aws application-autoscaling describe-scalable-targets \
--service-namespace ecs \
--query 'ScalableTargets[*].[ResourceId,MinCapacity,MaxCapacity]' \
--output table
# スケーリングポリシーの確認
aws application-autoscaling describe-scaling-policies \
--service-namespace ecs \
--query 'ScalingPolicies[*].[PolicyName,TargetTrackingScalingPolicyConfiguration.TargetValue]' \
--output table
# 負荷テストでAuto Scalingを確認
for i in {1..1000}; do
curl -s http://$ALB_DNS/api/heavy-endpoint &
done
wait
# タスク数の変化を監視
watch -n 5 'aws ecs describe-services \
--cluster $CLUSTER_NAME \
--services $SERVICE_NAME \
--query "services[0].[desiredCount,runningCount]" \
--output text'
Performance Insightsの確認
AWS Management Consoleで以下を確認します。
RDS > Performance Insights
確認項目 | 説明 |
---|---|
Top SQL | 実行時間の長いクエリが表示される |
Database Load | 時系列でDB負荷が可視化される |
Top Waits | 待機イベントの内訳が表示される |
SQL Statistics | クエリごとの統計情報が確認できる |
コスト管理
環境別のコスト最適化設定です。
リソース | 開発環境 | ステージング環境 | 本番環境 |
---|---|---|---|
NAT Gateway | 無効($0) | 有効(月額$65) | 有効(月額$130、Multi-AZ) |
ECS Fargate | 最小構成(月額$10) | 中規模(月額$50) | Auto Scaling(月額$200〜) |
RDS | t3.micro(月額$13) | t3.small(月額$26) | m5.large(月額$130) |
Performance Insights | 無効($0) | 7日保持(無料) | 30日保持(月額$20) |
トラブルシューティング
モジュール参照エラー
エラー | 原因 | 対処方法 |
---|---|---|
Module not found | パスの指定ミス | source = "…/…/modules/xxx"の相対パスを確認 |
Variable not defined | 変数の未定義 | モジュールのvariables.tfに変数を追加 |
Output not found | 出力の未定義 | モジュールのoutputs.tfに出力を追加 |
Auto Scalingが動作しない
# CloudWatch Alarmsの状態確認
aws cloudwatch describe-alarms \
--alarm-name-prefix $PROJECT_NAME \
--query 'MetricAlarms[?StateValue!=`OK`].[AlarmName,StateValue,StateReason]' \
--output table
# ECSサービスのイベント確認
aws ecs describe-services \
--cluster $CLUSTER_NAME \
--services $SERVICE_NAME \
--query 'services[0].events[0:5]' \
--output table
ベストプラクティス
terraform.tfvarsの管理
# 推奨: enable系フラグのみを管理
enable_nat_gateway = false
enable_monitoring = false
# 非推奨: すべての値を記述
vpc_cidr = "10.0.0.0/16"
availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
環境別設定の使い分け
設定項目 | 開発 | ステージング | 本番 | 理由 |
---|---|---|---|---|
enable_nat_gateway |
false | true | true | コスト vs セキュリティ |
enable_performance_insights |
false | true | true | 監視レベルの違い |
enable_deletion_protection |
false | false | true | 誤削除防止 |
enable_scheduled_scaling |
false | false | true | 本番環境のみ時間帯別スケーリング |
secrets_recovery_window_days |
0 | 7 | 30 | 環境別復旧期間設定 |
skip_final_snapshot |
true | false | false | 開発環境ではスナップショット不要 |
multi_az |
false | true | true | 可用性要件 |
モジュール設計の原則
原則 | 実装方法 |
---|---|
単一責任 | 1モジュール1責務(networkはVPC関連のみ) |
疎結合 | 出力値を通じた連携 |
再利用性 | 環境固有値は変数で外部化 |
条件分岐 | count/for_eachで条件付きリソース作成 |
次のステップ
次回は、セキュリティベストプラクティスと総まとめを行います。
- WAF(Web Application Firewall)の詳細設定
- AWS Config、GuardDutyによるセキュリティ監視
- IAMロールとポリシーの最適化
- セキュリティグループの最小権限化
- パフォーマンステストと負荷試験
- 12回分の総まとめとベストプラクティス集