Introduction
In this week’s post I’ll share my experince with HAProxy - a high performance TCP/HTTP load balancer
Disclaimer: There will be nothing that you can’t read in the haproxy options manual apart from my thoughts.
Still, if you’re interested in my experience configuring this application for my setup keep on reading.
This post will be shorter than usual because I am a bit low on time since I’m preparing for an important interview. I don’t feel like posting coding exercises due to the fact that there’s plenty of material on the web for all of them and I do not consider myself as an “algo guru” or something.
In the next couple of weeks I’ll probably not post anything because of the reason above.
Let’s get started
In the end of last week’s post I ended up using HAProxy to proxy connections made to my router at home to my container hosting the website. I went through a lot of hassle but in the end I was able to log the original client’s IP address which I passed as a header from HAProxy to my web server.
I will not go through that again. If you are curiuos feel free to read the whole story.
This time I’ll explain some of the options which are pretty well described in the documentation and my experience of using the software.
Some prerequisites
If you’re new to load balancing, I’d strongly recommend reading this blog on load balancing concepts in the context of HAProxy before reading on.
Hiding behind 1 public address
If you’ve read my post where I explained my network you know that I’m a bit low on public ip addresses.
If I had a single web service I wanted to be public that’s fine - I can just let it slide through port 80 or 443 and no issues whatsoever. However, recently I’ve been setting up more and more web services and I had to get creative to access them - let’s put that on port 8000, this other one on port 8080 etc.
Unfortunately, there are 2 major issues with this strategy:
- It is really inconvenient to have to remember what service on what port is
- I started running out of traditional “web”-related ports and running a web server on port 4443 is a bit odd, right?
HAProxy to the rescue
It turned out that appart from adding headers to requests, HAProxy is able to check for both presence of headers, as well as their value which is interesting.
This is what an ordinary get request to www.google.com
looks like:
GET / HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: close
Upgrade-Insecure-Requests: 1
This is what a request to mail.google.com
looks like:
GET / HTTP/1.1
Host: mail.google.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Upgrade-Insecure-Requests: 1
Connection: close
Notice how the Host
header changes its value.
Well what if I setup multiple (sub)domains to point to the same ip address and then have something route requests based on the Host
header?
Note that the domain I’m doing this is samitor.com
as I consider is for more proffesional. The actual domain does not matter here
Here is how my dns zone looks like:
Notice the multiple CNAME records that point to the same address which in my case is 213.191.184.70
.
On layer 4 this wouldn’t make much sense, but as we go up to layer 7, it makes all the difference.
So what I eventually want to achieve is have a service that listens on ports 80 and 443 for HTTP requests. When one is received, I want it to inspect the Host
header and route the request to the according backend service.
This is how this requirements look like in HAProxy config’s terms:
frontend http_proxy
bind 213.191.184.70:80 # Public address to bind to
mode http # We are routing http here
option forwardfor # Add Forward-For header to tell the backend server there is a proxy
option http-server-close
reqadd X-Forwared-Proto:\ http
reqadd X-Forwarded-Port:\ 80
acl host_website hdr(host) -i viktorbarzin.me # ACL that matches requests with Host header = viktorbarzin.me
acl host_kms_info hdr(host) -i kms.samitor.com # ACL that matches requests with Host header = kms.samitor.com
acl host_privatebin hdr(host) -i pb.samitor.com # ACL that matches requests with Host header = pb.samitor.com
use_backend docker_gateway_http if host_website # If host is viktorbarzin.me, then use backend that serves my site.
use_backend kms_info_http if host_kms_info # If host is kms.samitor.com - use the backend that serves this
use_backend privatebin_http if host_privatebin # --//--
# The below logic is the same as the one above. The difference is adding the ssl settings.
frontend https_proxy
# Bind to port 81 and 444 on all interfaces (0.0.0.0)
bind 213.191.184.70:443 ssl crt /root/le/live/viktorbarzin.me/viktorbarzin.me.pem
# We're proxying HTTP here...
mode http
option forwardfor
option http-server-close
reqadd X-Forwarded-Proto:\ https
reqadd X-Forwarded-Port:\ 443
acl host_website hdr(host) -i viktorbarzin.me
use_backend docker_gateway_https if host_website
Once an Access Control List (ACL) is matched, traffic is routed to the specified backend server. This is how they are configured:
backend docker_gateway_http # Specify backend name
mode http # We are routing http here
option httpclose
option forwardfor
server node1 10.2.0.1:80 # Route traffic to this host at this port
backend docker_gateway_https
mode http
option httpclose
option forwardfor
server node1 10.2.0.1:443 ssl verify none
backend kms_info_http
mode http
option httpclose
option forwardfor
server node1 10.2.0.3:80
backend privatebin_http
mode http
option httpclose
option forwardfor
server node1 10.2.0.1:8000
The configuration is pretty clear and intuitive and it doesn’t take any debugging to make it work. It just works.
So opening the domains, despite they resolve to the same ip address, a different service is loaded:
And of course accessing viktorbarzin.me routes to the website you’re on right now.
If you try another domain that resolves to this IP address, HAProxy will not be able to find a matching ACL therefore will return an error:
Conclusion
In this post I showed you how I have implemented and how I use HAProxy in my home environment.
You saw how you could run potentially infinite amount of services all seemingly listening on the same IP address at the same port which may not be obvious if you have only your Networking 101 knowledge.
Here is a list of some useful sources I went through when I was setting up HAProxy: