Load balancing with high availability can be tough to set up. Fortunately, Varnish HTTP Cache server provides a dead simple highly available load balancer that will also work as a caching server.
The modern use of SSL/TLS for all traffic has made this a little harder as Vanish has to handle unencrypted traffic to cache it. This means that we will need to terminate and decrypt the HTTPS connections before they are handed off to Varnish. We will do this with Apache2.
This means that the HTTPS requests will arrive at the Varnish server and get terminated by Apache2. Apache2 will then pass them on to the Varnish server for caching and distributing to the web front ends.
This guide will use the following three servers:
Function | Name | IP | Listen Port |
---|---|---|---|
Varnish load balancer | varnish | 1.1.1.1 | 443 |
Web server | web1 | 2.2.2.2 | 80 |
Web server | web2 | 3.3.3.3 | 80 |
You should already have web servers configured to serve your site over HTTP (port 80)on your web backends.
I recommend not attaching the web servers to the internet as they are not using HTTPS. Attach all the server’s onto a private network and configure the webservers to only listen to HTTP traffic on the private interfaces.
Install Varnish and Apache2
Log into your Ubuntu 20.04 server that you want to use as the load balancer and install Varnish and Apache2 with apt
:
apt install varnish apache2
Configure Apache2
First, enable the Apache2 modules that we will need:
a2enmod proxy
a2enmod proxy_balancer
a2enmod proxy_http
a2enmod ssl
Then restart Apache2
systemctl restart apache2.service
Next, create a VirtualHost file that will accept the HTTPS connections on the public IP address on port 443. Place this file into /etc/apache2/sites-available
:
<VirtualHost *:443>
ServerName <DOMAIN>
ErrorLog /var/log/apache2/<DOMAIN>-https_error.log
CustomLog /var/log/apache2/<DOMAIN>-https_access.log combined
SSLEngine on
SSLCertificateFile <PATH/TO/CERT>/<DOMAIN>.crt
SSLCertificateKeyFile <PATH/TO/KEY>/<DOMAIN>.key
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>
You will need to edit this to match your domain.
As you can see, you need to get an SSL certificate for your website. If you already have this then edit the SSLCertificateFile
and SSLCertificateKeyFile
lines to point to your certificate’s files.
Now, enable the new VirtualHost file:
a2ensite <VIRTUALHOST FILE>
And restart Apache2
systemctl restart apache2
Apache2 is now configured to terminate the HTTPS requests and pass them off to Varnish which will listen on 127.0.0.1:8080
for HTTP requests from Apache2.
Configure Varnish
The first job is to configure Varnish to listen on 127.0.0.1:8080
. This is done by modifying the start up parameters that are given to systemd.
Fist, create the following directory:
mkdir /etc/systemd/system/varnish.service.d
Next, create and edit this file /etc/systemd/system/varnish.service.d/override.conf
with the following contents:
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd -j unix,user=vcache -F -a 127.0.0.1:8080 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,256m
Next, reload systemd:
systemctl daemon-reload
Now that Varnish is listening on the correct port and IP you can create the load balancing configuration. Begin by moving to /etc/varnish/
then rename to supplied configuration file:
mv default.vcl default.vcl.origional
Then create and edit a new default.vcl
file by opening it with a text editor:
nano default.vcl
Then copy and past the following configuration:
vcl 4.0;
import directors;
backend web1 {
.host = "104.248.172.77";
.port = "80";
.probe = {
.url = "/";
.timeout = 1s;
.interval = 5s;
.window = 5;
.threshold = 3;
}
}
backend web2 {
.host = "165.232.104.211";
.port = "80";
.probe = {
.url = "/";
.timeout = 1s;
.interval = 5s;
.window = 5;
.threshold = 3;
}
}
sub vcl_init {
new balancer = directors.round_robin();
balancer.add_backend(web1);
balancer.add_backend(web2);
}
sub vcl_recv {
set req.backend_hint = balancer.backend();
}
Let’s break down these configuration blocks. The first two sections define the web backends:
backend web1 {
.host = "2.2.2.2";
.port = "80";
.probe = {
.url = "/";
.timeout = 1s;
.interval = 5s;
.window = 5;
.threshold = 3;
}
}
The .host
can the web server’s IP address or a domain name that resolves to it. The .probe
section is the health check that Varnish performs to determine if the webserver is online. It checks every 5 seconds that it can get an HTTP response within 1 second. If that fails Varnish will consider it offline and route traffic to the other backends.
Varnish will continue to probe the server and when it comes back online Varnish will direct traffic to it again.
The second section:
sub vcl_init {
new balancer = directors.round_robin();
balancer.add_backend(web1);
balancer.add_backend(web2);
}
Tells Varnish to create a load balancer called balancer
. The traffic is divided among the backends by round_robin
which means that web requests will be sent to the backends in turn.
The last section:
sub vcl_recv {
set req.backend_hint = balancer.backend();
}
routes all inbound traffic to the load balancer.
Finally, restart Varnish:
systemctl restart varnish.service
Testing
First, check that Varnish can communicate with the backends:
$ varnishadm backend.list
Backend name Admin Probe Last change
boot.web1 probe 5/5 good Mon, 07 Dec 2020 14:30:40 GMT
boot.web2 probe 5/5 good Mon, 07 Dec 2020 14:30:40 GMT
boot.balancer probe healthy Mon, 07 Dec 2020 14:30:40 GMT
Stop Apache2 on one of the webservers, wait a few seconds and try again:
$ varnishadm backend.list
Backend name Admin Probe Last change
boot.web1 probe 1/5 bad Mon, 07 Dec 2020 15:09:15 GMT
boot.web2 probe 5/5 good Mon, 07 Dec 2020 15:07:15 GMT
boot.lb probe healthy Mon, 07 Dec 2020 15:07:15 GMT
Varnish has detected that web1 is down and is now ignoring it. You can now restart Apache2 and watch Varnish accept it back into the cluster.
I also recommend putting different index.html
pages on the webservers during testing so you can tell where the page has been loaded from.