Dynamic HAProxy backends

Bill Lumbergh - Office Space

HAProxy is one of very few pieces of software that are truly a joy to use. It’s written in C, it’s reliable, fast, secure and versatile.

I’ve been using it for a number of years now for all kinds of purposes and its ability to inspect and manipulate HTTP requests is one of the features I use most often.

In this tutorial, I won’t explain how to install and configure HAProxy, but rather how to use HAProxy for load balancing or simply redirecting HTTP traffic to desired backends by matching specific cookies or request headers. The cherry on the top will be dynamic HAProxy backends defined in map files, for easier automation and cleaner configuration file.

For this scenario, lets assume that we have an frontend named fe_main and a couple of backends - be_alpha, be_bravo, be_charlie and be_delta as default backend.

Our fe_main frontend handles a bunch of RESTful API requests and based on X-API request header value, we’ll let HAProxy decide where to pass those requests. If the request doesn’t have such header, it’ll be forwarded to default HAProxy backend (be_delta).

To keep haproxy.cfg configuration file as clean as possible, we’ll map X-API values with backends in separate, map file. Thanks to HAProxy’s powerful, but still easy to read configuration language, we can achieve all of the above in just a few lines.

Example of /etc/haproxy/haproxy.cfg

frontend fe_main
  bind 192.168.1.100:80
  mode http
 
  # check the presence of X-API request header
  acl xapi_header req.hdr(X-API) -m found
 
  # if X-API header exists, check the backend to which the request needs to be forwarded
  # use be_delta if there's no match
  use_backend %[req.hdr(X-API),lower,map(/etc/haproxy/api.map,be_delta)] if xapi_header
 
  # if X-API header doesn't exist use default backend
  default_backend be_delta
 
# backends
backend be_alpha
  server alpha_server 192.168.1.200:80
 
backend be_bravo
  server bravo_server 192.168.1.201:80
 
backend be_charlie
  server charlie_server 192.168.1.202:80
 
backend be_delta
  server delta_server 192.168.1.203:80

The /etc/haproxy/api.map map file looks very simple. It contains space-delimited key-value pairs. The value of X-API header is key and the HAProxy backend which should handle the request is the value.

Example of /etc/haproxy/api.map

# X-API value | HAProxy backend
v1.0 be_alpha
v1.1 be_bravo
v2.5 be_charlie
v2.2 be_alpha
v1.1 be_delta

So, if the request has X-API: v1.1 header, that request will be forwarded to be_bravo backend. The request with X-API: v2.2 will end up at be_alpha backend and so on. If there’s no match in the map file, be_delta backend will be used.

If we wanted to inspect an cookie, instead of request header, pretty much the whole configuration remains the same:

frontend fe_main
  bind 192.168.1.100:80
  mode http
 
  # check the presence of APIcookie request header
  acl api_cookie hdr_sub(cookie) -i APIcookie -m found
 
  # if APIcookie cookie exists, check which backend should handle the request
  # use be_delta if there's no match
  use_backend %[req.cook(APIcookie),lower,map(/etc/haproxy/api.map,be_delta)] if api_cookie
 
  # if APIcookie doesn't exist, use default backend
  default_backend be_delta
 
# backends
backend be_alpha
  server alpha_server 192.168.1.200:80
 
backend be_bravo
  server bravo_server 192.168.1.201:80
 
backend be_charlie
  server charlie_server 192.168.1.202:80
 
backend be_delta
  server delta_server 192.168.1.203:80

Pretty simple, right? Just make sure that you restart/reload HAProxy when you modify the map file, as HAProxy won’t re-read it automatically.

For detailed information about configuration directives used in these examples, take a peek at official HAProxy documentation.