Amazon ECS Service ConnectをAWS CloudFormationでデプロイしてみた

2023.05.31

こんにちは、シマです。

当ブログサイト(DevelopersIO)では、様々な実装方式で「Amazon ECS Service Connect」のやってみた記事が掲載されています。

しかし、AWS CloudFormationでの実装が見当たらなかったので、今回はAWS CloudFormationで実装し、簡単な動作確認をしてみることにしました。構成については、上記のCDKの記事を参考にしています。

構成

名前空間内にウェブ用とクライアント用の2つのFargateサービスを作成します。タスク定義は同じものを流用していて、動作確認ではAWS CloudShellからECS Execによりクライアント用コンテナへログインします。その後、クライアント用コンテナからcurlコマンドでウェブ用コンテナへアクセスし、サイトの確認を行います。

テンプレートのポイント

Amazon ECS Service Connectをテンプレートで実装する上で、実施した点や注意が必要な点を以下に記載します。

名前空間

Cluster作成時に「ServiceConnectDefaults」を指定すると作成されます。

  cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: cluster
      ServiceConnectDefaults:
        Namespace: local

Amazon ECS Service Connectの設定

コンテナ定義にポートマッピングで名前を指定します。

  taskdefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - PortMappings:
            - Name: website

サービスに「ServiceConnectConfiguration」を追加します。
「Namespace」はCluster作成時のものを指定し、「PortName」はコンテナ定義で指定したものを指定します。

  serviceclient:
    Type: AWS::ECS::Service
    Properties:
      ServiceConnectConfiguration:
        Enabled: true
        Namespace: local
        Services:
          - DiscoveryName: client
            ClientAliases:
              - DnsName: "client.local"
                Port: 80
            PortName: website

考慮事項

AWS CloudFormationによる実装に限った話ではないですが、AWS公式ドキュメントにAmazon ECS Service Connectに関する考慮事項が記載されています。

考慮事項には主に利用するための前提条件が記載されており、個人的に気になった箇所は以下です。

Amazon ECS 最適化 Amazon Linux 2023 AMI は、Service Connect ではサポートされていません。これは、Service Connect エージェントが Amazon Linux 2023 で使用できないためです。
コンテナインスタンスが Service Connect を使用するには、Amazon ECS に最適化された Amazon Linux 2 AMI バージョン 2.0.20221115 以降を実行する必要があります。

現時点(2023/5/31)では、日本語版で上記のように記載されていますが、英語版を確認すると以下のように記載されていました。

Container instances must run the Amazon ECS-optimized Amazon Linux 2023 AMI version 20230428 or later, or Amazon ECS-optimized Amazon Linux 2 AMI version 2.0.20221115 to use Service Connect. For more information about the Service Connect agent, see Amazon ECS Service Connect Agent on GitHub.

そのため、今回はAmazon Linux 2023 AMI version 20230517を使用しました。
※試しにサポートされていないもの(20230322)でデプロイしてみましたが、エラー等は発生せずにデプロイできてしまいました。何かトラブル発生時にはサポートされていないという点にご注意ください。

動作確認

AWS CloudFormationでデプロイしたことによる特殊な点はないので、サクッと動作確認をします。

AWS CloudShellからECS Execによりクライアント用コンテナへログインします。

aws ecs execute-command \
>     --cluster cluster \
>     --task xxxxxxxxxxxxxxx \
>     --container container \
>     --interactive \
>     --command "/bin/bash"

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.

Starting session with SessionId: ecs-execute-command-xxxxxxxxx
bash-5.2#

クライアント用コンテナからcurlコマンドでウェブ用コンテナへアクセスし、サイトの確認を行います。

bash-5.2# curl http://web.local/
<html> <head> <title>Sample</title> </head> <body> TEST </body> </html>

問題なくアクセスできました。

使用したテンプレート全体

今回使用したAWS CloudFormationテンプレートファイルを記載します。

template.yml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  Amazon ECS Service Connect Sample

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  AZa:
    Type: AWS::EC2::AvailabilityZone::Name
    Default: ap-northeast-1a
  AZc:
    Type: AWS::EC2::AvailabilityZone::Name
    Default: ap-northeast-1c

Resources:

# VPC
# ------------------------------------------------------------#
  vpctest:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: "172.16.0.0/16"
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
      - Key: Name
        Value: "vpc-test"

# RouteTable
# ------------------------------------------------------------#
  rtpublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref vpctest
      Tags:
      - Key: Name
        Value: "rt-public"

# Subnet
# ------------------------------------------------------------#
  snpublica:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref vpctest
      CidrBlock: "172.16.1.0/24"
      AvailabilityZone: !Ref AZa
      Tags:
      - Key: Name
        Value: "sn-public-a"
  rtasnpublica:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref snpublica
      RouteTableId: !Ref rtpublic

  snpublicc:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref vpctest
      CidrBlock: "172.16.2.0/24"
      AvailabilityZone: !Ref AZc
      Tags:
      - Key: Name
        Value: "sn-public-c"
  rtasnpublicc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref snpublicc
      RouteTableId: !Ref rtpublic

# Internet Gateway
# ------------------------------------------------------------#
  igw:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: "igw"
  atigw:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref vpctest
      InternetGatewayId: !Ref igw

  rtpublicigw:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref rtpublic
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref igw

# Security Group
# ------------------------------------------------------------#
  sgclient:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: sgclient
      GroupDescription: sgclient
      VpcId: !Ref vpctest
      SecurityGroupEgress:
      - IpProtocol: "-1"
        CidrIp: 0.0.0.0/0
      Tags:
      - Key: Name
        Value: sgclient

  sgweb:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: sgweb
      GroupDescription: sgweb
      VpcId: !Ref vpctest
      SecurityGroupEgress:
      - IpProtocol: "-1"
        CidrIp: 0.0.0.0/0
      Tags:
      - Key: Name
        Value: sgweb

  sgwebfromclient:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      SourceSecurityGroupId: !Ref sgclient
      GroupId: !Ref sgweb
      Description: "web from client"

# IAM Role
# ------------------------------------------------------------#
  roleecstaskexec:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      RoleName: "role-ecs-taskexec"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Principal:
              Service:
                - ecs-tasks.amazonaws.com
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
      Policies:
        - PolicyName: roleecstaskexecpolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                Resource: '*'

  roleecstask:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      RoleName: "role-ecstask"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Principal:
              Service:
                - ecs-tasks.amazonaws.com
      Policies:
        - PolicyName: roleecstaskpolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - ssmmessages:CreateControlChannel
                  - ssmmessages:CreateDataChannel
                  - ssmmessages:OpenControlChannel
                  - ssmmessages:OpenDataChannel
                  - logs:CreateLogGroup
                Resource: '*'

# LogGroup
# ------------------------------------------------------------#
  ecslogs:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      LogGroupName: "/ecs/logs"
      RetentionInDays: 30

# Cluster
# ------------------------------------------------------------#
  cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: cluster
      ServiceConnectDefaults:
        Namespace: local

# TaskDefinition
# ------------------------------------------------------
  taskdefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: taskdefinition
      Cpu: 512
      Memory: 1024
      ExecutionRoleArn: !GetAtt roleecstaskexec.Arn
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      TaskRoleArn: !GetAtt roleecstask.Arn
      ContainerDefinitions:
        - Name: container
          Essential: True
          Image: "public.ecr.aws/amazonlinux/amazonlinux:2023.0.20230517.1"
          Command:
            - "/bin/sh -c \"yum install -y httpd && echo '<html> <head> <title>Sample</title> </head> <body> TEST </body> </html>' > /var/www/html/index.html && /usr/sbin/httpd -D FOREGROUND\""
          EntryPoint:
            - "sh"
            - "-c"
          Memory: 512
          PortMappings:
            - ContainerPort: 80
              Protocol: TCP
              AppProtocol: http
              Name: website
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: "/ecs/logs"
              awslogs-region: "ap-northeast-1"
              awslogs-stream-prefix: ecs
              awslogs-create-group: true

# Service
# ------------------------------------------------------------#
  serviceweb:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: "service-web"
      Cluster: !Ref cluster
      DesiredCount: 2
      LaunchType: FARGATE
      EnableExecuteCommand: True
      TaskDefinition: !Ref taskdefinition
      PlatformVersion: LATEST
      ServiceConnectConfiguration:
        Enabled: true
        Namespace: local
        Services:
          - DiscoveryName: web
            ClientAliases:
              - DnsName: "web.local"
                Port: 80
            PortName: website
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
          - !Ref sgweb
          Subnets:
          - !Ref snpublica
          - !Ref snpublicc

  serviceclient:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: "service-client"
      Cluster: !Ref cluster
      DesiredCount: 2
      LaunchType: FARGATE
      EnableExecuteCommand: True
      TaskDefinition: !Ref taskdefinition
      PlatformVersion: LATEST
      ServiceConnectConfiguration:
        Enabled: true
        Namespace: local
        Services:
          - DiscoveryName: client
            ClientAliases:
              - DnsName: "client.local"
                Port: 80
            PortName: website
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
          - !Ref sgclient
          Subnets:
          - !Ref snpublica
          - !Ref snpublicc

最後に

今回はAmazon ECS Service ConnectをAWS CloudFormationで実装してみました。実装する際に気になったことを調べてみたり、実際に試してみることで理解度が深まりました。
本記事がどなたかのお役に立てれば幸いです。

(※2023/6/6追記) 本記事内のテンプレートはスタック削除時に名前空間が削除されずに残っていました。それを解消したものを以下の記事に掲載しています。