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? global
and defaults
should be pretty self-explanatory, then we have a frontend
and abackend
. 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_bacon
and 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.