Application Load Balancer Example in AWS CDK

avatar

Borislav Hadzhiev

Fri Apr 15 20226 min read

banner

Photo by Atul Vinayak

Updated - Fri Apr 15 2022

Creating an Application Load Balancer in AWS CDK #

In this article, we're going to create an Application Load balancer that routes traffic to an auto scaling group that consists of two EC2 instances.

The process of creating an Application load balancer in CDK, consists of 3 steps:

  1. Create the ALB, by instantiating and configuring the ApplicationLoadBalancer class
  2. Add a listener to the ALB, e.g. listen for HTTP requests on port 80
  3. Add one or more targets to the ALB listener, e.g. an auto scaling group, consisting of multiple EC2 instances
For the example in this article we are going to provision 1 NAT Gateway and two t2.micro EC2 instances. Don't forget to delete the provisioned resources at the end.
The code for this article is available on GitHub

Let's start by looking at the code that creates our VPC, ALB and the listener:

lib/cdk-starter-stack.ts
import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import * as cdk from 'aws-cdk-lib/core'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); const vpc = new ec2.Vpc(this, 'vpc', {natGateways: 1}); const alb = new elbv2.ApplicationLoadBalancer(this, 'alb', { vpc, internetFacing: true, }); const listener = alb.addListener('Listener', { port: 80, open: true, }); } }

Let's go over what we did in the code snippet.

  1. We created a VPC with a single NAT Gateway. By default, the VPC construct creates a PUBLIC and a PRIVATE subnet groups with subnets in 3 availability zones.

    Our Application Load Balancer will be provisioned in PUBLIC subnets, whereas our EC2 instances will be provisioned in PRIVATE subnets.

  2. We created an an Application Load balancer. By setting the internetFacing prop to true, we allocate a public IPv4 address to our load balancer. The value of internetFacing is set to false by default.

  3. We added a listener to our ALB. We passed the following props to the listener:

NameDescription
portthe port on which the listener awaits requests. Now that we've set the port to 80, CDK will automatically add a rule to the ALB's security group to open inbound traffic on port 80 from the world
openwhether everyone on the internet should be able to reach our application load balancer. The default value is true.
The code for this article is available on GitHub

Next, let's add an auto scaling group and attach it as a target on the listener of the ALB:

lib/cdk-starter-stack.ts
import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import * as cdk from 'aws-cdk-lib/core'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ... rest // 👇 create user data script const userData = ec2.UserData.forLinux(); userData.addCommands( 'sudo su', 'yum install -y httpd', 'systemctl start httpd', 'systemctl enable httpd', 'echo "<h1>Hello World from $(hostname -f)</h1>" > /var/www/html/index.html', ); // 👇 create auto scaling group const asg = new autoscaling.AutoScalingGroup(this, 'asg', { vpc, instanceType: ec2.InstanceType.of( ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO, ), machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, }), userData, minCapacity: 2, maxCapacity: 3, }); // 👇 add target to the ALB listener listener.addTargets('default-target', { port: 80, targets: [asg], healthCheck: { path: '/', unhealthyThresholdCount: 2, healthyThresholdCount: 5, interval: cdk.Duration.seconds(30), }, }); // 👇 add an action to the ALB listener listener.addAction('/static', { priority: 5, conditions: [elbv2.ListenerCondition.pathPatterns(['/static'])], action: elbv2.ListenerAction.fixedResponse(200, { contentType: 'text/html', messageBody: '<h1>Static ALB Response</h1>', }), }); // 👇 add scaling policy for the Auto Scaling Group asg.scaleOnRequestCount('requests-per-minute', { targetRequestsPerMinute: 60, }); // 👇 add scaling policy for the Auto Scaling Group asg.scaleOnCpuUtilization('cpu-util-scaling', { targetUtilizationPercent: 75, }); // 👇 add the ALB DNS as an Output new cdk.CfnOutput(this, 'albDNS', { value: alb.loadBalancerDnsName, }); } }

Let's go over what we did in the code snippet.

  1. We created the user data script that initializes our EC2 instances. We basically installed and started apache and wrote a hello world message that prints the host's IP address to the root route.

  2. We created an auto scaling group. We passed the following props to the constructor:

NameDescription
vpcthe VPC, in which the EC2 instances should be launched. By default the instances will be launched in PRIVATE subnets in the specified VPC.
instanceTypethe type of EC2 instances the auto scaling group should consist of, in our case t2.micro.
machineImagethe Amazon Machine image for the EC2 instances, in our case AMAZON LINUX 2.
userDatathe initialization script that will run on the EC2 instances
minCapacitythe minimum number of running instances in our auto scaling group
maxCapacitythe maximum number of running instances in the auto scaling group
  1. We added a target to the listener of our Application Load balancer. The listener will route requests that come on port 80 to the EC2 instances in our auto scaling group.

    Note that we've configured a healthCheck on the / path. This is the default configuration for an ALB health check in CDK:

    • the requests are made on the / route
    • 2 consecutive request failures deem a target unhealthy
    • 5 consecutive request successes deem an unhealthy target healthy
    • health check requests are issued at an interval of 30 seconds
  2. We added an action to the listener of the ALB. The action is only ran if the condition matches, in our case - if the path of the request is /static, the action returns a static html response with a 200 status code.

  3. We added 2 scaling policies to our auto scaling group:

  • the scaleOnRequestCount method scales the number of provisioned EC2 instances up or down, depending on how many HTTP requests we want an EC2 instance to take per minute

  • the scaleOnCpuUtilization method scales up or down, depending on the average CPU utilization of all EC2 instances in the target group.

  1. We added an Output, which we will redirect to a file on the local file system to get the DNS name of the Application Load balancer.

Let's run the deployment command:

shell
npx aws-cdk deploy \ --outputs-file ./cdk-outputs.json

You can get the DNS name of the ALB from the cdk-outputs.json file or look it up in the EC2 management console.

If you paste it in your browser you will see that the ALB alternates routing traffic to both of our EC2 instances:

alb routing instance 1

If we visit the /static route, for which we configured an ALB fixed response, we can see that it also works:

alb action

CDK has configured the following inbound rules for the security group of our ALB:

TypeProtocolPortSource
HTTPTCP800.0.0.0/0

Everyone on the internet can access our ALB on port 80.

The outbound rules for the security group of our ALB, only allow traffic to the security group of our auto scaling group on port 80:

TypeProtocolPortDestination
HTTPTCP80ASG-SecGrp-ID

The inbound rules for the security group of the Auto Scaling Group allow HTTP traffic on port 80 from the security group of the Application Load Balancer:

TypeProtocolPortSource
HTTPTCP80ALB-SecGrp-ID

The outbound rules for the security group of the Auto Scaling Group allow all traffic:

TypeProtocolPortDestination
All TrafficAllAll0.0.0.0/0

As expected, our Application Load balancer has been deployed to 3 PUBLIC subnets and our EC2 instances to 2 PRIVATE subnets.

The listeners of our ALB have 2 rules:

  • if the path is /static return a fixed HTML response
  • else forward the requests to the registered target group

alb listener rules

If we look at the target group of our Application Load balancer, we can see that both of our EC2 instances are healthy and able to serve requests:

alb target group

Don't forget to delete any resources you have provisioned, to avoid incurring charges.

Clean up #

To delete the provisioned resources, issue the destroy command:

shell
npx aws-cdk destroy

Further Reading #

Use the search field on my Home Page to filter through my more than 1,000 articles.