Auto Scaling and ALB in AWS

A solution to handle host based load balancing with ALBs in AWS with Terraform.

I recently spotted a post about a migration from Classic load balancers (ELBs) to Application load balancers (ALB) in AWS. The author was primarily looking to save some money as each ELB can cost quite a bit per month. One ALB can take care of the job of more than one ELB thanks to how its listeners work so the cost can be at least halved.

The article (How to cut AWS ELB costs by 90% using Application Load Balancers.) mainly talked about using path based routing to differentiate between the different targets of the ALB. Yet, thanks to some more digging into Terraform documentation on AWS Load Balancer Listener rules I found out that there is also a host based routing option.

Before going further I would like to point out that a “Classic” setup with ELB + ASG is very very simple to setup while the ALB one … is a bit more complex since it involves more resources (ALB + Listener + Listener Rule + Target Group + ASG). I don’t really like the idea of this much complexity for now but the added bonus of such a setup is that a Target Group is more flexible for what belongs to it than the ASG. The added proxy (target group) thus allows to possibly use something else than an ASG to serve what ever is handling the connection.

Planning ahead

ALB comes with a few of different resources to setup in terraform :

Since it’s multiple resources it’s good to have a look at their documentation, I found that some pages were more useful than others to get a clue at how they all fit together.

To sum up here is what we will need :

  • one aws_lb_target_group per ASG or ASG pairs (blue/green)
  • one aws_lb
  • one (default) aws_lb_listener pointing to what will be the default aws_lb_target_group
  • one aws_lb_listener_rule per host or path to route to
  • one aws_lb_listener_certificate per SSL certificate in addition to the default one

Implementing

The implementation is actually pretty straight forward if you already have an ELB and an Auto Scaling Group setup. We will consider that there is already a Launch Template too.

Load balancer and first listener

Let's start with the Load Balancer and the first listener. The load balancer is fairly simple : a name, a boolean for the internal attribute, the type, the usual duo of security groups and subnets and finally a little protection flag.

Then comes the initial listener, the default one. You can plug it to a target group but here we just decided to make it a fixed response content, a kind of dummy placeholder. We will define explicitly one additional listener rule per application later on.

resource "aws_lb" "imfiny_frontend" {
  name               = "imfiny-frontend"
  internal           = false
  load_balancer_type = "application"
  security_groups    = ["${aws_security_group.public-elb.id}"]
  subnets            = [
    "${aws_subnet.pub-subnets.0.id}",
    "${aws_subnet.pub-subnets.1.id}",
    "${aws_subnet.pub-subnets.2.id}"
  ]
  enable_deletion_protection = true
}

resource "aws_lb_listener" "imfiny_frontend" {
  load_balancer_arn = "${aws_lb.imfiny_frontend.arn}"
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = "${var.ssl_certs["imfiny_com"]}"

  default_action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/plain"
      message_body = "Fixed response content"
      status_code  = "200"
    }
  }
}

Then we can go onto the part for one service.

resource "aws_lb_target_group" "hello_world" {
  name     = "${format("%s-%s-alb", local.product_name, "hello-world")}"
  port     = 8080
  protocol = "HTTP"
  vpc_id   = "${aws_vpc.app_vpc.id}"
}

resource "aws_lb_listener_rule" "hello_world_routing" {
  listener_arn = "${aws_lb_listener.imfiny_frontend.arn}"
  priority     = 97

  action {
    type             = "forward"
    target_group_arn = "${aws_lb_target_group.hello_world.arn}"
  }

  condition {
    field  = "host-header"
    values = ["hw.example.com"]
  }
}

resource "aws_autoscaling_group" "hello-world-blue" {
  vpc_zone_identifier = [
    "${aws_subnet.priv-subnets.0.id}",
    "${aws_subnet.priv-subnets.1.id}",
    "${aws_subnet.priv-subnets.2.id}"
  ]

  name = "hello-world-blue"
  max_size = 2
  min_size = 0
  desired_capacity = 1
  health_check_grace_period = 300
  health_check_type = "ELB"
  force_delete = true
  target_group_arns = ["${aws_lb_target_group.hello_world.arn}"]

  launch_template = {
    id      = "${aws_launch_template.hello-world-blue.id}"
    version = "$$Latest"
  }
}

resource "aws_lb_listener_certificate" "hello_world" {
  listener_arn    = "${aws_lb_listener.imfiny_frontend.arn}"
  certificate_arn = "${var.ssl_certs["imfiny_2"]}"
}

resource "aws_route53_record" "hello_world" {
  zone_id = "${var.route53_zone_ids["imfiny"]}"
  name    = "hw"
  type    = "A"

  alias {
    name                   = "${aws_lb.imfiny_frontend.dns_name}"
    zone_id                = "${aws_lb.imfiny_frontend.zone_id}"
    evaluate_target_health = true
  }
}

This is one defines :

  • the target group : self explanatory : defined the port the instances will listen on, the protocol and the vpc it will exist in
  • the listener rule : this one makes the link between the load balancer and the target group through the listener (defined in the previous part) with an action and a condition. That’s where the magic happens ! Note the host-header condition and the forward action
  • the auto scaling group : to link the EC2 instances with the target group so that the load balancer can find the running instances of the service
  • a listener certificate : if you are serving SSL/TLS you will need one listener certificate per rule, depending on your certificates and domains that is
  • a route53 record : that’s for nicety to make it easy to connect to the load balancer

And that’s it. As pointed out earlier it’s more complex than the classic load balancer with an auto scaling group but it gives you more flexibility as for targets and traffic routing.

Extending with another service through the same ALB

If you want to add more services to go through the ALB you need to repeat the second part of the previous section actually.

Monitoring

As for metrics in Cloudwatch most of them are split between Target Groups so you can easily create your own dashboards per Target Group : CloudWatch Metrics for Your Application Load Balancer - Elastic Load Balancing.

Conclusion

As to conclude we can say it was more straight forward than anticipated and works quite well. Several articles point out that ALBs are not a silver bullet so that you might want to dig deeper before choosing one. At least now you know it’s simple to setup through terraform.

Again this points out the greatness of using Terraform to handle AWS resources : you can easily prepare and maintain services' resources with a versioned code base. It's clean, easy to read, maintain and extend.

Interested in this architecture or something similar ? I am happy to tell you more about this and help you. I am based in France, and can consult remotely and on site. Contact me : thomas@imfiny.com.

Subscribe to Imfiny

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe