0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TomcatアプリをECS Fargateで動かす完全手順

0
Posted at

はじめに

既存の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まで含めた構成を最初から整えておくことで、後からの改修コストを大幅に減らせます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?