I tend to build a lot of web applications in NodeJS using the Express.js webserver. When you have a few of these apps running on one server, you generally want to run them on unique ports and put some kind of proxy in front of them. Nginx works great for this and Apache can be another decent, though more bloated, alternative. Recently I decided to branch out for the sake of variety and to learn something new. HAProxy filled that role.
For the uninformed, HAProxy is more than just a reverse proxy; it's a high performance load balancer. Sites with lots of traffic will use something like HAProxy to funnel traffic to a cluster of web servers or even balance taffic between database servers. But what happens when we want to route multiple domains (or subdomains) to different hosts or clusters?
Setting up a single proxy
First lets take a look at the basics of proxying to an app server.
# config for haproxy 1.5.x global log 127.0.0.1 local0 log 127.0.0.1 local1 notice maxconn 4096 user haproxy group haproxy daemon defaults log global mode http option httplog option dontlognull option forwardfor option http-server-close stats enable stats auth someuser:somepassword stats uri /haproxyStats frontend http-in bind :80 default_backend web-app-cluster backend web-app-cluster balance leastconn option httpclose cookie JSESSIONID prefix server node1 10.0.0.1:8080 cookie A check server node2 10.0.0.2:8080 cookie A check server node3 10.0.0.3:8080 cookie A check
So this is a pretty basic config that will loadbalance across 3 application servers, each of which is on a unqiue IP and probably on its own dedicated machine. Generally you'll also want to run your load balancer(s) on a different server.
So what does this all mean?
defaults should be pretty self-explanatory, then we have a
frontend and a
backend. The frontend, as you can see, tells HAProxy what to bind to and defines a default backend. There are a lot of things that can be specified in the front end and you can also have multiple frontend definitions (for example, if you wanted to provide an unsecure route running on port 80 and SSL on port 443 and have different, or the same, backends for each). We'll go over some other options in the multiple domain example.
Diving into multiple domains and ACLs
Now lets take a look at how to route to multiple domains based on matching specific domain names.
# config for haproxy 1.5.x global log 127.0.0.1 local0 log 127.0.0.1 local1 notice maxconn 4096 user haproxy group haproxy daemon defaults log global mode http option httplog option dontlognull option forwardfor option http-server-close stats enable stats auth someuser:somepassword stats uri /haproxyStats frontend http-in bind *:80 # Define hosts acl host_bacon hdr(host) -i ilovebacon.com acl host_milkshakes hdr(host) -i bobsmilkshakes.com ## figure out which one to use use_backend bacon_cluster if host_bacon use_backend milshake_cluster if host_milkshakes backend baconcluster balance leastconn option httpclose option forwardfor cookie JSESSIONID prefix server node1 10.0.0.1:8080 cookie A check server node1 10.0.0.2:8080 cookie A check server node1 10.0.0.3:8080 cookie A check backend milshake_cluster balance leastconn option httpclose option forwardfor cookie JSESSIONID prefix server node1 10.0.0.4:8080 cookie A check server node1 10.0.0.5:8080 cookie A check server node1 10.0.0.6:8080 cookie A check
So here we are routing between two applications; ilovebacon.com and bobsmilkshakes.com. Each one has its own cluster of app servers that we want to load balance between. Lets take a closer look at where all the magic happens in the frontend.
frontend http-inbind *:80 # Define hostsacl host_bacon hdr(host) -i ilovebacon.comacl host_milkshakes hdr(host) -i bobsmilkshakes.com ## figure out which one to useuse_backend bacon_cluster if host_baconuse_backend milshake_cluster if host_milkshakes
If you've ever used nginx or Apache as reverse proxies, youd generally set things up using virtual hosts. HAProxy uses the notion of access control lists (acl) which can be used to direct traffic.
After we bind to port 80, we set up two acls. The
hdr (short for header) checks the hostname header. We also specify
-i to make sure its case insensitive, then provide the domain name that we want to match. You could also setup acls to match routes, file types, file names, etc. If you want to know more, feel free to check the docs. So now we effectively have two variables;
host_milkshakes. Then we tell HAProxy what backend to use by checking to see if the variable is true or not. One other thing that we could do after the
use_backend checks is specify a
default_backend <backend name> like we did in the first example to act as a catch-all to traffic hitting our load balancer that doesn't match either of the domain names.
Next time, I'll go over how to add HTTPS/SSL support to your websites.