Last updated: Jan 26, 2024
Reading timeยท6 min
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:
t2.micro
EC2 instances. Don't forget to delete the provisioned resources at the end.Let's start by looking at the code that creates our VPC, ALB and listener:
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 sample.
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.
We created 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.
We added a listener to our ALB. We passed the following props to the listener:
Name | Description |
---|---|
port | the 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 |
open | whether everyone on the internet should be able to reach our application load balancer. The default value is true . |
Next, let's add an auto-scaling group and attach it as a target on the listener of the Application Load Balancer:
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 sample.
We created the user data script that initializes our EC2 instances. We basically installed and started an Apache web server and wrote a "hello world" message that prints the host's IP address to the root route.
We created an auto-scaling group. We passed the following props to the constructor:
Name | Description |
---|---|
vpc | the VPC, in which the EC2 instances should be launched. By default the instances will be launched in PRIVATE subnets in the specified VPC. |
instanceType | the type of EC2 instances the auto-scaling group should consist of, in our case t2.micro . |
machineImage | the Amazon Machine image for the EC2 instances, in our case AMAZON LINUX 2 . |
userData | the initialization script that will run on the EC2 instances |
minCapacity | the minimum number of running instances in our auto-scaling group |
maxCapacity | the maximum number of running instances in the auto-scaling group |
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:
/
routeWe added an action to the listener of the ALB. The action is only run 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.
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.
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:
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:
If we visit the /static
route, for which we configured an ALB fixed response,
we can see that it also works:
CDK has configured the following inbound rules for the security group of our ALB:
Type | Protocol | Port | Source |
---|---|---|---|
HTTP | TCP | 80 | 0.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:
Type | Protocol | Port | Destination |
---|---|---|---|
HTTP | TCP | 80 | ASG-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:
Type | Protocol | Port | Source |
---|---|---|---|
HTTP | TCP | 80 | ALB-SecGrp-ID |
The outbound rules for the security group of the Auto Scaling Group allow all traffic:
Type | Protocol | Port | Destination |
---|---|---|---|
All Traffic | All | All | 0.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 Application Load Balancer have 2 rules:
/static
return a fixed HTML responseIf 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:
To delete the provisioned resources, issue the destroy
command:
npx aws-cdk destroy
You can learn more about the related topics by checking out the following tutorials: