HAProxy
This is about HAProxy introduced on the wiki.
HAProxy is free, open source software that provides a high availability load balancer and proxy server for TCP and HTTP-based applications that spreads requests across multiple servers.[3] It is written in C[4] and has a reputation for being fast and efficient (in terms of processor and memory usage).[5]
HAProxy is used by a number of high-profile websites including GoDaddy, GitHub, Bitbucket,[6] Stack Overflow,[7] Reddit, Speedtest.net, Tumblr, Twitter[8][9] and Tuenti[10] and is used in the OpsWorks product from Amazon Web Services.[11]
HAProxy is mainly used for load balancing for load balancing of web servers. It is similar to L4 switch for load balancing.
<Web server load balancing using HAProxy>
However, HAProxy can be used not only for web server load
balancing, but also for load balancing or security enhancement of
general TCP/IP servers. In this article, I will mainly explain the configuration for strengthening security than load balancing of general TCP/IP services.
Like a web server, a TCP/IP server providing a general TCP/IP service must establish a connection through a public IP to access from a remote location. However, as soon as the server is located in the public network, it is exposed to numerous external attacks. When providing services for clients with unspecified IPs, firewall processing is also difficult on servers that provide TCP/IP services. HAProxy is a powerful tool to protect your server from this risk. With HAProxy, the TCP/IP server needs to allow only the HAProxy IP lists in the firewall. Therefore, the client cannot directly connect to the TCP/IP server and receive the service. The client must connect to HAProxy and receive service. Therefore, the information of the TCP/IP server is not exposed to the client.
In addition, HAProxy can be dually configured by using HA S/W such as Keepalived as shown in the following figure.
In addition, HAProxy can be dually configured by using HA S/W such as Keepalived as shown in the following figure.
Today's test setup configuration is like this:
For simple testing, I created a TCP/IP echo server and echo client program using Python.
import socket import argparse parser = argparse.ArgumentParser() parser.add_argument('--ip', type=str, default="192.168.126.133", help='target server(proxy) IP address') parser.add_argument('--port', type=int, default=12280, help='port number') args = parser.parse_args() # Proxy address PROXY = args.ip # Proxy port number PORT = args.port print('Connect To :', PROXY, ' Port:', PORT) # Create python socket client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # connect to proxy server client_socket.connect((PROXY, PORT)) index = 1 for i in range(10): #send simple message client_socket.sendall('Hello'.encode()) data = client_socket.recv(1024) #receive echo message print('index:%03d'%index, ' Received', repr(data.decode())) index += 1 # close socket client_socket.close()
<tcpclient.py that runs on 192.168.126.131>
import socket import argparse parser = argparse.ArgumentParser() parser.add_argument('--port', type=int, default=12280, help='port number') args = parser.parse_args() # accept any client HOST = '' # port number for accept PORT = args.port # create socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # If you restart the program, the socket might be blocked. # To prevent this, use SO_REUSEADDR options. server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((HOST, PORT)) server_socket.listen() client_socket, addr = server_socket.accept() # client information print('Connected by', addr) loop = True index = 1 while loop: for i in range(10): data = client_socket.recv(1024) if not data: print('Received NULL') loop = False break #continue # Print the message received from client print('index:%03d'%index, ' Received from', addr, data.decode()) # echo the messgae client_socket.sendall(data) index += 1 client_socket.close() server_socket.close()
<tcpserver.py that runs on 192.168.126.130>
Be careful : If the firewall of the 192.168.126.130 server does not allow tcp:12280, the service will not work. Be sure to test after changing the firewall settings or stopping the firewall service.
HAProxy Installation
Ubuntu 18.04 installation
Installing HAProxy on Ubuntu is not difficult.
apt install lua5.3 liblua5.3-dev libssl-dev libsystemd-dev apt show haproxy apt install -y haproxy
CentOS7 installation
On CentOS, package updates are relatively slow. If you check it in CentOS 7.5.1804 version, it is haproxy 1.5.18 version.
[root@gcloud-seoul /]# yum info haproxy Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile * base: ftp.kaist.ac.kr * epel: ftp.riken.jp * extras: ftp.kaist.ac.kr * nux-dextop: mirror.li.nux.ro * updates: ftp.kaist.ac.kr Available Packages Name : haproxy Arch : x86_64 Version : 1.5.18 Release : 9.el7 Size : 834 k Repo : base/7/x86_64 Summary : TCP/HTTP proxy and load balancer for high availability environments URL : http://www.haproxy.org/ License : GPLv2+ Description : HAProxy is a TCP/HTTP reverse proxy which is particularly suited for high : availability environments. Indeed, it can: : - route HTTP requests depending on statically assigned cookies : - spread load among several servers while assuring server persistence : through the use of HTTP cookies : - switch to backup servers in the event a main server fails : - accept connections to special ports dedicated to service monitoring : - stop accepting connections without breaking existing ones : - add, modify, and delete HTTP headers in both directions : - block requests matching particular patterns : - report detailed status to authenticated users from a URI : intercepted by the application
Since the haproxy version is too low, I will try to build the source code.
#install required packages first #haproxy needs lua language, update or install lua 5.3 #check current lua version, in my case lua 5.1 is installed [root@gcloud-seoul lua-5.3.5]# lua Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio yum install gcc openssl-devel readline-devel systemd-devel make pcre-devel cd /usr/local/src wget https://www.lua.org/ftp/lua-5.3.5.tar.gz wget http://www.haproxy.org/download/2.2/src/haproxy-2.2.0.tar.gz #build lua and haproxy tar -xvzf lua-5.3.5.tar.gz tar -xvzf haproxy-2.2.0.tar.gz #build lua 5.3 cd lua-5.3.5 make INSTALL_TOP=/opt/lua-5.3.5 linux install #backup old lua if exists and update the lua to 5.3.5 mv /usr/bin/lua /usr/bin/lua.old ln -s /opt/lua-5.3.5/bin/lua /usr/bin/lua #build haproxy 2.2.0 cd /usr/local/src/haproxy-2.2.0 [root@gcloud-seoul haproxy-2.2.0]# make USE_NS=1 \ USE_TFO=1 \ USE_OPENSSL=1 \ USE_ZLIB=1 \ USE_LUA=1 \ USE_PCRE=1 \ USE_SYSTEMD=1 \ USE_LIBCRYPT=1 \ USE_THREAD=1 \ TARGET=linux-glibc \ LUA_INC=/opt/lua-5.3.5/include \ LUA_LIB=/opt/lua-5.3.5/lib [root@gcloud-seoul haproxy-2.2.0]# make install [root@gcloud-seoul haproxy-2.2.0]# whereis haproxy haproxy: /usr/local/sbin/haproxy [root@gcloud-seoul haproxy-2.2.0]# ln -s /usr/local/sbin/haproxy /usr/sbin/haproxy
When installation is complete, start the haproxy configuration.
#Setting up HAProxy for your server mkdir -p /etc/haproxy mkdir -p /var/lib/haproxy touch /var/lib/haproxy/stats
Now let's make haproxy as a service.
The init file in the example directory is for ubuntu. Therefore, do not use this file, but use the following script.
copy this file to /etc/init.d/haproxy and to grant execution rights do "chmod 755 /etc/init.d/haproxy" command.
#!/bin/sh # # Init Script to run HaProxy in daemon mode at boot time. # # Author: Riccardo Riva - RPM S.r.l. # Revision 1.0 - 2011 November, 18</pre> #==================================================================== # Run level information: # # chkconfig: 2345 85 15 # description: HA-Proxy is a TCP/HTTP reverse proxy which is particularly suited # for high availability environments. # processname: haproxy # config: /etc/haproxy.cfg # pidfile: /var/run/haproxy.pid # # Run "/sbin/chkconfig --add haproxy" to add the Run levels. # This will setup the symlinks and set the process to run at boot. #==================================================================== # Source function library. if [ -f /etc/init.d/functions ]; then . /etc/init.d/functions elif [ -f /etc/rc.d/init.d/functions ] ; then . /etc/rc.d/init.d/functions else exit 0 fi # Source networking configuration. . /etc/sysconfig/network # Check that networking is up. [ ${NETWORKING} = "no" ] && exit 0 [ -f /etc/haproxy/haproxy.cfg ] || exit 1 RETVAL=0 start() { /usr/local/sbin/haproxy -c -q -f /etc/haproxy/haproxy.cfg if [ $? -ne 0 ]; then echo "Errors found in configuration file." return 1 fi echo -n "Starting HAproxy: " daemon /usr/local/sbin/haproxy -D -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/haproxy return $RETVAL } stop() { echo -n "Shutting down HAproxy: " killproc haproxy -USR1 RETVAL=$? echo [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/haproxy [ $RETVAL -eq 0 ] && rm -f /var/run/haproxy.pid return $RETVAL } restart() { /usr/local/sbin/haproxy -c -q -f /etc/haproxy/haproxy.cfg if [ $? -ne 0 ]; then echo "Errors found in configuration file, check it with 'haproxy check'." return 1 fi stop start } check() { /usr/local/sbin/haproxy -c -q -V -f /etc/haproxy/haproxy.cfg } rhstatus() { status haproxy } condrestart() { [ -e /var/lock/subsys/haproxy ] && restart || : } # See how we were called. case "$1" in start) start ;; stop) stop ;; restart) restart ;; reload) restart ;; condrestart) condrestart ;; status) rhstatus ;; check) check ;; *) echo $"Usage: haproxy {start|stop|restart|reload|condrestart|status|check}" RETVAL=1 esac exit $RETVAL
<haproxy.sample>
cp haproxy.sample /etc/init.d/haproxy chmod 755 /etc/init.d/haproxy chkconfig haproxy on systemctl daemon-reload
Configuration
After installation, edit the /etc/haproxy/haproxy.cfg file. After modifying the haproxy.cfg file, be sure to reload the service settings with the "systemctl relaod haproxy" command.
global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners stats timeout 30s user haproxy group haproxy daemon # Default SSL material locations ca-base /etc/ssl/certs crt-base /etc/ssl/private # Default ciphers to use on SSL-enabled listening sockets. # For more information, see ciphers(1SSL). This list is from: # https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ # An alternative list with additional directives can be obtained from # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS ssl-default-bind-options no-sslv3 defaults log global mode tcp timeout client 50000 timeout server 50000 frontend cti_1_front bind *:12280 mode tcp default_backend cti_1_back frontend cti_2_front bind *:12281 default_backend cti_2_back backend cti_1_back balance roundrobin server server_1 192.168.126.130:12280 check backend cti_2_back balance roundrobin server server_1 192.168.126.130:12282 check
The most important parts are the bottom frontend cti_1_front, frontend cti_2_front, backend cti_1_back, and backend cti_2_back. In brief, it is as follows.
- frontend cti_1_front : Connection through HAProxy 12280 port is regarded as general TCP communication and routed to backend cti_1_back.
- frontend cti_2_front : Connection through HAProxy 12281 port is regarded as general TCP communication and routed to backend cti_2_back.
- backend cti_1_back : Route to 12280 port on 192.168.126.130 server.
- backend cti_2_back : Route to 12282 port on 192.168.126.130 server. Note that the port number has changed.
Documentation on configuring the cfg file is detailed in https://www.haproxy.org/download/1.4/doc/configuration.txt.
I will only explain the important parts.
bind
bind specifies the connection port requested by the client.
bind [<address>]:<port_range> [, ...]
bind [<address>]:<port_range> [, ...] interface <interface>
bind [<address>]:<port_range> [, ...] mss <maxseg>
bind [<address>]:<port_range> [, ...] transparent
bind [<address>]:<port_range> [, ...] id <id>
bind [<address>]:<port_range> [, ...] name <name>
bind [<address>]:<port_range> [, ...] defer-accept
Define one or several listening addresses and/or ports in a frontend.
May be used in sections : defaults | frontend | listen | backend
no | yes | yes | no
Arguments :
<address> is optional and can be a host name, an IPv4 address, an IPv6
address, or '*'. It designates the address the frontend will
listen on. If unset, all IPv4 addresses of the system will be
listened on. The same will apply for '*' or the system's
special address "0.0.0.0".
<port_range> is either a unique TCP port, or a port range for which the
proxy will accept connections for the IP address specified
above. The port is mandatory. Note that in the case of an
IPv6 address, the port is always the number after the last
colon (':'). A range can either be :
- a numerical port (ex: '80')
- a dash-delimited ports range explicitly stating the lower
and upper bounds (ex: '2000-2100') which are included in
the range.
Particular care must be taken against port ranges, because
every <address:port> couple consumes one socket (= a file
descriptor), so it's easy to consume lots of descriptors
with a simple range, and to run out of sockets. Also, each
<address:port> couple must be used only once among all
instances running on a same system. Please note that binding
to ports lower than 1024 generally require particular
privileges to start the program, which are independant of
the 'uid' parameter.
<interface> is an optional physical interface name. This is currently
only supported on Linux. The interface must be a physical
interface, not an aliased interface. When specified, all
addresses on the same line will only be accepted if the
incoming packet physically come through the designated
interface. It is also possible to bind multiple frontends to
the same address if they are bound to different interfaces.
Note that binding to a physical interface requires root
privileges.
<maxseg> is an optional TCP Maximum Segment Size (MSS) value to be
advertised on incoming connections. This can be used to force
a lower MSS for certain specific ports, for instance for
connections passing through a VPN. Note that this relies on a
kernel feature which is theorically supported under Linux but
was buggy in all versions prior to 2.6.28. It may or may not
work on other operating systems. The commonly advertised
value on Ethernet networks is 1460 = 1500(MTU) - 40(IP+TCP).
<id> is a persistent value for socket ID. Must be positive and
unique in the proxy. An unused value will automatically be
assigned if unset. Can only be used when defining only a
single socket.
<name> is an optional name provided for stats
transparent is an optional keyword which is supported only on certain
Linux kernels. It indicates that the addresses will be bound
even if they do not belong to the local machine. Any packet
targeting any of these addresses will be caught just as if
the address was locally configured. This normally requires
that IP forwarding is enabled. Caution! do not use this with
the default address '*', as it would redirect any traffic for
the specified port. This keyword is available only when
HAProxy is built with USE_LINUX_TPROXY=1.
defer-accept is an optional keyword which is supported only on certain
Linux kernels. It states that a connection will only be
accepted once some data arrive on it, or at worst after the
first retransmit. This should be used only on protocols for
which the client talks first (eg: HTTP). It can slightly
improve performance by ensuring that most of the request is
already available when the connection is accepted. On the
other hand, it will not be able to detect connections which
don't talk. It is important to note that this option is
broken in all kernels up to 2.6.31, as the connection is
never accepted until the client talks. This can cause issues
with front firewalls which would see an established
connection while the proxy will only see it in SYN_RECV.
It is possible to specify a list of address:port combinations delimited by
commas. The frontend will then listen on all of these addresses. There is no
fixed limit to the number of addresses and ports which can be listened on in
a frontend, as well as there is no limit to the number of "bind" statements
in a frontend.
Example :
listen http_proxy
bind :80,:443
bind 10.0.0.1:10080,10.0.0.1:10443
See also : "source".
bind [<address>]:<port_range> [, ...] interface <interface>
bind [<address>]:<port_range> [, ...] mss <maxseg>
bind [<address>]:<port_range> [, ...] transparent
bind [<address>]:<port_range> [, ...] id <id>
bind [<address>]:<port_range> [, ...] name <name>
bind [<address>]:<port_range> [, ...] defer-accept
Define one or several listening addresses and/or ports in a frontend.
May be used in sections : defaults | frontend | listen | backend
no | yes | yes | no
Arguments :
<address> is optional and can be a host name, an IPv4 address, an IPv6
address, or '*'. It designates the address the frontend will
listen on. If unset, all IPv4 addresses of the system will be
listened on. The same will apply for '*' or the system's
special address "0.0.0.0".
<port_range> is either a unique TCP port, or a port range for which the
proxy will accept connections for the IP address specified
above. The port is mandatory. Note that in the case of an
IPv6 address, the port is always the number after the last
colon (':'). A range can either be :
- a numerical port (ex: '80')
- a dash-delimited ports range explicitly stating the lower
and upper bounds (ex: '2000-2100') which are included in
the range.
Particular care must be taken against port ranges, because
every <address:port> couple consumes one socket (= a file
descriptor), so it's easy to consume lots of descriptors
with a simple range, and to run out of sockets. Also, each
<address:port> couple must be used only once among all
instances running on a same system. Please note that binding
to ports lower than 1024 generally require particular
privileges to start the program, which are independant of
the 'uid' parameter.
<interface> is an optional physical interface name. This is currently
only supported on Linux. The interface must be a physical
interface, not an aliased interface. When specified, all
addresses on the same line will only be accepted if the
incoming packet physically come through the designated
interface. It is also possible to bind multiple frontends to
the same address if they are bound to different interfaces.
Note that binding to a physical interface requires root
privileges.
<maxseg> is an optional TCP Maximum Segment Size (MSS) value to be
advertised on incoming connections. This can be used to force
a lower MSS for certain specific ports, for instance for
connections passing through a VPN. Note that this relies on a
kernel feature which is theorically supported under Linux but
was buggy in all versions prior to 2.6.28. It may or may not
work on other operating systems. The commonly advertised
value on Ethernet networks is 1460 = 1500(MTU) - 40(IP+TCP).
<id> is a persistent value for socket ID. Must be positive and
unique in the proxy. An unused value will automatically be
assigned if unset. Can only be used when defining only a
single socket.
<name> is an optional name provided for stats
transparent is an optional keyword which is supported only on certain
Linux kernels. It indicates that the addresses will be bound
even if they do not belong to the local machine. Any packet
targeting any of these addresses will be caught just as if
the address was locally configured. This normally requires
that IP forwarding is enabled. Caution! do not use this with
the default address '*', as it would redirect any traffic for
the specified port. This keyword is available only when
HAProxy is built with USE_LINUX_TPROXY=1.
defer-accept is an optional keyword which is supported only on certain
Linux kernels. It states that a connection will only be
accepted once some data arrive on it, or at worst after the
first retransmit. This should be used only on protocols for
which the client talks first (eg: HTTP). It can slightly
improve performance by ensuring that most of the request is
already available when the connection is accepted. On the
other hand, it will not be able to detect connections which
don't talk. It is important to note that this option is
broken in all kernels up to 2.6.31, as the connection is
never accepted until the client talks. This can cause issues
with front firewalls which would see an established
connection while the proxy will only see it in SYN_RECV.
It is possible to specify a list of address:port combinations delimited by
commas. The frontend will then listen on all of these addresses. There is no
fixed limit to the number of addresses and ports which can be listened on in
a frontend, as well as there is no limit to the number of "bind" statements
in a frontend.
Example :
listen http_proxy
bind :80,:443
bind 10.0.0.1:10080,10.0.0.1:10443
See also : "source".
mode
mode has one of tcp, http, and health. Generally, web server load balancing uses http, but tcp is used to route user-created tcp/ip services.
mode { tcp|http|health }
Set the running mode or protocol of the instance
May be used in sections : defaults | frontend | listen | backend
yes | yes | yes | yes
Arguments :
tcp The instance will work in pure TCP mode. A full-duplex connection
will be established between clients and servers, and no layer 7
examination will be performed. This is the default mode. It
should be used for SSL, SSH, SMTP, ...
http The instance will work in HTTP mode. The client request will be
analyzed in depth before connecting to any server. Any request
which is not RFC-compliant will be rejected. Layer 7 filtering,
processing and switching will be possible. This is the mode which
brings HAProxy most of its value.
health The instance will work in "health" mode. It will just reply "OK"
to incoming connections and close the connection. Nothing will be
logged. This mode is used to reply to external components health
checks. This mode is deprecated and should not be used anymore as
it is possible to do the same and even better by combining TCP or
HTTP modes with the "monitor" keyword.
When doing content switching, it is mandatory that the frontend and the
backend are in the same mode (generally HTTP), otherwise the configuration
will be refused.
Example :
defaults http_instances
mode http
Set the running mode or protocol of the instance
May be used in sections : defaults | frontend | listen | backend
yes | yes | yes | yes
Arguments :
tcp The instance will work in pure TCP mode. A full-duplex connection
will be established between clients and servers, and no layer 7
examination will be performed. This is the default mode. It
should be used for SSL, SSH, SMTP, ...
http The instance will work in HTTP mode. The client request will be
analyzed in depth before connecting to any server. Any request
which is not RFC-compliant will be rejected. Layer 7 filtering,
processing and switching will be possible. This is the mode which
brings HAProxy most of its value.
health The instance will work in "health" mode. It will just reply "OK"
to incoming connections and close the connection. Nothing will be
logged. This mode is used to reply to external components health
checks. This mode is deprecated and should not be used anymore as
it is possible to do the same and even better by combining TCP or
HTTP modes with the "monitor" keyword.
When doing content switching, it is mandatory that the frontend and the
backend are in the same mode (generally HTTP), otherwise the configuration
will be refused.
Example :
defaults http_instances
mode http
balance
balance specifies how client requests are distributed. In general, round robin is used the most. It can be used when there is only one server, and in this case, the client's connection is routed to only one server.
balance <algorithm> [ <arguments> ]
balance url_param <param> [check_post [<max_wait>]]
Define the load balancing algorithm to be used in a backend.
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
Arguments :
<algorithm> is the algorithm used to select a server when doing load
balancing. This only applies when no persistence information
is available, or when a connection is redispatched to another
server. <algorithm> may be one of the following :
roundrobin Each server is used in turns, according to their weights.
This is the smoothest and fairest algorithm when the server's
processing time remains equally distributed. This algorithm
is dynamic, which means that server weights may be adjusted
on the fly for slow starts for instance. It is limited by
design to 4095 active servers per backend. Note that in some
large farms, when a server becomes up after having been down
for a very short time, it may sometimes take a few hundreds
requests for it to be re-integrated into the farm and start
receiving traffic. This is normal, though very rare. It is
indicated here in case you would have the chance to observe
it, so that you don't worry.
static-rr Each server is used in turns, according to their weights.
This algorithm is as similar to roundrobin except that it is
static, which means that changing a server's weight on the
fly will have no effect. On the other hand, it has no design
limitation on the number of servers, and when a server goes
up, it is always immediately reintroduced into the farm, once
the full map is recomputed. It also uses slightly less CPU to
run (around -1%).
leastconn The server with the lowest number of connections receives the
connection. Round-robin is performed within groups of servers
of the same load to ensure that all servers will be used. Use
of this algorithm is recommended where very long sessions are
expected, such as LDAP, SQL, TSE, etc... but is not very well
suited for protocols using short sessions such as HTTP. This
algorithm is dynamic, which means that server weights may be
adjusted on the fly for slow starts for instance.
source The source IP address is hashed and divided by the total
weight of the running servers to designate which server will
receive the request. This ensures that the same client IP
address will always reach the same server as long as no
server goes down or up. If the hash result changes due to the
number of running servers changing, many clients will be
directed to a different server. This algorithm is generally
used in TCP mode where no cookie may be inserted. It may also
be used on the Internet to provide a best-effort stickiness
to clients which refuse session cookies. This algorithm is
static by default, which means that changing a server's
weight on the fly will have no effect, but this can be
changed using "hash-type".
uri This algorithm hashes either the left part of the URI (before
the question mark) or the whole URI (if the "whole" parameter
is present) and divides the hash value by the total weight of
the running servers. The result designates which server will
receive the request. This ensures that the same URI will
always be directed to the same server as long as no server
goes up or down. This is used with proxy caches and
anti-virus proxies in order to maximize the cache hit rate.
Note that this algorithm may only be used in an HTTP backend.
This algorithm is static by default, which means that
changing a server's weight on the fly will have no effect,
but this can be changed using "hash-type".
This algorithm supports two optional parameters "len" and
"depth", both followed by a positive integer number. These
options may be helpful when it is needed to balance servers
based on the beginning of the URI only. The "len" parameter
indicates that the algorithm should only consider that many
characters at the beginning of the URI to compute the hash.
Note that having "len" set to 1 rarely makes sense since most
URIs start with a leading "/".
The "depth" parameter indicates the maximum directory depth
to be used to compute the hash. One level is counted for each
slash in the request. If both parameters are specified, the
evaluation stops when either is reached.
url_param The URL parameter specified in argument will be looked up in
the query string of each HTTP GET request.
If the modifier "check_post" is used, then an HTTP POST
request entity will be searched for the parameter argument,
when it is not found in a query string after a question mark
('?') in the URL. Optionally, specify a number of octets to
wait for before attempting to search the message body. If the
entity can not be searched, then round robin is used for each
request. For instance, if your clients always send the LB
parameter in the first 128 bytes, then specify that. The
default is 48. The entity data will not be scanned until the
required number of octets have arrived at the gateway, this
is the minimum of: (default/max_wait, Content-Length or first
chunk length). If Content-Length is missing or zero, it does
not need to wait for more data than the client promised to
send. When Content-Length is present and larger than
<max_wait>, then waiting is limited to <max_wait> and it is
assumed that this will be enough data to search for the
presence of the parameter. In the unlikely event that
Transfer-Encoding: chunked is used, only the first chunk is
scanned. Parameter values separated by a chunk boundary, may
be randomly balanced if at all.
If the parameter is found followed by an equal sign ('=') and
a value, then the value is hashed and divided by the total
weight of the running servers. The result designates which
server will receive the request.
This is used to track user identifiers in requests and ensure
that a same user ID will always be sent to the same server as
long as no server goes up or down. If no value is found or if
the parameter is not found, then a round robin algorithm is
applied. Note that this algorithm may only be used in an HTTP
backend. This algorithm is static by default, which means
that changing a server's weight on the fly will have no
effect, but this can be changed using "hash-type".
hdr(<name>) The HTTP header <name> will be looked up in each HTTP request.
Just as with the equivalent ACL 'hdr()' function, the header
name in parenthesis is not case sensitive. If the header is
absent or if it does not contain any value, the roundrobin
algorithm is applied instead.
An optional 'use_domain_only' parameter is available, for
reducing the hash algorithm to the main domain part with some
specific headers such as 'Host'. For instance, in the Host
value "haproxy.1wt.eu", only "1wt" will be considered.
This algorithm is static by default, which means that
changing a server's weight on the fly will have no effect,
but this can be changed using "hash-type".
rdp-cookie
rdp-cookie(name)
The RDP cookie <name> (or "mstshash" if omitted) will be
looked up and hashed for each incoming TCP request. Just as
with the equivalent ACL 'req_rdp_cookie()' function, the name
is not case-sensitive. This mechanism is useful as a degraded
persistence mode, as it makes it possible to always send the
same user (or the same session ID) to the same server. If the
cookie is not found, the normal roundrobin algorithm is
used instead.
Note that for this to work, the frontend must ensure that an
RDP cookie is already present in the request buffer. For this
you must use 'tcp-request content accept' rule combined with
a 'req_rdp_cookie_cnt' ACL.
This algorithm is static by default, which means that
changing a server's weight on the fly will have no effect,
but this can be changed using "hash-type".
<arguments> is an optional list of arguments which may be needed by some
algorithms. Right now, only "url_param" and "uri" support an
optional argument.
balance uri [len <len>] [depth <depth>]
balance url_param <param> [check_post [<max_wait>]]
The load balancing algorithm of a backend is set to roundrobin when no other
algorithm, mode nor option have been set. The algorithm may only be set once
for each backend.
Examples :
balance roundrobin
balance url_param userid
balance url_param session_id check_post 64
balance hdr(User-Agent)
balance hdr(host)
balance hdr(Host) use_domain_only
Note: the following caveats and limitations on using the "check_post"
extension with "url_param" must be considered :
- all POST requests are eligible for consideration, because there is no way
to determine if the parameters will be found in the body or entity which
may contain binary data. Therefore another method may be required to
restrict consideration of POST requests that have no URL parameters in
the body. (see acl reqideny http_end)
- using a <max_wait> value larger than the request buffer size does not
make sense and is useless. The buffer size is set at build time, and
defaults to 16 kB.
- Content-Encoding is not supported, the parameter search will probably
fail; and load balancing will fall back to Round Robin.
- Expect: 100-continue is not supported, load balancing will fall back to
Round Robin.
- Transfer-Encoding (RFC2616 3.6.1) is only supported in the first chunk.
If the entire parameter value is not present in the first chunk, the
selection of server is undefined (actually, defined by how little
actually appeared in the first chunk).
- This feature does not support generation of a 100, 411 or 501 response.
- In some cases, requesting "check_post" MAY attempt to scan the entire
contents of a message body. Scanning normally terminates when linear
white space or control characters are found, indicating the end of what
might be a URL parameter list. This is probably not a concern with SGML
type message bodies.
See also : "dispatch", "cookie", "appsession", "transparent", "hash-type" and
"http_proxy".
balance url_param <param> [check_post [<max_wait>]]
Define the load balancing algorithm to be used in a backend.
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
Arguments :
<algorithm> is the algorithm used to select a server when doing load
balancing. This only applies when no persistence information
is available, or when a connection is redispatched to another
server. <algorithm> may be one of the following :
roundrobin Each server is used in turns, according to their weights.
This is the smoothest and fairest algorithm when the server's
processing time remains equally distributed. This algorithm
is dynamic, which means that server weights may be adjusted
on the fly for slow starts for instance. It is limited by
design to 4095 active servers per backend. Note that in some
large farms, when a server becomes up after having been down
for a very short time, it may sometimes take a few hundreds
requests for it to be re-integrated into the farm and start
receiving traffic. This is normal, though very rare. It is
indicated here in case you would have the chance to observe
it, so that you don't worry.
static-rr Each server is used in turns, according to their weights.
This algorithm is as similar to roundrobin except that it is
static, which means that changing a server's weight on the
fly will have no effect. On the other hand, it has no design
limitation on the number of servers, and when a server goes
up, it is always immediately reintroduced into the farm, once
the full map is recomputed. It also uses slightly less CPU to
run (around -1%).
leastconn The server with the lowest number of connections receives the
connection. Round-robin is performed within groups of servers
of the same load to ensure that all servers will be used. Use
of this algorithm is recommended where very long sessions are
expected, such as LDAP, SQL, TSE, etc... but is not very well
suited for protocols using short sessions such as HTTP. This
algorithm is dynamic, which means that server weights may be
adjusted on the fly for slow starts for instance.
source The source IP address is hashed and divided by the total
weight of the running servers to designate which server will
receive the request. This ensures that the same client IP
address will always reach the same server as long as no
server goes down or up. If the hash result changes due to the
number of running servers changing, many clients will be
directed to a different server. This algorithm is generally
used in TCP mode where no cookie may be inserted. It may also
be used on the Internet to provide a best-effort stickiness
to clients which refuse session cookies. This algorithm is
static by default, which means that changing a server's
weight on the fly will have no effect, but this can be
changed using "hash-type".
uri This algorithm hashes either the left part of the URI (before
the question mark) or the whole URI (if the "whole" parameter
is present) and divides the hash value by the total weight of
the running servers. The result designates which server will
receive the request. This ensures that the same URI will
always be directed to the same server as long as no server
goes up or down. This is used with proxy caches and
anti-virus proxies in order to maximize the cache hit rate.
Note that this algorithm may only be used in an HTTP backend.
This algorithm is static by default, which means that
changing a server's weight on the fly will have no effect,
but this can be changed using "hash-type".
This algorithm supports two optional parameters "len" and
"depth", both followed by a positive integer number. These
options may be helpful when it is needed to balance servers
based on the beginning of the URI only. The "len" parameter
indicates that the algorithm should only consider that many
characters at the beginning of the URI to compute the hash.
Note that having "len" set to 1 rarely makes sense since most
URIs start with a leading "/".
The "depth" parameter indicates the maximum directory depth
to be used to compute the hash. One level is counted for each
slash in the request. If both parameters are specified, the
evaluation stops when either is reached.
url_param The URL parameter specified in argument will be looked up in
the query string of each HTTP GET request.
If the modifier "check_post" is used, then an HTTP POST
request entity will be searched for the parameter argument,
when it is not found in a query string after a question mark
('?') in the URL. Optionally, specify a number of octets to
wait for before attempting to search the message body. If the
entity can not be searched, then round robin is used for each
request. For instance, if your clients always send the LB
parameter in the first 128 bytes, then specify that. The
default is 48. The entity data will not be scanned until the
required number of octets have arrived at the gateway, this
is the minimum of: (default/max_wait, Content-Length or first
chunk length). If Content-Length is missing or zero, it does
not need to wait for more data than the client promised to
send. When Content-Length is present and larger than
<max_wait>, then waiting is limited to <max_wait> and it is
assumed that this will be enough data to search for the
presence of the parameter. In the unlikely event that
Transfer-Encoding: chunked is used, only the first chunk is
scanned. Parameter values separated by a chunk boundary, may
be randomly balanced if at all.
If the parameter is found followed by an equal sign ('=') and
a value, then the value is hashed and divided by the total
weight of the running servers. The result designates which
server will receive the request.
This is used to track user identifiers in requests and ensure
that a same user ID will always be sent to the same server as
long as no server goes up or down. If no value is found or if
the parameter is not found, then a round robin algorithm is
applied. Note that this algorithm may only be used in an HTTP
backend. This algorithm is static by default, which means
that changing a server's weight on the fly will have no
effect, but this can be changed using "hash-type".
hdr(<name>) The HTTP header <name> will be looked up in each HTTP request.
Just as with the equivalent ACL 'hdr()' function, the header
name in parenthesis is not case sensitive. If the header is
absent or if it does not contain any value, the roundrobin
algorithm is applied instead.
An optional 'use_domain_only' parameter is available, for
reducing the hash algorithm to the main domain part with some
specific headers such as 'Host'. For instance, in the Host
value "haproxy.1wt.eu", only "1wt" will be considered.
This algorithm is static by default, which means that
changing a server's weight on the fly will have no effect,
but this can be changed using "hash-type".
rdp-cookie
rdp-cookie(name)
The RDP cookie <name> (or "mstshash" if omitted) will be
looked up and hashed for each incoming TCP request. Just as
with the equivalent ACL 'req_rdp_cookie()' function, the name
is not case-sensitive. This mechanism is useful as a degraded
persistence mode, as it makes it possible to always send the
same user (or the same session ID) to the same server. If the
cookie is not found, the normal roundrobin algorithm is
used instead.
Note that for this to work, the frontend must ensure that an
RDP cookie is already present in the request buffer. For this
you must use 'tcp-request content accept' rule combined with
a 'req_rdp_cookie_cnt' ACL.
This algorithm is static by default, which means that
changing a server's weight on the fly will have no effect,
but this can be changed using "hash-type".
<arguments> is an optional list of arguments which may be needed by some
algorithms. Right now, only "url_param" and "uri" support an
optional argument.
balance uri [len <len>] [depth <depth>]
balance url_param <param> [check_post [<max_wait>]]
The load balancing algorithm of a backend is set to roundrobin when no other
algorithm, mode nor option have been set. The algorithm may only be set once
for each backend.
Examples :
balance roundrobin
balance url_param userid
balance url_param session_id check_post 64
balance hdr(User-Agent)
balance hdr(host)
balance hdr(Host) use_domain_only
Note: the following caveats and limitations on using the "check_post"
extension with "url_param" must be considered :
- all POST requests are eligible for consideration, because there is no way
to determine if the parameters will be found in the body or entity which
may contain binary data. Therefore another method may be required to
restrict consideration of POST requests that have no URL parameters in
the body. (see acl reqideny http_end)
- using a <max_wait> value larger than the request buffer size does not
make sense and is useless. The buffer size is set at build time, and
defaults to 16 kB.
- Content-Encoding is not supported, the parameter search will probably
fail; and load balancing will fall back to Round Robin.
- Expect: 100-continue is not supported, load balancing will fall back to
Round Robin.
- Transfer-Encoding (RFC2616 3.6.1) is only supported in the first chunk.
If the entire parameter value is not present in the first chunk, the
selection of server is undefined (actually, defined by how little
actually appeared in the first chunk).
- This feature does not support generation of a 100, 411 or 501 response.
- In some cases, requesting "check_post" MAY attempt to scan the entire
contents of a message body. Scanning normally terminates when linear
white space or control characters are found, indicating the end of what
might be a URL parameter list. This is probably not a concern with SGML
type message bodies.
See also : "dispatch", "cookie", "appsession", "transparent", "hash-type" and
"http_proxy".
timeout client, timeout server
HAProxy terminates the connection if there is no packet transmission from the client or server during this time. If the service without a packet transmission can last a long time, set this value large.
The unit can be us, ms, s, m, h, d.
If set to 2d, it means that the connection is maintained even if there is no data transmission for 2 days.
The values of timeout client and timeout server generally apply the same values.
The unit can be us, ms, s, m, h, d.
If set to 2d, it means that the connection is maintained even if there is no data transmission for 2 days.
The values of timeout client and timeout server generally apply the same values.
Some parameters involve values representing time, such as timeouts. These
values are generally expressed in milliseconds (unless explicitly stated
otherwise) but may be expressed in any other unit by suffixing the unit to the
numeric value. It is important to consider this because it will not be repeated
for every keyword. Supported units are :
- us : microseconds. 1 microsecond = 1/1000000 second
- ms : milliseconds. 1 millisecond = 1/1000 second. This is the default.
- s : seconds. 1s = 1000ms
- m : minutes. 1m = 60s = 60000ms
- h : hours. 1h = 60m = 3600s = 3600000ms
- d : days. 1d = 24h = 1440m = 86400s = 86400000ms
values are generally expressed in milliseconds (unless explicitly stated
otherwise) but may be expressed in any other unit by suffixing the unit to the
numeric value. It is important to consider this because it will not be repeated
for every keyword. Supported units are :
- us : microseconds. 1 microsecond = 1/1000000 second
- ms : milliseconds. 1 millisecond = 1/1000 second. This is the default.
- s : seconds. 1s = 1000ms
- m : minutes. 1m = 60s = 60000ms
- h : hours. 1h = 60m = 3600s = 3600000ms
- d : days. 1d = 24h = 1440m = 86400s = 86400000ms
In the cfg file of the above example, 50000ms (50 seconds) was specified.
Be Careful : If this value is neglected, a connection may be suddenly terminated. Adjust according to the characteristics of the service.
If the configuration of the cfg file is completed, restart the HAProxy service of Ububtu Server.
#if started
systemctl reload haproxy
#if not started
systemctl start haproxy
Then run the tcpserver example on the server with the address 192.168.126.130. Then, run the tcpclient example on the client with the address 192.168.126.131. If you look at this example code, you can see that you are connecting to the HAProxy server 133, not the 130 server.
Be Careful : In this way, when a client directly accesses the address of a proxy server, the proxy server is called a reverse proxy.
#Run at 192.168.126.130 [root@zabbix-server python]# python3 tcpserver.py --port=12282 #Run at 192.168.126.131 [root@zabbix-server python]# python3 tcpclient.py --ip='192.168.126.133' --port=12281
As can be seen in the figure below, the service requested in 131 was delivered to server 130 via HAProxy 133. And it can be seen that the response from server 130 is transmitted back to 131 through HAProxy.
Wrapping up
HAProxy is not only for web server load balancing, but also for load balancing of general TCP/IP services or security enhancement of TCP/IP servers.
댓글
댓글 쓰기