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

2023.06.06

こんにちは、シマです。

先日、以下の記事を書きました。今回はそのリソース削除に関するお話です。

背景

そもそも、AWS CloudFormationではスタックを削除することで作成したリソースを削除することが可能です。

今回はタイトル通りAWS CloudFormationにより、Amazon ECS Service Connectを実装しており、テンプレート削除により実装したリソースの削除を行いました。・・行ったつもりでした。実際何が起こっていたかというと名前空間が削除されずに残っていました。

問題はある?

では、名前空間が削除されずに残っていることにより何か問題があるのでしょうか。結論としては影響のあるような問題はなさそうです。
真っ先に気になる料金ですが、名前空間が残っているだけでは、料金の発生はなさそうでした。(よかった)

次に検証のため、AWS CloudFormationによるスタックの作成、削除の繰り返しを行うと思いますが、名前空間が残っていることによるエラー発生はせずに実施できます。これは、スタックの再作成時には、残ってしまっている名前空間に再度関連付けされているのでエラーにならずに作成ができています。

暫定対応

AWS CloudFormationのスタックは削除済みなので、AWS CloudFormationからアクションをすることはできません。単純にAWS マネジメントコンソールから削除すれば削除できます。


ですが、せっかくAWS CloudFormationで作成しているのに手動による削除対応は正直モヤッとします。

AWS CloudFormationの対応

恒久対応としてテンプレートを変更しました。変更内容は以下の追加を行いました。

# Namespace
# ------------------------------------------------------------#
  namespace:
    Type: AWS::ServiceDiscovery::HttpNamespace
    Properties:
      Name: local

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

ECSクラスター作成時の自動生成により名前空間を作成することをやめて、別で定義して作成するようにしました。また、同じ名前空間名を指定することでリソースの関連付けをしているため、依存関係が指定されていません。依存関係が指定されていないと、リソース作成や削除時にエラーが出ます。

そのため、DependsOnで明示的に依存関係を宣言しています。

テンプレート全体

前回のテンプレートに対して、前述の更新内容を反映しました。

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

# Namespace
# ------------------------------------------------------------#
  namespace:
    Type: AWS::ServiceDiscovery::HttpNamespace
    Properties:
      Name: local

# Cluster
# ------------------------------------------------------------#
  cluster:
    DependsOn: namespace
    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

最後に

今回はスタックの削除で削除しきれていなかった名前空間をスタックの削除で同時に削除されるようにテンプレートを更新しました。
本記事がどなたかのお役に立てれば幸いです。