This is a guest blog post by Felix Candelario & Benjamin F., AWS Solutions Architects.
This blog post will focus on architectures you can unlock with the recently launched Application Load Balancer and compare them with the implementations that use what we now refer to as the Classic Load Balancer. An Application Load Balancer operates at the application layer and makes routing and load-balancing decisions on application traffic using HTTP and HTTPS.
There are several features to help you unlock new workloads:
- Allows you to define rules that route traffic to different target groups based on the path of a URL. The target group typically represents a service in a customer's architecture.
- Container support
- Allows you to monitor and associate health checks per target group.
Service Segmentation Using Subdomains
Our customers often need to break big, monolithic applications into smaller service-oriented architectures while hosting this functionality under the same domain name.
In the example.com architecture shown here, a customer has decided to segment services such as processing orders, serving images, and processing registrations. Each function represents a discrete collection of instances. Each collection of instances host several applications that provide a service.
Using a classic load balancer, the customer has to deploy several load balancers. Each load balancer points to the instances that represent and front the service by using a subdomain.
With the introduction of content-based routing on the new application load balancers, customers can reduce the number of load balancers required to accomplish the segmentation.
Application Load Balancers introduce the concept of rules, targets, and target groups. Rules determine how to route requests. Each rule specifies a target group, a condition, and a priority. An action is taken when the conditions on a rule are matched. Targets are endpoints that can be registered as a member of a target group. Target groups are used to route requests to registered targets as part of the action for a rule. Each target group specifies a protocol and target port. You can define health checks per target group and you can route to multiple target groups from each Application Load Balancer.
A new architecture shown here accomplishes with a single load balancer what previously required three. Here we’ve configured a single Application Load Balancer with three rules.
Let’s walk through the first rule in depth. To configure the Application Load Balancer to route traffic destined to www.example.com/orders/, we must complete five tasks.
- Create the Application Load Balancer.
- Create a target group.
- Register targets with the target group.
- Create a listener with the default rule that forwards requests to the default target group.
- Create a listener that forwards requests to the previously created target group.
To create the Application Load Balancer, we must provide a name for it and a minimum of two subnets.
aws elbv2 create-load-balancer --name example-loadbalancer --subnets "subnet-9de127c4" "subnet-0b1afc20"
To create a target group, we must specify a name, protocol, port, and vpc-id. Based on the preceding figure, we execute the following command to create a target group for the instances that represent the order-processing functionality.
aws elbv2 create-target-group --name order-instances --protocol HTTP --port 80 --vpc vpc-85a268e0
After the target group has been created, we can either add instances manually or through the use of an Auto Scaling group. To add an Auto Scaling group, we use the Auto Scaling group name and the generated target group ARN:
aws autoscaling attach-load-balancer-target-groups --auto-scaling-group-name order_autoscaling_group --target-group-arns "arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/order-instances/f249f89ef5899de1"
If we want to manually add instances, we would supply a list of instances and the generated target group ARN to register the instances associated with the order-processing functionality:
aws elbv2 register-targets --target-group-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/order-instances/f249f89ef5899de1" --targets Id=i-01cb16f914ec4714c,Port=80
After the instances have been registered with the target group, we create a listener with a default rule that forwards requests to the first target group. For the sake of this example, we’ll assume that the orders target group is the default group:
aws elb create-listener --load-balancer-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/order-instances/f249f89ef5899de1" --protocol HTTP --port 80 --default-actions Type=forward,TargetGroupArn="arn:aws:elasticloadbalancing:us-east-1:007038732177:targetgroup/orders-instances/e53f8f9dfaf230c8"
Finally, we create a rule that forwards a request to the target group to which the order instances are registered when the condition of a path-pattern (in this case, '/orders/*') is met:
aws elbv2 create-rule --listener-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:listener/app/example-loadbalancer/6bfa6ad4a2dd7925/6f916335439e2735" --conditions Field=path-pattern,Values='/orders/*' --priority 20 --actions Type=forward,TargetGroupArn="arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/order-instances/f249f89ef5899de1"
We repeat this process (with the exception of creating the default listener) for the images and registration functionality.
With this new architecture, we can move away from segmenting functionality based on subdomains and rely on paths. In this way, we preserve the use of a single subdomain, www, throughout the entire user experience. This approach reduces the number of Elastic Load Balancing load balancers required, which results in costs savings. It also reduces the operational overheard required for monitoring and maintaining additional elements in the application architecture.
Important The move from subdomain segmentation to path segmentation requires you to rewrite code to accommodate the new URLs.
Service Segmentation Using a Proxy Layer
A proxy layer pattern is used when customers want to use a single subdomain, such as www, while still segmenting functionality by grouping back-end servers. The following figure shows a common implementation of this pattern using the popular open source package NGINX.
In this implementation, the subdomain of www.example.com is associated with a top-level external load balancer. This load balancer is configured so that traffic is distributed to a group of instances running NGINX. Each instance running NGINX is configured with rules that direct traffic to one of the three internal load balancers based on the path in the URL.
For example, when a user browses to www.example.com/amazingbrand/, the external Elastic Load Balancing load balancer sends all traffic to the NGINX layer. All three of the NGINX installations are configured in the same way. When one of the NGINX instances receives the request, it parses the URL, matches a location for “/amazing”, and sends traffic to the server represented by the internal load balancer fronting the group of servers providing the Amazing Brand functionality.
It’s important to consider the impact of failed health checks. Should one of the NGINX instances fail health checks generated by the external load balancer, this load balancer will stop sending traffic to that newly marked unhealthy host. In this scenario, all of the discrete groups of functionality would be affected, making troubleshooting and maintenance more complex.
The following figure shows how customers can achieve segmentation while preserving a single subdomain without having to deploy a proxy layer.
In this implementation, both the proxy layer and the internal load balancers can be removed now that we can use the content-based routing associated with the new application load balancers. Using the previously demonstrated rules functionality, we can create three rules that point to different target groups based on different path conditions.
For this implementation, you’ll need to create the application load balancer, create a target group, register targets to the target group, create the listener, and create the rules.
1. Create the application load balancer.
aws elbv2 create-load-balancer --name example2-loadbalancer --subnets "subnet-fc02b18b" "subnet-63029106"
2. Create three target groups.
aws elbv2 create-target-group --name amazing-instances --protocol HTTP --port 80 --vpc vpc-85a268e0
aws elbv2 create-target-group --name stellar-instances --protocol HTTP --port 80 --vpc vpc-85a268e0
aws elbv2 create-target-group --name awesome-instances --protocol HTTP --port 80 --vpc vpc-85a268e0
3. Register targets with each target group.
aws elbv2 register-targets --target-group-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/amazing-instances/ad4a2174e7cc314c" --targets Id=i-072db711f70c36961,Port=80
aws elbv2 register-targets --target-group-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/stellar-instances/ef828b873624ba7a" --targets Id=i-08def6cbea7584481,Port=80
aws elbv2 register-targets --target-group-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/awesome-instances/116b2df4cd7fcc5c" --targets Id=i-0b9dba5b06321e6fe,Port=80
4. Create a listener with the default rule that forwards requests to the default target group.
aws elbv2 create-listener --load-balancer-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:loadbalancer/app/example2-loadbalancer/a685c68b17dfd091" --protocol HTTP --port 80 --default-actions Type=forward,TargetGroupArn="arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/amazing-instances/ad4a2174e7cc314c"
5. Create a listener that forwards requests for each path to each target group. You need to make sure that every priority is unique.
aws elbv2 create-rule --listener-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:listener/app/example2-loadbalancer/a685c68b17dfd091/546af7daf3bd913e" --conditions Field=path-pattern,Values='/amazingbrand/*' --priority 20 --actions Type=forward,TargetGroupArn="arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/amazing-instances/ad4a2174e7cc314c"
aws elbv2 create-rule --listener-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:listener/app/example2-loadbalancer/a685c68b17dfd091/546af7daf3bd913e" --conditions Field=path-pattern,Values='/stellarbrand/*' --priority 40 --actions Type=forward,TargetGroupArn="arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/stellar-instances/ef828b873624ba7a"
aws elbv2 create-rule --listener-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:listener/app/example2-loadbalancer/a685c68b17dfd091/546af7daf3bd913e" --conditions Field=path-pattern,Values='/awesomebrand/*' --priority 60 --actions Type=forward,TargetGroupArn="arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/awesome-instances/116b2df4cd7fcc5c"
This implementation not only saves you the costs associated with running instances that support a proxy layer and an additional layer of load balancers. It also increases robustness as a result of application monitoring. In the Classic Load Balancer implementation of a proxy pattern, the failure of a single instance hosting NGINX impacts all of the other discrete functionality represented by the grouping of instances. In the application load balancer implementation, health checks are now associated with a single target group only. Failures and performance are now segmented from each other.
Run the following command to verify the health of the registered targets in the Amazing Brands target group:
aws elbv2 describe-target-health --target-group-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/amazing-instances/ad4a2174e7cc314c"
If the instances in this target group were marked as unhealthy, you would see the following output:
"Description": "Request timed out"
Service Segmentation Using Containers
Increasingly, customers are using containers as a way to package and isolate applications. Instead of grouping functionality by instances, customers are providing an even more granular collection of computing resources by using containers.
When you use Classic load balancers, you create a fixed relationship between the load balancer port and the container instance port. For example, it is possible to map the load balancer port 80 to the container instance port 3030 and the load balancer port 4040 to the container instance port 4040. However, it is not possible to map the load balancer port 80 to port 3030 on one container instance and port 4040 on another container instance.
The following figure illustrates this limitation. It also points out a pattern of using a proxy container to represent other containers operating on different ports. Logically, this implementation is similar to the proxy segmentation implementation described earlier.
Figure 5 Classic load balancer container based segmentation
Enhanced container support is one of the major features of the Application Load Balancer. It makes it possible to load-balance across multiple ports on the same EC2 instance. The following figure shows how this capability removes the need to run containers that proxy access to other containers.
To integrate containers, you only need to register the targets in the target group, which the Amazon ECS scheduler handles automatically. The following command configures /cart as illustrated in the preceding figure.
aws elbv2 register-targets –-target-group-arn "arn:aws:elasticloadbalancing:us-west-2:007038732177:targetgroup/cart-instances/ad4a2174e7cc314c" –-targets Id=i-84ri3a2c6dcd16b9c,Port=90 Id=i-83fc3a2c6dcd16b9c,Port=90 Id=i-qy342a2c6dcd16b9c,Port=100
A/B testing is a term used for randomized experiments of two separate website experiences to test and gather data that will be helpful in decision-making. To facilitate this type of testing, you need to redirect a percentage of traffic to the secondary stack.
By using Classic Load Balancers, you can conduct these experiments by grouping the different experiences under separate load balancers. By using Amazon Route 53, you can then leverage a group of weighted resource record sets that point to the CNAMEs provided by the Classic Load Balancer. By modifying the weight of a given record, you can then move a random sampling of customers to a different website experience represented by the instances behind the Classic Load Balancer.
The introduction of the application load balancer optimizes A/B testing in a couple of ways. In the following figure, you can see the same grouping of instances that represent the two different website experiences (the A and the B experience shown in the preceding figure). The major differences here are one less load balancer, which reduces costs and configuration, and a new mechanism, rules, to control the switch from the A to the B experience. In this configuration, the logic for redirecting a percentage of traffic must be done at the application level, not the DNS level, by rewriting URLs that point to the B stack instead of the default A stack. The benefits of this approach are that specific users are targeted based on criteria that the application is aware of (random users, geographies, users’ history or preferences). There is also no need to rely on DNS for redirecting some traffic, so the control of who is directed to stack B is much more fine-grained. This mechanism also allows for a more immediate transitioning of users from the A to the B experience because there is no delay associated with DNS records having to be flushed for user cache.
The launch of the application load balancer provides significant optimization in segmentation techniques and A/B testing. These two use cases represent only a subset, but they illustrate how you can leverage the new features associated with this launch. Feel free to leave your feedback in the comments.