Attaching an ALB to ECS

In the previous article of this terraform CDK series, we have created an ECS task-definition and service to run Nginx server on ECS.

In this article, we are going to attach an ALB to our ECS service.

An ALB (Application Load Balancer) helps in distributing incoming application traffic across multiple targets – in this case multiple ECS tasks. And this increases the availability of your application.

While creating an Application Load Balancer, we need to focus on two major components:

  • Listeners: Before you start using your Application Load Balancer, you must add one or more listeners. A listener is a process that checks for connection requests, using the protocol and port that you configure. The rules that you define for a listener determine how the load balancer routes requests to its registered targets.
  • Target Groups: Each target group is used to route requests to one or more registered targets. When you create each listener rule, you specify a target group and conditions. When a rule condition is met, traffic is forwarded to the corresponding target group. You can create different target groups for different types of requests.

Before starting with the creation of ALB components, First create a security group that is specific to the load balancer (like a firewall to the load balancer).

Import the security group provider:

import { SecurityGroup } from "../.gen/providers/aws/vpc";

Create ALB security group:

We want to specify the inbound and outbound traffic for our ALB. Hence, need to attach a security group to the ALB. Here, we are creating an ALB specific security group to allow traffic from the internet. i. e. from 0.0.0.0/0 cidr block.

const albSecurityGroup = new SecurityGroup(this, "test-nginx-alb-sg", {
      name: "test-nginx-alb-sg",
      description: "Firewall for internet traffic",
      vpcId: vpc-XXXX, // replace vpc id here
      ingress: [
        {
          description: "Internet to ALB",
          fromPort: 80,
          toPort: 80,
          protocol: "tcp",
          cidrBlocks: ["0.0.0.0/0"]
        }
      ],
      egress: [
        {
          fromPort: 0,
          toPort: 0,
          protocol: "-1",
          cidrBlocks: ["0.0.0.0/0"]
        }
      ],
      tags: {
        Name: "Alb Security Group Nginx"
      }
    });

Create ECS Security group:

One important change we need to make is – we need to create a security group for ECS and attach it to the EC2 instance we have created earlier. The security group will be specific for the ECS and allow traffic from the ALB only. It's like only ALB is allowed to access our ECS instances. (refer this to create and attach EC2 instance to the ECS).

As we are using dynamic-port mapping for our ECS containers, the fromPort and toPort attributes from the ingress should provide the range of ports allowed for the host. Our container port is 80 but the host port can be anything between the range 32768 to 65535.

const ecsSecurityGroup = new SecurityGroup(this, "test-nginx-ecs-sg", {
      name: "test-nginx-ecs-sg",
      description: "Firewall for ECS traffic",
      vpcId: vpc-XXXX, // replace vpc id here
      ingress: [
        {
          description: "Traffic to ECS",
          fromPort: 32768,
          toPort: 65535,
          protocol: "tcp",
          securityGroups: [albSecurityGroup.id]
        }
      ],
      egress: [
        {
          fromPort: 0,
          toPort: 0,
          protocol: "-1",
          cidrBlocks: ["0.0.0.0/0"]
        }
      ],
      tags: {
        Name: "ECS Security Group Nginx"
      }
    });
📔
You can add SSH ingress rules if you want to access the EC2 instance through SSH.

Create target group:

We'll be creating a target group for our application load balancer.

Import target group provider:

import { Alb, AlbTargetGroup, AlbListener } from "./.gen/providers/aws/elb";
📔
Note: importing few more resources that we'll be needing.
const targetGroup = new AlbTargetGroup(this, "test-nginx-tg", {
      targetType: "instance",
      vpcId: vpc-XXXX, // replace vpc id here
      name: "test-nginx-tg",
      protocol: "HTTP",
      port: 80,
      protocolVersion: "HTTP1",
      healthCheck: {
        protocol: "HTTP",
        path: "/"
      }
    });
  • targetType: Type of target that you must specify when registering targets with this target group. See doc for supported values. The default is instance.
  • vpcId: (Optional, Forces new resource) Identifier for the VPC in which to create the target group. Required when target_type is instance, ip or alb. Does not apply when target_type is lambda.
  • name: (Optional, Forces new resource) Name of the target group. If omitted, Terraform will assign a random, unique name.
  • protocol: Protocol to use to connect with the target. Defaults to HTTP. Not applicable when target_type is lambda.
  • port: (May be required, Forces new resource) Port on which targets receive traffic, unless overridden when registering a specific target. Required when target_type is instance, ip or alb. Does not apply when target_type is lambda.
  • protocolVersion: (Optional, Forces new resource) Only applicable when protocol is HTTP or HTTPS. The protocol version. Specify GRPC to send requests to targets using gRPC. Specify HTTP2 to send requests to targets using HTTP/2. The default is HTTP1, which sends requests to targets using HTTP/1.1
  • healthCheck: to check target health. Refer this for more health check parameters.

Create application load balancer:

Use Alb resource to create the load balancer.

const alb = new Alb(this, "test-nginx-alb", {
      loadBalancerType: "application",
      name: "test-nginx-alb",
      internal: false,
      ipAddressType: "ipv4",
      subnets: Fn.tolist([subnet-XXXX, subnet-YYYY]), // replace subnets here
      securityGroups: [albSecurityGroup.id]
    });
  • loadBalancerType: (Optional) The type of load balancer to create. Possible values are application, gateway, or network. The default value is application.
  • name: Name of the load balancer.
  • internal: (Optional) If true, the LB will be internal.
  • ipAddressType: (Optional) The type of IP addresses used by the subnets for your load balancer. The possible values are ipv4 and dualstack
  • subnets: (Optional) A list of subnet IDs to attach to the LB. Subnets cannot be updated for Load Balancers of type network. Changing this value for load balancers of type network will force a recreation of the resource.
  • securityGroups:  (Optional) A list of security group IDs to assign to the LB. Only valid for Load Balancers of type application.
📔
Note: Please note that internal LBs can only use ipv4 as the ip_address_type. You can only change to dualstack ip_address_type if the selected subnets are IPv6 enabled.
📔
Note: Please note that one of either subnets or subnet_mapping is required.

Create ALB listener and attach it to the ALB:

We are done with creating target group and the ALB. Let's now create and attach a listener to the ALB. We are attaching a HTTP listener with the target group created.

new AlbListener(this, "http-listener", {
      loadBalancerArn: alb.arn,
      port: 80,
      protocol: "HTTP",
      defaultAction: [
        {
          type: "forward",
          forward: {
              targetGroup: [
                {
                  arn: targetGroup.arn
                }
              ]
          }
        }
      ]
    });

That's it. Deploy the changes using cdktf deploy and Nginx's welcome page will be accessible on ALB dns name. You can find the ALB DNS name in AWS load balancers section.

Kiran Kamalakar

Kiran Kamalakar

Design and development enthusiast.
Pune, India