Amazon ECS Execを使って、プライベートサブネット内のECS(Fargate)コンテナにアクセスする

Amazon ECS Execを使って、プライベートサブネット内のECS(Fargate)コンテナにアクセスする

Amazon ECS Execを使って、プライベートサブネット内のECS(Fargate)コンテナにアクセスする

Amazon ECS Execを使用すると、稼働中のECSコンテナにアクセスすることができます。

Amazon ECS Exec を使用すれば、最初にホストコンテナのオペレーティングシステムとやり取りしたり、インバウンドポートを開いたり、SSH キーを管理したりすることなく、コンテナと直接やり取りできます。ECS Exec を使用して、Amazon EC2 インスタンスまたは AWS Fargate で実行されているコンテナでコマンドを実行したり、シェルを取得したりできます。これにより、診断情報を収集し、エラーを迅速にトラブルシューティングすることが容易になります。

デバッグ用にAmazon ECS Exec を使用

本ページでは、プライベートサブネット内のECS(Fargate)コンテナにアクセスする方法を確認します。

構築する環境

Diagram of using Amazon ECS Exec to access ECS (Fargate) containers in private subnet.

2つのプライベートサブネット内にそれぞれFargateタイプのECSサービス・タスクを定義します。

ECS Execを使用して、それぞれのコンテナにアクセスします。
各コンテナには、以下の経路でアクセスします。

  • NATゲートウェイ
  • VPCエンドポイント

ECRリポジトリ(S3)についても同様の経路でアクセスします。

今回使用するDockerイメージをCodeBuildを使ってビルドします。
ビルドする際に、Secrets Managerに保存されたDockerHubのアカウント情報を参照します。

CloudFormationカスタムリソースに関連づけたLambda関数を作成します。
この関数の働きは、CodeBuildによるビルドを開始することです。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-fa/tree/main/127

テンプレートファイルのポイント解説

ECS Execを使用するためのネットワーク設定

NATゲートウェイ

Resources:
  IGW:
    Type: AWS::EC2::InternetGateway

  IGWAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref IGW

  EIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIP.AllocationId
      SubnetId: !Ref PublicSubnet

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref CidrIp1
      VpcId: !Ref VPC
      AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone1}"

  ContainerSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref CidrIp2
      VpcId: !Ref VPC
      AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone1}"

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  ContainerRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  RouteToInternet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW

  RouteToNATGateway:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref ContainerRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway

  PublicRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

  ContainerRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref ContainerSubnet1
      RouteTableId: !Ref ContainerRouteTable1
Code language: YAML (yaml)

パブリックサブネット上にNATゲートウェイを作成します。
プライベートサブネット上のインスタンスが、NATゲートウェイを経由してECS Exec(System Manager)と通信できるように、ルートテーブルを以下の通りに設定します。

  • プライベートサブネット用のルートテーブル:NATゲートウェイ向けにデフォルトルートを設定
  • パブリックサブネット用のルートテーブル:インターネットゲートウェイ向けにデフォルトルートを設定

VPCエンドポイント

Resources:
  ContainerRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  ContainerRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref ContainerSubnet2
      RouteTableId: !Ref ContainerRouteTable2

  ContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${Prefix}-ContainerSecurityGroup"
      GroupDescription: Deny all.
      VpcId: !Ref VPC

  EndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${Prefix}-EndpointSecurityGroup"
      GroupDescription: Allow HTTPS from ContainerSecurityGroup.
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref HTTPSPort
          ToPort: !Ref HTTPSPort
          SourceSecurityGroupId: !Ref ContainerSecurityGroup

  S3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref ContainerRouteTable2
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      VpcId: !Ref VPC

  SSMMessagesEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref EndpointSecurityGroup
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages"
      SubnetIds:
        - !Ref ContainerSubnet2
      VpcEndpointType: Interface
      VpcId: !Ref VPC

  ECRDkrEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref EndpointSecurityGroup
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.dkr"
      SubnetIds:
        - !Ref ContainerSubnet2
      VpcEndpointType: Interface
      VpcId: !Ref VPC

  ECRApiEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref EndpointSecurityGroup
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.api"
      SubnetIds:
        - !Ref ContainerSubnet2
      VpcEndpointType: Interface
      VpcId: !Ref VPC
Code language: YAML (yaml)

2つの用途で合計4つのVPCエンドポイントを作成します。

  • ECS Execを使用してイメージにアクセスするため
  • ECRリポジトリからイメージをプルするため

なおECS Exec用のVPCエンドポイントですが、以下の通りに言及されています。

ECS Exec 機能を使用する場合は、Systems Manager セッションマネージャーのインターフェイス VPC エンドポイントを作成する必要があります。

ECS Exec 機能を使用する場合は、Systems Manager Session Manager VPC エンドポイントを作成します

上記に従い、ssmmessages用のVPCエンドポイントを定義します。

Fargate

本ページでは、ECS Execを使用して、プライベートサブネット内のFargateコンテナにアクセスする方法を中心に取り上げます。

プライベートサブネットでFargateを展開する方法については、以下のページをご確認ください。

https://awstut.com/2022/07/24/create-ecs-fargate-in-private-subnet

タスク用IAMロール

Resources:
  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      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: "*"
Code language: YAML (yaml)

ECS Execを使用するためには、タスク用のIAMロールに関して要件があります。

ECS Exec 機能には、マネージド型 SSM エージェント (execute-command エージェント) と SSM サービス間の通信に必要なアクセス許可をコンテナに付与するためのタスク IAM ロール ロールが必要です。

ECS Exec の使用

引用元の公式ページを参照して、4つのアクションを許可します。

タスク定義

Resources:
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Cpu: !Ref TaskCpu
      ContainerDefinitions:
        - Name: !Sub "${Prefix}-amazon-linux"
          Command:
            - "/bin/sh"
            - "-c"
            - "sleep 3600"
          Essential: true
          Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryName}:latest"
          LinuxParameters:
            InitProcessEnabled: true
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: !Sub "${Prefix}-amazon-linux"
      ExecutionRoleArn: !Ref FargateTaskExecutionRole
      Memory: !Ref TaskMemory
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      RuntimePlatform:
        CpuArchitecture: ARM64
        OperatingSystemFamily: LINUX
      TaskRoleArn: !Ref TaskRole
Code language: YAML (yaml)

こちらも以下のページを参考に設定しました。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/ecs-exec.html#ecs-exec-enabling-and-using

ポイントはLinuxParametersプロパティ内のInitProcessEnabledプロパティです。
本プロパティに「true」を指定すると、以下に引用する効果が得られます。

タスク定義パラメーター initProcessEnabled を true に設定すると、コンテナ内で init プロセスが開始され、見つかったゾンビ SSM Agent の子プロセスがすべて削除されます。

ECS Exec の使用

サービス

Resources:
  Service1:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref Cluster
      LaunchType: FARGATE
      DesiredCount: 1
      EnableExecuteCommand: true
      TaskDefinition: !Ref TaskDefinition
      ServiceName: !Sub "${Prefix}-service1"
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups:
            - !Ref ContainerSecurityGroup
          Subnets:
            - !Ref ContainerSubnet1

  Service2:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref Cluster
      LaunchType: FARGATE
      DesiredCount: 1
      EnableExecuteCommand: true
      TaskDefinition: !Ref TaskDefinition
      ServiceName: !Sub "${Prefix}-service2"
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups:
            - !Ref ContainerSecurityGroup
          Subnets:
            - !Ref ContainerSubnet2
Code language: YAML (yaml)

2つのサービスを定義します。
設定そのものは全く同じですが、配置先のサブネットが異なります。
それぞれNATゲートウェイ経由、VPCエンドポイント経由で通信することになります。

ポイントはEnableExecuteCommandプロパティです。
本プロパティに「true」を指定することで、サービスおよびタスクでECS Execが有効化されます。

(参考)CFNカスタムリソースとCodeBuildを使って、テストイメージをECRに自動的にプッシュする

Resources:
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: NO_ARTIFACTS
      Cache:
        Type: NO_CACHE
      Environment:
        ComputeType: !Ref ProjectEnvironmentComputeType
        EnvironmentVariables:
          - Name: DOCKERHUB_PASSWORD
            Type: SECRETS_MANAGER
            Value: !Sub "${Secret}:password"
          - Name: DOCKERHUB_USERNAME
            Type: SECRETS_MANAGER
            Value: !Sub "${Secret}:username"
        Image: !Ref ProjectEnvironmentImage
        ImagePullCredentialsType: CODEBUILD
        Type: !Ref ProjectEnvironmentType
        PrivilegedMode: true
      LogsConfig:
        CloudWatchLogs:
          Status: DISABLED
        S3Logs:
          Status: DISABLED
      Name: !Ref Prefix
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Source:
        Type: NO_SOURCE
        BuildSpec: !Sub |
          version: 0.2

          phases:
            pre_build:
              commands:
                - echo Logging in to Amazon ECR...
                - aws --version
                - aws ecr get-login-password --region ${AWS::Region} | docker login --username AWS --password-stdin ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com
                - REPOSITORY_URI=${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryName}
                - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
                - IMAGE_TAG=${!COMMIT_HASH:=latest}
                - echo Logging in to Docker Hub...
                - echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin
                - |
                  cat << EOF > Dockerfile
                  FROM amazonlinux:latest
                  EXPOSE 80
                  EOF
            build:
              commands:
                - echo Build started on `date`
                - echo Building the Docker image...
                - docker build -t $REPOSITORY_URI:latest .
                - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
            post_build:
              commands:
                - echo Build completed on `date`
                - echo Pushing the Docker images...
                - docker push $REPOSITORY_URI:latest
                - docker push $REPOSITORY_URI:$IMAGE_TAG
      Visibility: PRIVATE
Code language: YAML (yaml)

CodeBuildを使って、イメージをビルド後、コンテナのイメージをECRリポジトリに用意します。
今回は最新バージョンのAmazon Linuxをベースとします。

詳細につきましては、以下のページをご確認ください。

https://awstut.com/2023/04/30/automatically-push-test-images-to-ecr-using-cfn-custom-resources-and-codebuild

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

https://awstut.com/2021/12/02/cloudformation-nested-stacks

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • ECRリポジトリ:fa-127
  • ECSクラスター:fa-127
  • ECSサービス1:fa-127-service1
  • ECSサービス2:fa-127-service2

AWS Management Consoleから各リソースを確認します。

ECRリポジトリを確認します。

Detail of ECR 1.

正常にイメージがプッシュされています。
つまりCloudFormationカスタムリソースに関連づいたLambda関数によって、CodeBuildの開始がトリガーされました。
そしてイメージのビルドが行われた後に、こちらにプッシュされたということです。

ECSクラスターを確認します。

Detail of ECS 1.

クラスター内に2つのサービスが動作していることがわかります。

それぞれサービスを確認します。

Detail of ECS 2.
Detail of ECS 3.

各サービスに1つずつタスクが作成されており、そのタスクにはコンテナが1つ起動しています。

情報を整理します。

  • サービス1
    • タスクID:a986b1c860bf4f8cb02740b037514351
    • コンテナ名:fa-127-amazon-linux
  • サービス2
    • タスクID:b6f91fb2dc89471f811b052cca7be0ee
    • コンテナ名:fa-127-amazon-linux

動作確認

NATゲートウェイ経由でコンテナにアクセス

準備が整いましたので、ECS Execを使用して、サービス1のコンテナにアクセスします。

アクセスはAWS CLIを使用します。

% aws ecs execute-command \
--cluster fa-127-cluster \
--task a986b1c860bf4f8cb02740b037514351 \
--container fa-127-amazon-linux \
--interactive --command "/bin/sh"

...

sh-5.2#
Code language: Bash (bash)

サービス1のコンテナにアクセスすることができました。
このようにNATゲートウェイ経由でも、ECS Execを使用して、コンテナにアクセすることができます。

VPCエンドポイント経由でコンテナにアクセス

同様にサービス2のコンテナにアクセスします。

% aws ecs execute-command \
--cluster fa-127-cluster \
--task b6f91fb2dc89471f811b052cca7be0ee \
--container fa-127-amazon-linux \
--interactive \
--command "/bin/sh"

...

sh-5.2#
Code language: Bash (bash)

サービス2のコンテナにもアクセスすることができました。
このようにVPCエンドポイント経由でも、ECS Execを使用して、コンテナにアクセすることができます。

まとめ

プライベートサブネット内のECS(Fargate)コンテナにアクセスする方法を確認します。
プライベートサブネット内のコンテナに対しては、NATゲートウェイおよびVPCエンドポイントを経由することでアクセス可能です。