Sharing Our Passion for Technology
& continuous learning
〈  Back to Blog

Node Reference - Application Load Balancer

Teammates sitting together and looking at code

Prerequisites

This article builds on the prior article about AWS Fargate.

Add an Application Load Balancer

For a long time, AWS has supported an HTTP(S) load balancing service in the form of an Elastic Load Balancer (now called a “Classic load balancer”). When paired with ECS, a classic load balancer suffers from a limitation where every node in the cluster it is balancing must be listening on the same port (the port is configured at the cluster level, not the node level). Because of this, a host port had to be mapped to the container port for each running instance. This prevented two containers from sharing a host if they were both mapped to the same host port. Fargate does not allow us to map host ports at all (because we don’t control the host). So, we have to use a newer load balancer product called an Application Load Balancer.

An Application Load Balancer (or ALB) consists of three pieces:

  1. The Load Balancer represents an IP address and associated domain name that can receive traffic.
  2. One or more Listener elements configure the ports and protocols the Load Balancer will receive traffic on. We will ultimately create two of these (one for HTTP on port 80 and one for HTTPS on port 443).
  3. Finally, a TargetGroup represents a pool of back end services that can be load balanced across.

It is worth mentioning that we do not directly configure the TargetGroup to reference our service. Instead, we point the service at the TargetGroup. The service is responsible for allocating the cluster instances and then associating their IP addresses into the Load Balancer.

Add a Load Balancer to your app, by adding the following CloudFormation resources to your cloudformation.template.yaml:

LoadBalancerSG: #This security group allows us to whitelist traffic from the internet to our load balancer
  Type: "AWS::EC2::SecurityGroup"
  Properties:
    GroupDescription: "Load balancer security group"
    VpcId: !Ref VpcId      
    SecurityGroupIngress:
      - IpProtocol: "tcp"
        CidrIp: "0.0.0.0/0"
        FromPort: 80
        ToPort: 80
      - IpProtocol: "tcp"
        CidrIp: "0.0.0.0/0"
        FromPort: 443
        ToPort: 443
AppSG: #This security group holds the application
  Type: "AWS::EC2::SecurityGroup"
  Properties:
    GroupDescription: "Application security group"
    VpcId: !Ref VpcId 
    SecurityGroupIngress:
      - IpProtocol: "tcp"
        SourceSecurityGroupId: !Ref LoadBalancerSG
        FromPort: 1
        ToPort: 65000
LoadBalancer:
  Type: AWS::ElasticLoadBalancingV2::LoadBalancer
  Properties:
    Scheme: internet-facing
    Subnets: !Ref SubnetIds
    SecurityGroups:
      - !Ref LoadBalancerSG
LBListener:
  Type: AWS::ElasticLoadBalancingV2::Listener
  DependsOn:
    - LoadBalancer
    - TargetGroup
  Properties:
    DefaultActions:
    - Type: forward
      TargetGroupArn: !Ref TargetGroup
    LoadBalancerArn: !Ref LoadBalancer
    Port: 80
    Protocol: HTTP
TargetGroup:
  Type: AWS::ElasticLoadBalancingV2::TargetGroup
  DependsOn: 
    - LoadBalancer
  Properties:
    TargetType: ip #the default is "instance" but we must use ip to forward to fargate
    VpcId: !Ref VpcId
    Protocol: HTTP # do not change to HTTPS
    Port: 3000
    HealthCheckPath: /hello
    HealthCheckIntervalSeconds: 10
    HealthCheckTimeoutSeconds: 5
    HealthyThresholdCount: 2
    UnhealthyThresholdCount: 2
    TargetGroupAttributes:
      - Key:  deregistration_delay.timeout_seconds
        Value:  30

You will also need to uncomment these lines from the Service definition:

  LoadBalancers: 
    - ContainerName: ProductService
      ContainerPort: 3000
      TargetGroupArn: !Ref TargetGroup

and

  AwsvpcConfiguration:
    AssignPublicIp: ENABLED
    Subnets: !Ref SubnetIds
    SecurityGroups:
      - !Ref AppSG

Once deployed, AWS will assign a hostname to your load balancer that can be obtained by looking at the AWS Management Console as shown below:

Or by adding an “Outputs” section to your cloudformation.template.yml:

Outputs:
  LoadBalancerDNS:
    Value: !GetAtt LoadBalancer.DNSName

Once you specify this output, you can grab the value by running this AWS CLI command:

aws cloudformation describe-stacks --stack-name ProductService-DEV --query 'Stacks[0].Outputs[?(OutputKey==`LoadBalancerDNS`) ].OutputValue' --output text

You should be able to make an http:// request to the above domain name and get back our hello message.

dev_domain=$(aws cloudformation describe-stacks --stack-name ProductService-DEV --query 'Stacks[0].Outputs[?(OutputKey==`LoadBalancerDNS`) ].OutputValue' --output text)
curl http://$dev_domain/hello/

prod_domain=$(aws cloudformation describe-stacks --stack-name ProductService-PROD --query 'Stacks[0].Outputs[?(OutputKey==`LoadBalancerDNS`) ].OutputValue' --output text)
curl http://$prod_domain/hello/

See the changes we made here.

Table of Contents

If you have questions or feedback on this series, contact the authors at nodereference@sourceallies.com.

〈  Back to Blog