Search code examples
amazon-web-servicesaws-cloudformationamazon-ecsaws-fargateaws-cloudformation-custom-resource

Translate ECS service to cloudformation


I am trying to translate a manually created service to a cloudformation template but I keep getting errors.

Task definition is already created with UI because it needs some specific roles

This template gives me: Classic Load Balancers are not supported with Fargate

  ServicesSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for cluster services
      VpcId: !Ref 'VPC'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 0
          ToPort: 65535
          SourceSecurityGroupId: !Ref "PublicLoadBalancerSG"

  ServiceStaging:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: pouch-svc-staging
      TaskDefinition: pouch-td-staging:4
      Cluster: !Ref 'ClusterECS'
      DesiredCount: 2
      SchedulingStrategy: REPLICA
      LaunchType: FARGATE
      EnableECSManagedTags: true
      DeploymentConfiguration:
        MinimumHealthyPercent: 100
        MaximumPercent: 200
        DeploymentCircuitBreaker:
          Enable: false
          Rollback: false
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          Subnets:
            - !Ref PublicSubnetOne
            - !Ref PublicSubnetTwo
          SecurityGroups:
            - !Ref ServicesSG
      LoadBalancers:
        - ContainerName: pouch-image-staging
          LoadBalancerName: !Ref 'LoadBalancerName'
          ContainerPort: 3100

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

Update: Here is the modified full cloudformation template without a load balancer name as explicited by some comments

AWSTemplateFormatVersion: '2010-09-09'
Description: VPC, subnets and external, public facing load balancer, for forwarding public traffic to containers
Parameters:
  LoadBalancerName:
    Type: String
    Default: pouch-api-elb
  ClusterName:
    Type: String
    Default: pouch-api-cluster

Mappings:
  SubnetConfig:
    VPC:
      CIDR: '172.16.0.0/16'
    PublicOne:
      CIDR: '172.16.0.0/24'
    PublicTwo:
      CIDR: '172.16.1.0/24'

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      EnableDnsSupport: true
      EnableDnsHostnames: true
      CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR']

  PublicSubnetOne:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
         Fn::Select:
         - 0
         - Fn::GetAZs: {Ref: 'AWS::Region'}
      VpcId: !Ref 'VPC'
      CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
      MapPublicIpOnLaunch: true
  PublicSubnetTwo:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
         Fn::Select:
         - 1
         - Fn::GetAZs: {Ref: 'AWS::Region'}
      VpcId: !Ref 'VPC'
      CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR']
      MapPublicIpOnLaunch: true

  InternetGateway:
    Type: AWS::EC2::InternetGateway
  GatewayAttachement:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref 'VPC'
      InternetGatewayId: !Ref 'InternetGateway'
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref 'VPC'
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: GatewayAttachement
    Properties:
      RouteTableId: !Ref 'PublicRouteTable'
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref 'InternetGateway'
  PublicSubnetOneRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetOne
      RouteTableId: !Ref PublicRouteTable
  PublicSubnetTwoRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetTwo
      RouteTableId: !Ref PublicRouteTable

  PublicLoadBalancerSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Access to the public facing load balancer
      VpcId: !Ref 'VPC'
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: -1
  PublicLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Ref 'LoadBalancerName'
      Scheme: internet-facing
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: "30"
      Subnets:
        - !Ref PublicSubnetOne
        - !Ref PublicSubnetTwo
      SecurityGroups: [!Ref "PublicLoadBalancerSG"]

  TargetGroupStaging:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 6
      HealthCheckPath: /
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      TargetType: ip
      UnhealthyThresholdCount: 2
      VpcId: !Ref 'VPC'
  TargetGroupProduction:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 6
      HealthCheckPath: /
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      TargetType: ip
      UnhealthyThresholdCount: 2
      VpcId: !Ref 'VPC'

  ListenerRuleProduction:
    Type: "AWS::ElasticLoadBalancingV2::ListenerRule"
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref "TargetGroupProduction"
      Conditions:
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - /production/*
      ListenerArn: !Ref PublicLoadBalancerListener
      Priority: 100
  ListenerRuleStaging:
    Type: "AWS::ElasticLoadBalancingV2::ListenerRule"
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref "TargetGroupStaging"
      Conditions:
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - /staging/*
      ListenerArn: !Ref PublicLoadBalancerListener
      Priority: 50

  PublicLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: "redirect"
          RedirectConfig:
            Protocol: "#{protocol}"
            Port: "#{port}"
            Host: "#{host}"
            Path: "/production/"
            StatusCode: "HTTP_301"
      LoadBalancerArn: !Ref "PublicLoadBalancer"
      Port: 80
      Protocol: HTTP

  ClusterECS:
    Type: AWS::ECS::Cluster
    DependsOn: PublicLoadBalancerListener
    Properties:
      ClusterName: !Ref 'ClusterName'
      CapacityProviders:
        - FARGATE

  ServicesSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for cluster services
      VpcId: !Ref 'VPC'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 0
          ToPort: 65535
          SourceSecurityGroupId: !Ref "PublicLoadBalancerSG"

  ServiceStaging:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: pouch-svc-staging
      TaskDefinition: pouch-td-staging:4
      Cluster: !Ref 'ClusterECS'
      DesiredCount: 2
      SchedulingStrategy: REPLICA
      LaunchType: FARGATE
      EnableECSManagedTags: true
      DeploymentConfiguration:
        MinimumHealthyPercent: 100
        MaximumPercent: 200
        DeploymentCircuitBreaker:
          Enable: false
          Rollback: false
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          Subnets:
            - !Ref PublicSubnetOne
            - !Ref PublicSubnetTwo
          SecurityGroups:
            - !Ref ServicesSG
      LoadBalancers:
        - ContainerName: pouch-image-staging
          TargetGroupArn: !Ref 'TargetGroupStaging'
          ContainerPort: 3100
  ServiceProduction:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: pouch-svc-production
      TaskDefinition: pouch-td-production:4
      Cluster: !Ref 'ClusterECS'
      DesiredCount: 2
      SchedulingStrategy: REPLICA
      LaunchType: FARGATE
      EnableECSManagedTags: true
      DeploymentConfiguration:
        MinimumHealthyPercent: 100
        MaximumPercent: 200
        DeploymentCircuitBreaker:
          Enable: false
          Rollback: false
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          Subnets:
            - !Ref PublicSubnetOne
            - !Ref PublicSubnetTwo
          SecurityGroups:
            - !Ref ServicesSG
      LoadBalancers:
        - ContainerName: pouch-image-production
          TargetGroupArn: !Ref 'TargetGroupProduction'
          ContainerPort: 3100

Outputs:
  VpcId:
    Description: The ID of the VPC that this stack is deployed in
    Value: !Ref 'VPC'
  PublicSubnetOne:
    Description: Public subnet one
    Value: !Ref 'PublicSubnetOne'
  PublicSubnetTwo:
    Description: Public subnet two
    Value: !Ref 'PublicSubnetTwo'
  ExternalUrl:
    Description: The url of the external load balancer
    Value: !Sub http://${PublicLoadBalancer.DNSName}

And now I am getting "The target group with targetGroupArn arn:aws:elasticloadbalancing: ... :targetgroup/pouch-Targe-XFJ4AI7HCF6G/f2a665925da27326 does not have an associated load balancer"


Solution

  • From official AWS documentation:

    LoadBalancerName The name of the load balancer to associate with the Amazon ECS service or task set.

    A load balancer name is only specified when using a Classic Load Balancer. If you are using an Application Load Balancer or a Network Load Balancer the load balancer name parameter should be omitted.

    That means that you should not specify the name of the load balancer in the CloudFormation template, since you're using Fargate, and consequently, don't use the classic load balancer.

    Also, in your place, I would consider using something like Former2. It's a great tool that can generate CloudFormation template for you. You can scan your account, choose the services for which you want a template created. In order to use it though, you need an Access key and secret key, so consider creating an IAM user with read-only privilege.

    Edit to cover 2nd error about target group:

    The Amazon ECS service requires an explicit dependency on the Application Load Balancer listener rule and the Application Load Balancer listener. This prevents the service from starting before the listener is ready.

    It's possible that AWS::ECS::Service is trying to attach to the target group before the target group is added to the load balancer. In order to fix that, you should add a dependency in your Service, like this:

    Type: AWS::ECS::Service
    DependsOn: Listener       # Add exact listener name, depending on the service
    Properties:
    

    That should help fix the issue.