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.