Application Load Balancer Example in AWS CDK

avatar

Borislav Hadzhiev

Tue May 11 20216 min read

banner

Photo by Atul Vinayak

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 twot2.microEC2 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/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import * as cdk from '@aws-cdk/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've 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 ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import * as cdk from '@aws-cdk/core'; import * as autoscaling from '@aws-cdk/aws-autoscaling' 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.T2, 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 executed at an interval of 30 seconds
  2. we've added an action to the listener of the ALB. The action is only executed 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 execute a deployment:

shell
npx 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, execute the destroy command:

shell
npx cdk destroy

Further Reading #

Add me on LinkedIn

I'm a Web Developer with TypeScript, React.js, Node.js and AWS experience.

Let's connect on LinkedIn

Join my newsletter

I'll send you 1 email a week with links to all of the articles I've written that week

Buy Me A Coffee