HAProxy: routing HTTP requests by domain name

2018-02-20

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 eng.eelcowesemann.nl
  acl goto-app-wordpress hdr(host) -i www.eelcowesemann.nl

  # 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/appservers.map
# hostname              backend
eng.eelcowesemann.nl    app-ghost-eng
zelda.eelcowesemann.nl  app-ghost-zelda
www.eelcowesemann.nl    app-ghost-www
iddqd.nl                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/appservers.map,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/appservers.map): 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.

linuxhaproxy
Creative Commons License

Exiting the vi editor

HAProxy inside Docker: getting the logs