skip to Main Content

I have multiple Docker containers that I’d like to put behind a single Application Load Balancer in AWS. The containers run in EC2 ECS, not Fargate, for cost. I’m able to do this in CloudFormation, but so far I can’t figure out the secret incantation to have CDK do what I’m trying to do.

Each service has a Target Group and a Listener Rule, the Rule looks for a specific sub-domain to do the routing appropriately.

Here’s what works in CloudFormation:

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      TargetType: "instance"
      VpcId: !Ref VpcId
      Port: !Ref DashboardContainerPort
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: "10"
      Matcher:
        HttpCode: 200-499
      Name: !Ref DashboardServiceName
      HealthCheckPath: /
      HealthCheckProtocol: HTTP
      HealthCheckIntervalSeconds: 130
      HealthCheckTimeoutSeconds: 120
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3

  Service:
    Type: AWS::ECS::Service
    DependsOn: HttpsListenerRule
    Properties:
      ServiceName: !Ref DashboardServiceName
      Cluster: !Ref Cluster
      DesiredCount: !Ref DesiredCount
      TaskDefinition: !Ref TaskDefinition
      LaunchType: "EC2"
      LoadBalancers:
        - ContainerName: !Ref DashboardServiceName
          ContainerPort: !Ref DashboardContainerPort
          TargetGroupArn: !Ref TargetGroup
      Tags:
        - Key: Service
          Value: !Ref DashboardServiceName

The Target Group is mapped to a Listener on the ALB with a Listener rule.

Here’s what’s not working in CDK:

service = ecs.Ec2Service(
    self,
    f"ECS-Service-{dash_name}",
    cluster=cluster,
    task_definition=task_definition,
)
self._add_autoscaling(service)


service.connections.allow_from(load_balancer, ec2.Port.tcp(3838))
load_balancer.connections.allow_to(service, ec2.Port.tcp(3838))

target_group = elbv2.ApplicationTargetGroup(
    self,
    f"{dash_name}-targetgroup",
    port=3838,
    protocol=elbv2.ApplicationProtocol.HTTP,
    vpc=cluster.vpc,
)


listener443.add_target_groups(
    f"{dash_name}-listener-rule",
    target_groups=[target_group],
    conditions=[
        elbv2.ListenerCondition.host_headers(
            [f"{dash_name}.dashboards.targetsmart.com"]
        ),
    ],
    priority=x,
)

service.attach_to_application_target_group(target_group)

The CDK documentation says not to use service.attach_to_application_target_group, so, ok, but how can I cajole CDK to do what I’m trying to get it to do?

Right now, I can see the tasks register themselves as targets in the Target Group, but no matter what I do in the Console with the Security Group or the Health Checks, the Tasks fail the health check and are terminated in a loop till the entire deployment fails. I’ve downloaded and run the containers locally and they seem fine.

I feel like I’m very close to finishing this project. If I can work out this last bit, I think I’ll have it. What’s the right way to do this? Any pointers in the right direction are appreciated.

2

Answers


  1. Chosen as BEST ANSWER

    You know, every time I think "oh it can't be the security group", it's the security group. In the code above I was attempting to add a security group to the service, which is wrong for EC2 ECS clusters. I needed to add the security group to the autoscaling group.

            auto_scaling_group.connections.allow_from(load_balancer, ec2.Port.tcp_range(1,65535))
    
    

    Also, my application takes a while to start, so I also needed to update the load balancer's health checks:

                target_group = elbv2.ApplicationTargetGroup(self,
                    f"{dash_name}-targetgroup",
                    port=3838,
                    protocol=elbv2.ApplicationProtocol.HTTP,
                    vpc=cluster.vpc,
                    health_check=elbv2.HealthCheck(
                        healthy_http_codes="200-499",
                        unhealthy_threshold_count=5,
                        timeout=duration.seconds(120),
                        interval=duration.seconds(130),
                    ),
                )
    

    I hope this helps if anyone else has the same problem down the road. Or, just as likely, if I have the same problem down the road.


  2. Here’s how I did it on my project.

    listener.add_targets(
        'ContainerTarget',
        target_group_name='MyEscServiceTargetGroupName',
        targets=[
            service.load_balancer_target(
                container_name='container_name',
                container_port='container_port',
                protocol=ecs.Protocol.TCP
            )
        ],
        port=port,
        protocol=elbv2.ApplicationProtocol.HTTP,
        health_check=elbv2.HealthCheck(...),
        conditions=[elbv2.ListenerCondition...],
        priority=priority
    )
    

    In your case, you can try this:

    target_group = elbv2.ApplicationTargetGroup(
        ...
        targets=[
            service.load_balancer_target(
                container_name='container_name',
                container_port='container_port',
                protocol=ecs.Protocol.TCP
            )
        ]
        ...
    )
    
    listener443.add_target_groups(
        f"{dash_name}-listener-rule",
        target_groups=[target_group],
        conditions=[
            elbv2.ListenerCondition.host_headers(
                [f"{dash_name}.dashboards.targetsmart.com"]
            ),
        ],
        priority=x,
    )
    

    service.load_balancer_target return an IEcsLoadBalancerTarget which is an IApplicationLoadBalancerTarget

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search