Terraform and AWS Application Load Balancers

First off an apology, sorry it has been a whole year since I wrote my last article. I have been a little busy since then, becoming a new dad and a promotion at work meant blogging about my adventures in AWS took a bit of a backseat… Now the very british apologies are out of the way i’ll jump straight in.

The last article outlined the basics of Application Load Balancers (ALB) and how you can use these to leverage a decent saving — this saving was significantly increased earlier this year when AWS increase the number of listeners per ALB from 10 to a whopping 50!

I'll start out with the architecture diagram of an ALB.

AWS ALB Architecture

As you can see there are a quite few more moving parts when compared with its predecessor the ELB. Let’s look in more detail what each part is and it’s function while throwing out some nice and handy terraform code snippets. The code below is to be used as an example.

AWS ALB — This is the top level component in the architecture the ALB handles the incoming traffic, offloads SSL and balances the load — duh…

resource "aws_alb" "alb" {  
name = "${var.alb_name}"
subnets = ["${split(",",var.alb_subnets)}"]
security_groups = ["${split(",", var.alb_security_groups)}"]
internal = "${var.internal_alb}"
idle_timeout = "${var.idle_timeout}"
tags {
Name = "${var.alb_name}"
access_logs {
bucket = "${var.s3_bucket}"
prefix = "ELB-logs"

ALB Listener. Listeners are assigned a specific port to keep an ear out for incoming traffic they are you can have a maximum of 50 listeners assigned to each load balancer.

resource "aws_alb_listener" "alb_listener" {  
load_balancer_arn = "${aws_alb.alb.arn}"
port = "${var.alb_listener_port}"
protocol = "${var.alb_listener_protocol}"

default_action {
target_group_arn = "${aws_alb_target_group.alb_target.arn}"
type = "forward"

ALB listener rules — this is where things get pretty nifty! Each listener can have many rules which means we can route traffic to different places based on two conditions; the path and/or the host.

resource "aws_alb_listener_rule" "listener_rule" {
depends_on = ["aws_alb_target_group.alb_target_group"]
listener_arn = "${aws_alb_listener.alb_listener.arn}"
priority = "${var.priority}"
action {
type = "forward"
target_group_arn = "${aws_alb_target_group.alb_target_group.id}"
condition {
field = "path-pattern"
values = ["${var.alb_path}"]

Now the listener has some rules we can forward these onto target groups. Target groups are essentially the end point of the ALB architecture — When the listener rule matches a pattern for a request it gets forwarded to the correlating target group. The cool thing about target groups is they have a health check that can directly check the health of a path, for example I have an instance that runs 2 tomcat servers foo and bar. I am able to check the health of foo and bar independently, even though they are on the same instance. Nice!

resource "aws_alb_target_group" "alb_target_group" {  
name = "${var.target_group_name}"
port = "${var.svc_port}"
protocol = "HTTP"
vpc_id = "${var.vpc_id}"
tags {
name = "${var.target_group_name}"
stickiness {
type = "lb_cookie"
cookie_duration = 1800
enabled = "${var.target_group_sticky}"
health_check {
healthy_threshold = 3
unhealthy_threshold = 10
timeout = 5
interval = 10
path = "${var.target_group_path}"
port = "${var.target_group_port}"

Now we have a target group we need to assign something to it. This is done through target group attachments. Two methods here — 1. Autoscaling Group attachment, for all you Chaos Monkey fans, 2. Instance attachment, for those crazy people who are not using ASG’s. But don’t worry I’ll still show you code for both!

#Autoscaling Attachment
resource "aws_autoscaling_attachment" "svc_asg_external2" {
alb_target_group_arn = "${aws_alb_target_group.alb_target_group.arn}"
autoscaling_group_name = "${aws_autoscaling_group.svc_asg.id}"
#Instance Attachment
resource "aws_alb_target_group_attachment" "svc_physical_external" {
target_group_arn = "${aws_alb_target_group.alb_target_group.arn}"
target_id = "${aws_instance.svc.id}"
port = 8080

That is pretty much it go forth and make yourself some Application Load Balancers using Hashicorp Terraform.


There are a few gotchas that had me going in circles for a while…

  1. Health checks — Because the health checks act independently if you are using an ASG inside a Target Group configuring them differently can make it difficult to track down where an issue lies.
  2. Security Groups — Make sure the ALB has rights to call the application in the target group, otherwise it will always be unhealthy.
  3. Internal and external — An ALB can have the internal property like the ELB. If you have a service that needs both an internal and external endpoint you will need to add 2 target groups to 2 listener rules on 2 different ALBs.
  4. Dependencies — I have had to add a few depends_on statements otherwise Terraform got its knickers in a twist and started shouting about cycling… as if getting on a bike is going to help here!