Recently, I have been looking into HAProxy as an alternative load balancer to NGINX. NGINX’ free version lacks features vital to a multi-container environment. The most important features we need are upstream health checks and sticky sessions.

So far, least_conn or ip_hash have worked well enough, but cracks are beginning to show. Two reasons forced the cracks to show: * Bad session handlers built into applications, forcing us away from least_conn to ip_hash * Heavy use from one IP address killing one container, and leaving others idle.

Being frugal is a good thing, so I started playing with HAProxy, discovering its features.

HAProxy supports the features mentioned above, but getting up and running took some experimenting. The docs for HAProxy are vast, and I felt overwhelmed. Finally, I figured out routing HTTP requests based on domain names, is possible in several ways, using ACLs.

Direct ACL in haproxy.cfg

First, you define an ACL with acl <name-of-acl> hdr(host) -i <fqdn>. The -i flag, tells hdr(host) to perform a case-insensitive check on hdr(host). Then, you can route the requests to their backends, using the use_backend directive:

use_backend <backend-name> if <name-of-acl>

Here is a cleaned up snippet:

frontend fe-http-incoming
  bind :80

  # Decide which ACL to set by reading host header
  acl goto-app-ghost     hdr(host) -i
  acl goto-app-wordpress hdr(host) -i

  # Use wanted backend, based on ALC
  use_backend app-ghost     if goto-app-ghost
  use_backend app-wordpress if goto-app-wordpress

Mapping file

Another way to route requests is to use a mapping file. In this file, you have two columns like so:

cat /etc/haproxy/
# hostname              backend    app-ghost-eng  app-ghost-zelda    app-ghost-www                static-iddqd

You can then tell HAProxy, you use this file to decide which backend to use based on the Host header.

frontend fe-http-incoming
  mode         http
  bind         :80
  use_backend  %[req.hdr(host),lower,map_dom(/etc/haproxy/,app-ghost-eng)]

The use_backend line explained: req.hdr(host): use the Host request header lower: translate the Host header to lowercase map_dom(/etc/haproxy/ the path to the mapping file app-ghost-eng: the default backend to use if none provided.

Which method to use

You can choose whichever method you want, and whether you use a configuration management tool like Puppet. It is easier to generate a separate mapping file, and to point haproxy.cfg towards that. When not using configuration management, I would choose use the mapping file when the amount of host names you want to route exceeds ten entries, this way the main configuration file stays nice and clean.