ECS Express Modeで未対応のARM64 / FARGATE_SPOT / ECS Execを利用してみた

ECS Express Modeで未対応のARM64 / FARGATE_SPOT / ECS Execを利用してみた

ECS Express Modeの制限を回避し、ARM64(Graviton)、Fargate Spot、ECS Execを事後的に有効化する2段階デプロイプロセスを解説。CloudFormationとAWS CLIを組み合わせた実装手順を紹介します。
2026.02.11

ECS Express Modeは最小限の設定で迅速に環境を構築できる便利な機能ですが、ARM64(Graviton)やFargate Spot、ECS Execなどの機能を、作成時に設定して利用することができません。

今回、ECS Express Modeを採用した開発環境で、コスト改善やデバッグ効率を向上させるため、これらの機能の有効化を試みる機会がありましたので、その手順を紹介します。

課題:Express Modeの制約と回避策

ECS Express Mode(AWS::ECS::ExpressGatewayService)は、タスク定義・サービス・ALB・Auto Scalingなどの低レベルリソースを内部で自動生成・管理する高レベルコンポーネントです。そのため、通常のCloudFormationで指定できる runtimePlatformcapacityProviderStrategy といったプロパティが、Express Modeのテンプレートには存在しません。

アーキテクチャは x86_64 に固定、update-express-gateway-service APIを通じても変更できません。

この制約を回避するため、以下の2段階デプロイを実施しました。

  1. ダミーデプロイ: 本番と同一ポート・パスを持つx86ダミーイメージでスタックを作成。
  2. アーキテクチャ転換:自動生成されたタスク定義をCLIでARM64に編集し、update-service で適用。

手順1:CloudFormationによる初期スタック作成

まずはx86のダミーイメージを使用して、Express Modeの管理下にあるリソースを正常にデプロイしました。

検証コード(CloudFormationテンプレート)

  • ECS Execを利用するため、SSM用のIAMは初期テンプレートの設置時点で設定しました。
初期テンプレート全文
AWSTemplateFormatVersion: '2010-09-09'
Description: 'ECS Express Mode - 初回設置用(x86ダミー)'

Parameters:
  ServiceName:
    Type: String
    Default: 'my-app-dev'
  InitialStackName:
    Type: String
    Default: 'ecs-express-initial'

Resources:
  ECSLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/ecs/default/${ServiceName}-service'
      RetentionInDays: 14

  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${ServiceName}-task-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: ECSExecPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ssmmessages:CreateControlChannel
                  - ssmmessages:CreateDataChannel
                  - ssmmessages:OpenControlChannel
                  - ssmmessages:OpenDataChannel
                Resource: '*'

  ExpressModeService:
    Type: AWS::ECS::ExpressGatewayService
    DependsOn: ECSLogGroup
    Properties:
      ServiceName: !Sub '${ServiceName}-service'
      Cluster: 'default'
      ExecutionRoleArn:
        Fn::ImportValue: !Sub '${InitialStackName}-TaskExecutionRoleArn'
      InfrastructureRoleArn:
        Fn::ImportValue: !Sub '${InitialStackName}-InfrastructureRoleArn'
      TaskRoleArn: !GetAtt TaskRole.Arn
      PrimaryContainer:
        Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/my-app:dummy'
        ContainerPort: 8000
        AwsLogsConfiguration:
          LogGroup: !Sub '/aws/ecs/default/${ServiceName}-service'
          LogStreamPrefix: 'ecs'
      NetworkConfiguration:
        Subnets:
          Fn::Split:
            - ','
            - Fn::ImportValue: !Sub '${InitialStackName}-PrivateSubnets'
      Cpu: '256'
      Memory: '512'
      HealthCheckPath: '/health'
      ScalingTarget:
        MinTaskCount: 1
        MaxTaskCount: 1
        AutoScalingMetric: 'AVERAGE_CPU'
        AutoScalingTargetValue: 70

Outputs:
  ServiceEndpoint:
    Value: !GetAtt ExpressModeService.Endpoint

このテンプレートをデプロイし、タスク定義の runtimePlatform がデフォルトの x86_64 で起動していることを確認しました。


手順2:タスク定義のARM64化と適用

Express Modeが自動生成したタスク定義リビジョンを取得し、cpuArchitectureARM64 に、イメージを本番用ARMイメージに置換して登録しました。

SERVICE_NAME="my-app-dev-service"
TD_FAMILY="default-${SERVICE_NAME}"
REGION="ap-northeast-1"
ARM_IMAGE="123456789012.dkr.ecr.$REGION.amazonaws.com/my-app:latest"

# 1. 現在のタスク定義を取得
LATEST=$(aws ecs list-task-definitions --family-prefix $TD_FAMILY --sort DESC \
    --region $REGION --query 'taskDefinitionArns[0]' --output text)

aws ecs describe-task-definition --task-definition $LATEST \
    --region $REGION --query 'taskDefinition' > /tmp/td.json

# 2. ARM64 + 本番イメージに変更(メタデータの削除を含む)
jq --arg img "$ARM_IMAGE" '
  .runtimePlatform.cpuArchitecture = "ARM64" |
  .containerDefinitions[0].image = $img |
  .cpu = "512" | .memory = "2048" |
  del(.taskDefinitionArn, .revision, .status, .requiresAttributes,
      .compatibilities, .registeredAt, .registeredBy)
' /tmp/td.json > /tmp/td_arm.json

# 3. 新リビジョンを登録
aws ecs register-task-definition \
    --cli-input-json file:///tmp/td_arm.json \
    --region $REGION

# 4. サービスを更新
NEW_REV=$(aws ecs list-task-definitions --family-prefix $TD_FAMILY --sort DESC \
    --region $REGION --query 'taskDefinitionArns[0]' --output text)

aws ecs update-service \
    --cluster default \
    --service $SERVICE_NAME \
    --task-definition $NEW_REV \
    --force-new-deployment \
    --region $REGION

update-stack 実行時の挙動確認

CLIを用いてARM64へ切り替えた後、CloudFormationの update-stack を実行し、テンプレート内のイメージ設定を変更しました。

  • update-stack の実行により、新しいタスク定義リビジョンが生成されました。
  • 新しいリビジョンにおいても、runtimePlatform 内の cpuArchitectureARM64 として維持されていることを確認しました。
  • テンプレートで指定したイメージへの更新が、新リビジョンに反映されていることを確認しました。

Express Modeのスタック更新時、現在稼働中のタスク定義からアーキテクチャ設定を引き継ぐ挙動を確認できました。


手順3:FARGATE_SPOTとECS Execの有効化

ARM64への転換後、さらにコスト最適化とデバッグ機能を有効化しました。
これらは aws ecs update-service で適用しました。

aws ecs update-service \
    --cluster default \
    --service my-app-dev-service \
    --capacity-provider-strategy capacityProvider=FARGATE_SPOT,base=0,weight=1 \
    --enable-execute-command \
    --propagate-tags TASK_DEFINITION \
    --force-new-deployment \
    --region ap-northeast-1

確認

設定反映後、ECS Execでコンテナ内にログインし、アーキテクチャを確認しました。

# コンテナ内で実行
$ uname -m
aarch64

aarch64 と表示され、ARM64(Graviton)で動作していることを確認しました。また、マネジメントコンソール上のサービス設定において、キャパシティプロバイダー戦略に FARGATE_SPOT が適用されていることを確認しました。

機能 デフォルト 拡張後の設定 方法
Arch x86_64 ARM64 (Graviton) タスク定義の再登録
購入モデル FARGATE FARGATE_SPOT update-service (CLI)
デバッグ 無効 ECS Exec 有効 IAM付与 + update-service

まとめ

ECS Express Modeにおいても、2段階のデプロイプロセスを経ることでARM64、Fargate Spot、ECS Execを利用することができました。

ただし現時点で公式ドキュメントに明記されていない手順となるため、今後のアップデートにより挙動が変化したり、IaC外の変更による干渉や、ドリフトなどの問題が生じる可能性があります。開発、検証環境など自己責任で利用できる範囲に留めた利用をおすすめします。

この記事をシェアする

FacebookHatena blogX

関連記事