はじめに
既存のTomcatアプリをAWS ECS Fargateに移行する手順をまとめました。
セッション管理・DBマイグレーション・Auto Scalingまで含めた本番を想定した構成です。
対象読者
- TomcatアプリをAWSに移行したい方
- ECS Fargateを初めて使う方
最終的な構成
Internet
│
ALB (80/443)
│
ECS Fargate
├─ ElastiCache (Redis) ← セッション管理
└─ RDS (PostgreSQL) ← データ永続化
STEP 1: セキュリティグループの作成
ALB・ECS・Redis・RDS それぞれに専用のセキュリティグループを作成します。
最小権限の原則に従い、必要なものだけを許可します。
# ALB 用(インターネットから 80, 443 を許可)
aws ec2 create-security-group \
--group-name sg-alb \
--description "ALB SG" \
--vpc-id vpc-xxxxxxxx
aws ec2 authorize-security-group-ingress \
--group-id sg-alb --protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-alb --protocol tcp --port 443 --cidr 0.0.0.0/0
# ECS 用(ALB からの 8080 のみ許可)
aws ec2 create-security-group \
--group-name sg-ecs \
--description "ECS SG" \
--vpc-id vpc-xxxxxxxx
aws ec2 authorize-security-group-ingress \
--group-id sg-ecs --protocol tcp --port 8080 \
--source-group sg-alb
# Redis 用(ECS からの 6379 のみ許可)
aws ec2 create-security-group \
--group-name sg-redis \
--description "Redis SG" \
--vpc-id vpc-xxxxxxxx
aws ec2 authorize-security-group-ingress \
--group-id sg-redis --protocol tcp --port 6379 \
--source-group sg-ecs
# RDS 用(ECS からの 5432 のみ許可)
aws ec2 create-security-group \
--group-name sg-rds \
--description "RDS SG" \
--vpc-id vpc-xxxxxxxx
aws ec2 authorize-security-group-ingress \
--group-id sg-rds --protocol tcp --port 5432 \
--source-group sg-ecs
セキュリティグループの関係を整理すると以下の通りです。
| SG | インバウンド許可 |
|---|---|
| sg-alb | 0.0.0.0/0 → 80, 443 |
| sg-ecs | sg-alb → 8080 |
| sg-redis | sg-ecs → 6379 |
| sg-rds | sg-ecs → 5432 |
STEP 2: Secrets Manager に認証情報を登録
パスワードなどの機密情報は Secrets Manager で管理し、タスク定義に平文で書かないようにします。
aws secretsmanager create-secret \
--name myapp/db-password \
--secret-string "your-db-password"
aws secretsmanager create-secret \
--name myapp/db-user \
--secret-string "your-db-user"
aws secretsmanager create-secret \
--name myapp/redis-password \
--secret-string "your-redis-password"
STEP 3: RDS の構築
# サブネットグループ作成(プライベートサブネット)
aws rds create-db-subnet-group \
--db-subnet-group-name myapp-rds-subnet \
--db-subnet-group-description "myapp RDS subnet" \
--subnet-ids subnet-aaaaaaaa subnet-bbbbbbbb
# RDS インスタンス作成
aws rds create-db-instance \
--db-instance-identifier myapp-db \
--db-instance-class db.t4g.small \
--engine postgres \
--engine-version 15.4 \
--master-username myapp \
--master-user-password "your-db-password" \
--db-name myapp \
--db-subnet-group-name myapp-rds-subnet \
--vpc-security-group-ids sg-rds \
--multi-az \
--storage-type gp3 \
--allocated-storage 20 \
--no-publicly-accessible
STEP 4: ElastiCache (Redis) の構築
Fargateはタスクが再起動するたびにインスタンスが変わるため、セッションをメモリに持つと消えてしまいます。
また複数タスクがある場合、リクエストが別タスクに届くとセッションが見つかりません。
ElastiCache (Redis) でセッションを外出しすることでこれを解決します。
# サブネットグループ作成
aws elasticache create-cache-subnet-group \
--cache-subnet-group-name myapp-redis-subnet \
--cache-subnet-group-description "myapp redis subnet" \
--subnet-ids subnet-aaaaaaaa subnet-bbbbbbbb
# パラメータグループ作成
aws elasticache create-cache-parameter-group \
--cache-parameter-group-name myapp-redis-params \
--cache-parameter-group-family redis7 \
--description "myapp redis params"
aws elasticache modify-cache-parameter-group \
--cache-parameter-group-name myapp-redis-params \
--parameter-name-values \
ParameterName=maxmemory-policy,ParameterValue=allkeys-lru \
ParameterName=timeout,ParameterValue=300
# Redis 作成(マルチAZ・暗号化あり)
aws elasticache create-replication-group \
--replication-group-id myapp-redis \
--description "myapp session store" \
--engine redis \
--engine-version 7.0 \
--cache-node-type cache.t4g.medium \
--num-cache-clusters 2 \
--multi-az-enabled \
--automatic-failover-enabled \
--cache-subnet-group-name myapp-redis-subnet \
--cache-parameter-group-name myapp-redis-params \
--security-group-ids sg-redis \
--at-rest-encryption-enabled \
--transit-encryption-enabled \
--auth-token "your-redis-password"
Spring Session の設定
pom.xml に依存関係を追加します。
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
application.yml を設定します。
spring:
session:
store-type: redis
timeout: 30m
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD}
ssl:
enabled: true
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
STEP 5: Docker イメージのビルドと ECR への Push
Dockerfile(アプリ用)
FROM tomcat:10.1-jdk17
RUN rm -rf /usr/local/tomcat/webapps/*
COPY target/myapp.war /usr/local/tomcat/webapps/ROOT.war
EXPOSE 8080
CMD ["catalina.sh", "run"]
Dockerfile.migration(マイグレーション用)
FROM flyway/flyway:10-alpine
COPY db/migration /flyway/sql
COPY flyway.conf /flyway/conf/flyway.conf
ENTRYPOINT ["flyway", "migrate"]
flyway.conf は以下の通りです。
flyway.url=jdbc:postgresql://${DB_HOST}:5432/${DB_NAME}
flyway.user=${DB_USER}
flyway.password=${DB_PASSWORD}
flyway.locations=filesystem:/flyway/sql
flyway.baselineOnMigrate=true
ECR への Push
# リポジトリ作成
aws ecr create-repository --repository-name myapp
aws ecr create-repository --repository-name myapp-migration
# ログイン
aws ecr get-login-password --region ap-northeast-1 \
| docker login --username AWS --password-stdin \
<account_id>.dkr.ecr.ap-northeast-1.amazonaws.com
# アプリイメージ
docker build -t myapp .
docker tag myapp:latest \
<account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:latest
docker push \
<account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:latest
# マイグレーションイメージ
docker build -f Dockerfile.migration -t myapp-migration .
docker tag myapp-migration:latest \
<account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp-migration:latest
docker push \
<account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp-migration:latest
STEP 6: CloudWatch Logs グループの作成
aws logs create-log-group --log-group-name /ecs/myapp/migration
aws logs create-log-group --log-group-name /ecs/myapp/tomcat
STEP 7: IAM ロールの作成
ECS エージェントが ECR からイメージを取得したり、Secrets Manager から認証情報を取得するために必要なロールです。
aws iam create-role \
--role-name ecsTaskExecutionRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "ecs-tasks.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}'
aws iam attach-role-policy \
--role-name ecsTaskExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
aws iam attach-role-policy \
--role-name ecsTaskExecutionRole \
--policy-arn arn:aws:iam::aws:policy/SecretsManagerReadWrite
STEP 8: タスク定義の登録
マイグレーションコンテナが正常終了してから Tomcat が起動するように dependsOn を設定します。
{
"family": "myapp-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::<account_id>:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "migration",
"image": "<account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp-migration:latest",
"essential": false,
"secrets": [
{ "name": "DB_USER", "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:<account_id>:secret:myapp/db-user" },
{ "name": "DB_PASSWORD", "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:<account_id>:secret:myapp/db-password" }
],
"environment": [
{ "name": "DB_HOST", "value": "myapp-db.xxxx.ap-northeast-1.rds.amazonaws.com" },
{ "name": "DB_NAME", "value": "myapp" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/myapp/migration",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
}
},
{
"name": "tomcat",
"image": "<account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:latest",
"essential": true,
"dependsOn": [
{ "containerName": "migration", "condition": "SUCCESS" }
],
"portMappings": [
{ "containerPort": 8080, "protocol": "tcp" }
],
"environment": [
{ "name": "DB_HOST", "value": "myapp-db.xxxx.ap-northeast-1.rds.amazonaws.com" },
{ "name": "DB_NAME", "value": "myapp" },
{ "name": "REDIS_HOST", "value": "myapp-redis.xxxx.ng.0001.apne1.cache.amazonaws.com" },
{ "name": "REDIS_PORT", "value": "6379" },
{ "name": "TZ", "value": "Asia/Tokyo" }
],
"secrets": [
{ "name": "DB_USER", "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:<account_id>:secret:myapp/db-user" },
{ "name": "DB_PASSWORD", "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:<account_id>:secret:myapp/db-password" },
{ "name": "REDIS_PASSWORD", "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:<account_id>:secret:myapp/redis-password" }
],
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 90
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/myapp/tomcat",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
aws ecs register-task-definition \
--cli-input-json file://task-definition.json
マイグレーションコンテナは essential: false にします。これにより、マイグレーション終了後もタスクが停止しません。
Tomcat は condition: SUCCESS でマイグレーションの正常終了を待ってから起動します。
STEP 9: ALB の作成
# ALB 作成
aws elbv2 create-load-balancer \
--name myapp-alb \
--subnets subnet-aaaaaaaa subnet-bbbbbbbb \
--security-groups sg-alb \
--scheme internet-facing \
--type application
# ターゲットグループ作成
# Fargate は target-type を ip にする必要があります
aws elbv2 create-target-group \
--name myapp-tg \
--protocol HTTP \
--port 8080 \
--vpc-id vpc-xxxxxxxx \
--target-type ip \
--health-check-path /health \
--health-check-interval-seconds 30 \
--healthy-threshold-count 2 \
--unhealthy-threshold-count 3
# リスナー作成
aws elbv2 create-listener \
--load-balancer-arn <alb-arn> \
--protocol HTTP \
--port 80 \
--default-actions Type=forward,TargetGroupArn=<tg-arn>
# Draining を短縮(デフォルト300秒は長すぎる)
aws elbv2 modify-target-group-attributes \
--target-group-arn <tg-arn> \
--attributes Key=deregistration_delay.timeout_seconds,Value=30
STEP 10: ECS クラスター & サービスの作成
# クラスター作成
aws ecs create-cluster --cluster-name myapp-cluster
# サービス作成
aws ecs create-service \
--cluster myapp-cluster \
--service-name myapp-service \
--task-definition myapp-task \
--desired-count 8 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={
subnets=[subnet-aaaaaaaa,subnet-bbbbbbbb],
securityGroups=[sg-ecs],
assignPublicIp=DISABLED
}" \
--load-balancers "targetGroupArn=<tg-arn>,containerName=tomcat,containerPort=8080" \
--deployment-configuration \
"minimumHealthyPercent=50,maximumPercent=200,deploymentCircuitBreaker={enable=true,rollback=true}"
deploymentCircuitBreaker を有効にすることで、デプロイ失敗時に自動で前のリビジョンにロールバックされます。
STEP 11: Auto Scaling の設定
500 RPS のトラフィックの波に対応するために Auto Scaling を設定します。
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/myapp-cluster/myapp-service \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 4 \
--max-capacity 12
aws application-autoscaling put-scaling-policy \
--policy-name cpu-tracking \
--service-namespace ecs \
--resource-id service/myapp-cluster/myapp-service \
--scalable-dimension ecs:service:DesiredCount \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 70.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ECSServiceAverageCPUUtilization"
},
"ScaleOutCooldown": 60,
"ScaleInCooldown": 300
}'
| 設定 | 値 | 理由 |
|---|---|---|
| min-capacity | 4 | 平常時の最低限のタスク数 |
| max-capacity | 12 | ピーク時の最大タスク数 |
| TargetValue | 70% | CPU 70% を超えたらスケールアウト |
| ScaleOutCooldown | 60秒 | 素早くスケールアウト |
| ScaleInCooldown | 300秒 | 急激なスケールインを防ぐ |
完了確認
# タスクが起動しているか確認
aws ecs list-tasks --cluster myapp-cluster
# サービスの状態確認
aws ecs describe-services \
--cluster myapp-cluster \
--services myapp-service \
--query 'services[0].{Status:status,Running:runningCount,Desired:desiredCount}'
# ALB の DNS 名を確認してブラウザでアクセス
aws elbv2 describe-load-balancers \
--names myapp-alb \
--query 'LoadBalancers[0].DNSName'
よくあるハマりポイント
| 問題 | 原因 | 対策 |
|---|---|---|
| タスクが起動してすぐ落ちる | アプリ起動エラー | CloudWatch Logs でスタックトレースを確認 |
| ヘルスチェックが通らない | Tomcat 起動が間に合わない |
startPeriod を 90〜120秒 に設定 |
| ECR からイメージ取得できない |
ecsTaskExecutionRole の権限不足 |
AmazonECSTaskExecutionRolePolicy をアタッチ |
| Redis に接続できない | SG の設定漏れ | sg-ecs → sg-redis 6379 を確認 |
| マイグレーション後に Tomcat が起動しない |
condition: SUCCESS 未設定 |
dependsOn の設定を確認 |
| ALB に 502 が出る | ターゲットタイプが instance になっている |
ip に変更する |
まとめ
| STEP | 内容 |
|---|---|
| 1 | セキュリティグループ作成 |
| 2 | Secrets Manager に認証情報登録 |
| 3 | RDS 構築 |
| 4 | ElastiCache 構築 |
| 5 | ECR にイメージ push |
| 6 | CloudWatch Logs グループ作成 |
| 7 | IAM ロール作成 |
| 8 | タスク定義登録 |
| 9 | ALB 作成 |
| 10 | ECS クラスター & サービス作成 |
| 11 | Auto Scaling 設定 |
Fargate を使うことでサーバー管理が不要になり、デプロイ・スケーリングをAWSに任せられます。
セッション管理・マイグレーション・Auto Scalingまで含めた構成を最初から整えておくことで、後からの改修コストを大幅に減らせます。