Building HAProxy from sources for performance, latest 2.4 under RHEL / CentOS 7
Some performance tips and how to upgrade or install haproxy in 2021
This article summarizes my adventures on installing HAProxy 2.4 which is the latest LTS version as of 2021. I wanted to upgrade an old 2.0.x LTS, compiled from sources, under CentOS 7.
So, we're going to build HAProxy ourselves, as it should be done to know a lot more about this proxy / load balancer and squeeze some extra performance out of it.
The goal of exploiting the maximum possible speed from HAProxy node(s) make sense since nowadays using HAProxy is useful not only for load balancing, but for SSL offloading: it provides high-performance SSL termination (so certificates management can be centralized there and backend webservers just receive unencrypted traffic).
I'm writing this because:
There is a myriad of "how to install haproxy" guides on the internet, but you have to do your own research for specific version gotchas (related to haproxy/OS/distro). Unless I'm missing something, HAProxy itself seems to enforce the "Read the F. Manual" motto about how to install / customize, which is not a bad thing, I'd also want my users read my carefully updated documentation overtime, but you know.
I was upgrading my own scripted installation of HAProxy 2.0.x to 2.4.x so I said: OK, let's try to do it and learn something new while doing it, since wanted to upgrade HAProxy in some production servers.
TLDR; I'm in a hurry
For you, lazy people 😜
How to compile haproxy from sources:
- for systemd, in CentOS 7
- with CPU optimizations (if suitable on bare-metal hardware)
- using a modern gcc and letting haproxy decide proper CFLAGS.
# Build HAProxy 2.4.x from sources under CentOS 7 -------------> scroll
$ sudo yum -y install virt-what curl wget gcc pcre-static pcre-devel make \
perl pcre-devel zlib-devel openssl openssl-devel readline-devel systemd-devel
# grab haproxy sources and cd into dir.
$ _haproxy_v="2.4.3"; mkdir -p ~/src; cd ~/src; wget \
http://www.haproxy.org/download/${_haproxy_v:0:3}/src/haproxy-${_haproxy_v}.tar.gz \
&& tar xzf haproxy-${_haproxy_v}.tar.gz -C ~/src && cd ~/src/haproxy-$_haproxy_v
# install newer gcc without messing the system stock gcc
$ sudo yum -y install centos-release-scl; sudo yum -y install devtoolset-10
# the following scl command will open a new bash to use gcc10
# If you want to script it, use the this "source" command instead: source scl_source enable devtoolset-10
$ scl enable devtoolset-10 bash
# compile haproxy using gcc 10 now
$ _MY_HAPROXY_CPU_NATIVE=; _VMTEST=$(sudo virt-what); \
if [[ -z $_VMTEST ]] ;then _MY_HAPROXY_CPU_NATIVE="CPU=native"; \
echo "This seems a Bare-metal server, using CPU=native"; else \
echo "This seems virtualized: $_VMTEST"; fi; \
make -j $(nproc) TARGET=linux-glibc $_MY_HAPROXY_CPU_NATIVE \
USE_PCRE=1 USE_PCRE_JIT=1 USE_OPENSSL=1 USE_TFO=1 USE_LIBCRYPT=1 USE_THREAD=1 USE_SYSTEMD=1
$ sudo make install
# here is where you could just restart haproxy and exit if this is an "update only" task
# the following is only needed if it is a first install
$ sudo ln -s /usr/local/sbin/haproxy /usr/sbin/haproxy
$ cd admin/systemd && make
$ sudo cp haproxy.service /usr/lib/systemd/system
$ sudo systemctl daemon-reload
$ sudo mkdir -p /etc/haproxy; sudo mkdir -p /run/haproxy
$ sudo mkdir -p /var/lib/haproxy; sudo touch /var/lib/haproxy/stats
$ sudo useradd -r haproxy
# manually create config, new file: /etc/haproxy/haproxy.cfg
# setup systemd service
$ sudo systemctl start haproxy
$ sudo systemctl status haproxy
# enable haproxy on boot
$ sudo systemctl enable haproxy
Or here you have the long, explained stuff:
Pre-requisites
This guide assumes that we're a non-root user with sudo privileges.
Let's install the following pre-requisites. I've tested myself that will work from a fresh, minimal CentOS 7 installation (after applying the latest yum update
).
$ sudo yum -y install virt-what curl wget gcc pcre-static pcre-devel make perl \
pcre-devel zlib-devel openssl openssl-devel readline-devel systemd-devel
Get the sources
Using this one-liner you can easily change the desired version and get the sources into a src
directory in your home:
$ _haproxy_v="2.4.3"; \
mkdir -p ~/src; cd ~/src; \
wget http://www.haproxy.org/download/${_haproxy_v:0:3}/src/haproxy-${_haproxy_v}.tar.gz && \
tar xzf haproxy-${_haproxy_v}.tar.gz -C ~/src && \
cd ~/src/haproxy-$_haproxy_v
Using a modern gcc
The stock gcc from CentOS 7 is pretty old... from 2015:
$ gcc --version
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)
From this site we learn that:
Software Collections, also known as SCL is a community project that allows you to build, install, and use multiple versions of software on the same system, without affecting system default packages. By enabling Software Collections, you gain access to the newer versions of programming languages and services which are not available in the core repositories.
The SCL repositories provide a package named Developer Toolset, which includes newer versions of the GNU Compiler Collection, and other development and debugging tools.
So let's go:
$ sudo yum -y install centos-release-scl
Currently, the Developer Toolset collections consists of #6 to 10. We install the #10:
$ sudo yum -y install devtoolset-10
To access GCC version 10 you need to launch a new shell instance using the Software Collection scl
tool:
scl enable devtoolset-10 bash
Now check the GCC version, you’ll notice that GCC 10 is the default version in your current shell. 2021 sounds much better and in fact you can tell the difference by looking at the CFLAGS that will get auto-injected on make
by doing haproxy -vv
later:
$ gcc --version
gcc (GCC) 10.2.1 20210130 (Red Hat 10.2.1-11)
⚠️ If you want to script this stuff, opening a new bash is a call for trouble (fork-bomb) so you could do the following to "enable devtoolset-x" just for your shell script: source scl_source enable devtoolset-10
Make options
We should be at ~/src/haproxy-2.4.3
A pretty safe make
command for haproxy is the following:
make -j $(nproc) TARGET=linux-glibc \
USE_PCRE=1 USE_PCRE_JIT=1 USE_OPENSSL=1 USE_TFO=1 \
USE_LIBCRYPT=1 USE_THREAD=1 USE_SYSTEMD=1
Let's disect some of the make compilation options (I like when an article explain this stuff). If you're extra curious, see the INSTALL and/or Makefile files from haproxy sources.
Note there is no
CFLAGS
. See below for more about it.We should use
CPU=native
to get the build machine's specific processor optimizations, but should be used only on non-virtualized environments (see INSTALL file: "known to break" in section "5) How to build HAProxy"). The default CPU setting for haproxy's make isgeneric
so no CPU-specific optimization.
If you want a one-liner to detect whenever to use CPU=native
or not in the make
command, here is one:
_MY_HAPROXY_CPU_NATIVE=; _VMTEST=$(sudo virt-what); \
if [[ -z $_VMTEST ]] ;then _MY_HAPROXY_CPU_NATIVE="CPU=native"; \
echo "This seems a Bare-metal server, using CPU=native"; \
else echo "This seems virtualized: $_VMTEST"; fi; \
make -j $(nproc) TARGET=linux-glibc $_MY_HAPROXY_CPU_NATIVE \
USE_PCRE=1 USE_PCRE_JIT=1 USE_OPENSSL=1 USE_TFO=1 \
USE_LIBCRYPT=1 USE_THREAD=1 USE_SYSTEMD=1
You can later check the options used to build haproxy by issuing haproxy -vv
-j $(nproc)
will try to use all the available CPUs to speed up compilation times.TARGET=linux-glibc
is the default for linux (kernel 2.6.28 and above). Any other is just other platforms or custom stuff.USE_SYSTEMD=1
if you want to integrate with systemd, since is not enabled by default. This is "new", lot of haproxy how-tos just use the init.d way. To properly install, after compiling haproxy you need to go toadmin/systemd
directory under sources andmake
to produce an usable service file for systemd. This one was "hard" to guess, had to google a bit.USE_ZLIB=1
if you really want to change the default, faster compression of HTTP responses that HAProxy can do (see INSTALL file: "4.6) Compression"). Lot of guides out there just putUSE_ZLIB=1
, but please read the INSTALL file about it. Don't use it and let the libslz library that is embedded in haproxy do the job.USE_LUA=1
if you really need the Lua scripting capabilities of HAProxy. You'll need to provideLua 5.3+
and tellLUA_INC=/path/to/lua/include
andLUA_LIB=/path/to/lua/lib
as seen here
About CFLAGS
You'll see many guides on the Internet that pass CFLAGS
to make to compile HAProxy. That overwrites the default CFLAGS
which is bad.
In fact latest HAProxy versions (in several branches) complain about how you messed up things doing that. So just leave CFLAGS out of make
for HAProxy.
This is the full CFLAGS complain message from a bad built haproxy
, which BTW is very kind advice from the devs:
$ haproxy -v
FATAL ERROR: invalid code detected -- cannot go further, please recompile!
The source code was miscompiled by the compiler, which usually indicates that
some of the CFLAGS needed to work around overzealous compiler optimizations
were overwritten at build time. Please do not force CFLAGS, and read Makefile
and INSTALL files to decide on the best way to pass your local build options.
Make install ... and a bit more
Now, the typical make install
will put the haproxy binary haproxy
in /usr/local/sbin
among other stuff:
$ sudo make install
Now test if haproxy
program is available:
$ haproxy -v
HAProxy version 2.4.3-4dd5a5a 2021/08/17 - https://haproxy.org/
[...]
Typing haproxy -vv
(two 'v') should show how haproxy was built.
Create a symbolic link for the binary to allow you to run HAProxy commands as a normal user.
$ sudo ln -s /usr/local/sbin/haproxy /usr/sbin/haproxy
Let's generate a valid haproxy.service
file to use with systemd:
$ cd admin/systemd && make
sed -e 's:@SBINDIR@:'/usr/local/sbin':' haproxy.service.in > haproxy.service
Let's copy the generated systemd service file to /usr/lib/systemd/system
$ sudo cp haproxy.service /usr/lib/systemd/system
Now let's create some more needed directories and files:
$ sudo mkdir -p /etc/haproxy; sudo mkdir -p /run/haproxy
$ sudo mkdir -p /var/lib/haproxy; sudo touch /var/lib/haproxy/stats
Create a system user haproxy
(this also creates the group):
$ sudo useradd -r haproxy
Making a simple config
The default config file is /etc/haproxy/haproxy.cfg
.
We manually create one with something like the following (or look for the examples
directory at haproxy sources).
This is just an example, but will let you start haproxy without complaining.
global
daemon
user haproxy
group haproxy
chroot /var/lib/haproxy
maxconn 1024
defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:80
default_backend servers
backend servers
balance roundrobin
server ws01 1.1.1.1:80
server ws02 1.1.1.2:80
Systemctl
Let's setup the service:
$ sudo systemctl daemon-reload
$ sudo systemctl start haproxy
$ sudo systemctl status haproxy
To enable on boot:
$ sudo systemctl enable haproxy
And you can start to play around. Another article could be done about different haproxy configurations, but you have general good articles on that on the internet.
Conclusion
I hope this helps some people. It's not easy to find a proper "build haproxy from sources" article for all cases / distros / haproxy versions overtime.
Please correct me if anything is [terribly] wrong in this guide.