金曜日, 9月 26, 2025
金曜日, 9月 26, 2025
- Advertisment -
ホームニューステックニュースSpring Boot WebアプリをECS on Fargateにデプロイする際の注意点、およびecspressoいいよという話

Spring Boot WebアプリをECS on Fargateにデプロイする際の注意点、およびecspressoいいよという話



  • Amazon ECS on Fargate 1.4.0
  • Java 21
  • Spring Boot 3.5
  • Terraform 1.13
  • ecspresso 2.6

ECSって何?という人は下記のBlackbelt資料を読むと良いでしょう(PDFへの直リンクです)。

また、要点のみに絞るため、ECS関連のTerraformリソースの作成方法も詳細には解説しません。それらを学ぶ場合は下記の本をどうぞ。

https://www.oreilly.co.jp/books/9784814400133/

  • ecspressoいいですよ
  • タスク定義に"readonlyRootFilesystem": trueを設定しましょう
  • ECS on Fargateの場合、Cloud Native BuildpacksではなくDockerfileでコンテナイメージを作成しましょう

https://github.com/kayac/ecspresso

ecspressoは簡単に言えば、ECSのサービスとタスク定義をデプロイしてくれるツールです。

それ以外のクラスター・ロードバランサー・IAMロール・セキュリティグループなどはecspressoの範囲外なので、Terraformなどで作成します。

サービスとタスク定義もTerraformで作ることはできます。しかし、サービス+タスク定義=アプリケーションのデプロイ時にTerraformを触らずに済むのは嬉しいです。

ecspresso.yml

ecspressoの設定ファイルです。

ecspresso.yml


region: ap-northeast-1

cluster: web-app-cluster

service: web-app-service

service_definition: ecs-service-def.json

task_definition: ecs-task-def.json

plugins:
  - name: tfstate
    config:
      url: s3://バケット名/tfstateファイル名

サービス定義

ところどころ {{tfstate ... }} と書かれている部分は、Terraformで作成したリソースを利用しています。

このJSONはAWS公式ドキュメントに書かれたサービス定義テンプレートに従って記述します。場合によっては不要な設定もあったりするので、精査しながら記述してください。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/sd-template.html

ecs-service-def.json

{
  "serviceName": "web-app-service",
  "loadBalancers": [
    {
      "targetGroupArn": "{{ tfstate `aws_lb_target_group.web_app.arn` }}",
      "containerName": "web-app",
      "containerPort": 8080
    }
  ],
  "desiredCount": 2,
  "launchType": "FARGATE",
  "platformVersion": "LATEST",
  "platformFamily": "LINUX",
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "subnets": [
        "{{ tfstate `aws_subnet.app_az1.id` }}",
        "{{ tfstate `aws_subnet.app_az2.id` }}"
      ],
      "securityGroups": [
        "{{ tfstate `aws_security_group.web_app.id` }}"
      ],
      "assignPublicIp": "DISABLED"
    }
  },
  "healthCheckGracePeriodSeconds": 10,
  "schedulingStrategy": "REPLICA",
  "enableExecuteCommand": true,
  "availabilityZoneRebalancing": "ENABLED"
}

タスク定義

タスク定義のJSONも、AWS公式ドキュメントに書かれたサービス定義テンプレートに従って記述します。場合によっては不要な設定もあったりするので、精査しながら記述してください。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-definition-template.html

コンテナ内のファイルシステムを悪い人に変更されないよう、"readonlyRootFilesystem": trueとしてルートファイルシステムを読み取りのみに設定します(ただしECS Execができなくなるので注意してください)。

しかしそうすると、Spring Boot内のTomcatが必要なディレクトリを/tmpに作成できなくなります。

そこで、タスクにEphemeral Storageというボリュームを別途定義し、それをコンテナ内の/tmpにマウントします。そうすることで、Tomcatが/tmpに書き込みできるようになります。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/fargate-task-storage.html

ecs-task-def.json

{
  "family": "web-app-task-definition",
  "runtimePlatform": {
    "operatingSystemFamily": "LINUX",
    "cpuArchitecture": "ARM64"
  },
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "cpu": "512",
  "memory": "1024",
  "taskRoleArn": "{{ tfstate `aws_iam_role.web_app.arn` }}",
  "executionRoleArn": "{{ tfstate `aws_iam_role.web_app_execution.arn` }}",
  "networkMode": "awsvpc",
  "volumes": [  {
      "name": "web-app-ephemeral-volume"
    }
  ],
  "containerDefinitions": [
    {
      "name": "web-app",
      "image": "{{ tfstate `aws_ecr_repository.web_app.repository_url` }}:{{ must_env `IMAGE_TAG` }}",
      "user": "webappuser",  "readonlyRootFilesystem": true,  "mountPoints": [
        {
          "sourceVolume": "web-app-ephemeral-volume",  "containerPath": "/tmp",  "readOnly": false
        }
      ],
      "portMappings": [
        {
          "containerPort": 8080,
          "hostPort": 8080,
          "protocol": "tcp"
        }
      ],
      "essential": true,
      "environment": [
        {
          "name": "TZ",
          "value": "Asia/Tokyo"
        },
        {
          "name": "SPRING_PROFILES_ACTIVE",
          "value": "{{ must_env `SPRING_PROFILES_ACTIVE` }}"
        }
      ],
      "secrets": [
        {
          "name": "SPRING_DATASOURCE_URL",
          "valueFrom": "{{ tfstate `data.aws_secretsmanager_secret.rds.arn`}}:jdbcUrl::"
        },
        {
          "name": "SPRING_DATASOURCE_USERNAME",
          "valueFrom": "{{ tfstate `data.aws_secretsmanager_secret.rds.arn`}}:jdbcUsername::"
        },
        {
          "name": "SPRING_DATASOURCE_PASSWORD",
          "valueFrom": "{{ tfstate `data.aws_secretsmanager_secret.rds.arn`}}:jdbcPassword::"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-region": "ap-northeast-1",
          "awslogs-group": "{{ tfstate `aws_cloudwatch_log_group.web_app.name` }}",
          "awslogs-stream-prefix": ""
        }
      }
    }
  ]
}

Dockerfileを書く際は色々と注意点があるのですが、最低限以下の点は気をつけましょう。

  • ベースイメージの脆弱性が無いか確認する
    • TrivyやDocker Hubなどで確認できる
  • アプリ実行用のユーザーを作成する
    • これをしないと、root権限でコンテナが実行される

細かい注意点はSpring公式ブログを見てください。

https://spring.io/guides/gs/spring-boot-docker

加えて、ECS on FargateがEphemeral StorageとマウントするディレクトリをVOLUMEで指定し、かつchownコマンドでディレクトリの所有者をアプリ実行用ユーザーに変更します。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/bind-mounts.html#bind-mount-considerations

Dockerfile



FROM amazoncorretto:21-alpine3.22


RUN apk add --no-cache shadow


RUN mkdir /app


RUN groupadd -r webappgroup && useradd -r -s /usr/sbin/nologin -g webappgroup webappuser


WORKDIR /app



VOLUME /tmp


RUN chown -R webappuser:webappgroup /app \
    && chown -R webappuser:webappgroup /tmp


USER webappuser


COPY ./target/web-app.jar app.jar


ENTRYPOINT ["java","-jar","app.jar"]

Cloud Native Buildpacksには、DockerfileのVOLUME相当の設定が無いようでした(見つけたら教えてください・・・)。なので、Cloud Native Buildpacksは使っていません。

イメージのタグは、個人的にはGitのコミットハッシュにするのが好きです。git rev-parse --short HEAD)でコミットハッシュを取得できます。

Dockerfileでのビルド・ECRへのプッシュ・ecspressoでのデプロイを、シェルスクリプトにまとめます(アプリのディレクトリ直下にDockerfileやecspresso関連ファイルがある前提です)。

build-push-deploy.sh


export IMAGE_TAG=$(git rev-parse --short HEAD)

export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)

export SPRING_PROFILES_ACTIVE=development


mvn clean package -Dmaven.test.skip=true


docker buildx build --platform linux/arm64 -t web-app:${IMAGE_TAG} .


aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com


docker image tag web-app:${IMAGE_TAG} ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/web-app:${IMAGE_TAG}


docker image push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/web-app:${IMAGE_TAG}


ecspresso deploy



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -