In this blog post, you’ll learn how to dynamically configure HAProxy using the Runtime API.
Over the past 15 years, HAProxy has become well known for its reliability, superior performance, extensible features, and advanced security. It is relatively less known that one of HAProxy’s core building blocks is the Runtime API which provides very powerful dynamic configuration capabilities with no service reloads or restarts.
The early improvements to the Runtime API’s dynamic configuration capabilities were driven by requests from advanced HAProxy users such as Airbnb and Yelp through their development of SmartStack, a pioneering automated service discovery and registration platform. Since then, we have been strongly committed to evolving the Runtime API based on feedback from our users, as exemplified by our recent blog post Dynamic Scaling for Microservices with the HAProxy Runtime API and the release of HAProxy version 1.8.
In this blog post, we are going to take you on a tour of the HAProxy Runtime API and some of its key features, such as the ability to dynamically configure ACLs, stick tables, and TLS ticket keys. Beyond that, it allows for improved integration with service discovery tools and orchestration systems, enhances geolocation functionality, enables adaptive security policies, increases the efficiency of SSL/TLS clusters, and much more.
Related Blog:
Getting Started
The HAProxy Runtime API traces its origins back to our wishes to create a complete configuration and statistics API for HAProxy, whose commands would all take effect immediately, during runtime. One of our early features in this API was of course the ability to retrieve detailed real-time statistics. Also, unlike typical newer APIs which only support HTTP, the HAProxy Runtime API was always accessible through TCP and Unix sockets. That is why we sometimes still refer to it as the HAProxy stats socket or just socket, and why the configuration directive for enabling the Runtime API bears the same name.
The Runtime API is enabled in the HAProxy configuration by using the following example:
global | |
stats socket ipv4@127.0.0.1:9999 level admin | |
stats socket /var/run/hapee-lb.sock mode 666 level admin | |
stats timeout 2m |
If you are not using HAProxy Enterprise, the default Unix socket name in your installation might be named /var/run/haproxy.sock instead of /var/run/hapee-lb.sock.
Then, for testing or executing commands interactively, the Runtime API can be conveniently accessed using the interactive command prompt provided by socat:
global | |
stats socket ipv4@127.0.0.1:9999 level admin | |
stats socket /var/run/hapee-lb.sock mode 666 level admin | |
stats timeout 2m |
Please note that for the above to work, your version of socat needs to be compiled with GNU Readline support. This could be verified by running socat -V | grep READLINE
. If the output of socat -V
doesn’t mention READLINE, or it mentions under READLINE or running the actual command produces an error unknown device/address “readline”, which means you have a version without readline support.
For accessing the Runtime API from scripts, or as an alternative to the interactive use shown above, the following command could be used:
$ echo "help" | socat stdio /var/run/hapee-lb.sock | |
$ echo "help" | socat stdio tcp4-connect:127.0.0.1:9999 |
And finally, as an alternative to socat altogether, command nc from the package netcat could be used. In that case, please use the netcat-openbsd version of netcat; it also supports the option nc -U
for connecting to Unix domain sockets.
The best way to start with the Runtime API is to execute a simple request returning a list of all available commands. This is done by sending help
or any unknown command to the API. Here is the actual output from “help”, executed on HAProxy version 1.8-dev2. If your HAProxy is missing any particular commands or options, please make sure you are using a recent HAProxy or HAProxy Enterprise release.
> help | |
help : this message | |
prompt : toggle interactive mode with prompt | |
quit : disconnect | |
show errors : report last request and response errors for each proxy | |
clear counters : clear max statistics counters (add 'all' for all counters) | |
show info : report information about the running process | |
show stat : report counters for each proxy and server | |
show schema json : report schema used for stats | |
disable agent : disable agent checks (use 'set server' instead) | |
disable health : disable health checks (use 'set server' instead) | |
disable server : disable a server for maintenance (use 'set server' instead) | |
enable agent : enable agent checks (use 'set server' instead) | |
enable health : enable health checks (use 'set server' instead) | |
enable server : enable a disabled server (use 'set server' instead) | |
set maxconn server : change a server's maxconn setting | |
set server : change a server's state, weight or address | |
get weight : report a server's current weight | |
set weight : change a server's weight (deprecated) | |
show sess [id] : report the list of current sessions or dump this session | |
shutdown session : kill a specific session | |
shutdown sessions server : kill sessions on a server | |
clear table : remove an entry from a table | |
set table [id] : update or create a table entry's data | |
show table [id] : report table usage stats or dump this table's contents | |
disable frontend : temporarily disable specific frontend | |
enable frontend : re-enable specific frontend | |
set maxconn frontend : change a frontend's maxconn setting | |
show servers state [id] : dump volatile server information (for backend ) | |
show backend : list backends in the current running config | |
shutdown frontend : stop a specific frontend | |
set dynamic-cookie-key backend : change a backend secret key for dynamic cookies | |
enable dynamic-cookie backend : enable dynamic cookies on a specific backend | |
disable dynamic-cookie backend : disable dynamic cookies on a specific backend | |
show stat resolvers [id] : dumps counters from all resolvers section and associated name servers | |
set maxconn global : change the per-process maxconn setting | |
set rate-limit : change a rate limiting value | |
set timeout : change a timeout setting | |
show env [var] : dump environment variables known to the process | |
show cli sockets : dump list of cli sockets | |
add acl : add acl entry | |
clear acl : clear the content of this acl | |
del acl : delete acl entry | |
get acl : report the patterns matching a sample for an ACL | |
show acl [id] : report available acls or dump an acl's contents | |
add map : add map entry | |
clear map : clear the content of this map | |
del map : delete map entry | |
get map : report the keys and values matching a sample for a map | |
set map : modify map entry | |
show map [id] : report available maps or dump a map's contents | |
show pools : report information about the memory pools usage |
Retrieving Statistics
For a light introduction before going into the Runtime API’s more powerful commands, let’s demonstrate the method for obtaining real-time statistics from HAProxy.
One of the already well known methods for retrieving real-time statistics is through HAProxy’s built-in, HTML web page. These statistics, formatted in a simple and straightforward way, can be obtained after configuring the stats uri in HAProxy and visiting the statistics URL.
The same complete and raw information can also be obtained through the HAProxy Runtime API. The API command is named show stat:
> show stat |
The command’s output will be provided in CSV format. Support for JSON output has been included in HAProxy version 1.8 and HAProxy Enterprise version 1.7r2.
The statistics output contains more than 80 different metrics. To quickly convert it to a shorter and human readable output, we could use standard command line tools. Here is an example that shows a chosen subset of data and refreshes it every two seconds:
$ watch 'echo "show stat" | socat stdio /var/run/hapee-lb.sock | cut -d "," -f 1-2,5-10,34-36 | column -s, -t' | |
# pxname svname scur smax slim stot bin bout rate rate_lim rate_max | |
fe_main FRONTEND 10 10 7000 83 6347 15476 0 0 10 | |
be_stats BACKEND 0 0 700 0 0 0 0 0 | |
be_app websrv1 0 0 0 0 0 0 0 | |
be_app websrv2 0 0 0 0 0 0 0 | |
be_app BACKEND 0 0 700 0 0 0 0 0 | |
be_other websrv1 0 10 322 6347 15476 14 20 | |
be_other BACKEND 10 10 700 83 6347 15476 0 10 |
By using the command cut
above, we have narrowed the selection down to the fields useful for quick and summarized monitoring:
scur: current number of sessions
smax: highest number of sessions seen since HAProxy was reloaded/restarted or had its statistics reset
slim: configured upper limit on the number of sessions
stot: cumulative number of sessions processed so far
bin: bytes in
bout: bytes out
rate: average number of sessions per second, calculated based on the 1 second
rate_lim: configured upper limit on new sessions per second
rate_max: highest number of new sessions per second seen since HAProxy was reloaded/restarted or had its statistics reset
For a listing and description of all the fields available in the show stat
output, please refer to the HAProxy Management Guide, section 9.1.
Administering Servers and Proxies
Using the Runtime API, you’re able to disable and enable servers or gradually drain traffic away from them. You can also enable and disable health checks, enable and disable frontends, and change the load balancing weight assigned to servers. Let’s take a look at a few examples.
If you want to gradually drain traffic away from a particular server, use the set server
command with the state
argument set to drain:
# Drain traffic | |
> set server be_app/webserv1 state drain | |
# Allow server to accept traffic again | |
> set server be_app/webserv1 state ready |
When a server is in drain mode, HAProxy will still send it regular health checks. You can stop them too by setting the server’s state to maint instead of drain. Draining a server gradually takes it out of the load balancing rotation, while putting it into maintenance mode is the same as disabling it immediately.
If you only want to stop the health checks, then use the disable health
command, like so:
# Disable health checks | |
> disable health be_app/webserv1 | |
# Enable health checks | |
> enable health be_app/webserv1 |
Or, if you’d like to send more or less traffic to a server, you can change its weight. Use the set server
command with the weight
argument:
# Change weight by percentage of its original value | |
> set server be_app/webserv1 weight 50% | |
# Change weight in proportion to other servers | |
> set server be_app/webserv1 weight 100 |
Something else that you can do is stop all traffic to a frontend proxy. Use the disable frontend
command to release a port binding from a frontend. You might use this to associate that port with a different frontend, if the original is causing trouble. Here’s an example:
# Disable a frontend | |
> disable frontend fe_main | |
# Enable frontend | |
> enable frontend fe_main |
There’s more to see, including changing a server’s maxconn
value, changing a server’s assigned IP address, and changing the health check port. See the official documentation for more information.
Updating Stick Tables
Stick tables are yet another very powerful and very applicable, but simple to use feature in HAProxy. Stick tables allow keeping track of arbitrary client information across requests, so they are used for purposes such as creating “sticky” sessions, implementing security protections, setting limits, and maintaining counters and lists.
Stick tables (along with timers, schedulers, ACLs, and many other features and tools) are based on high-performance Elastic Binary Trees, invented by our HAProxy Technologies CTO, Willy Tarreau. Ebtrees allow these tables to contain millions of records while maintaining extremely fast speed of access. We have written about stick tables and their uses in many of our previous blog posts, including:
The Runtime API allows stick table entries to be added, removed, searched, and updated during runtime. In the following example, we are going to create a stick table of 1 million entries to keep track of the request rates coming from individual client IPs. We will impose a limit of a maximum 5 requests per second, and the IP addresses which keep sending requests with a rate above that limit over a period of 10 seconds will be denied further requests for a period of 15 minutes.
frontend ft_web | |
bind 0.0.0.0:8080 | |
# Stick table definition | |
stick-table type ip size 1m expire 15m store gpt0,http_req_rate(10s) | |
http-request allow if { src -f /etc/hapee/whitelist.lst } | |
http-request track-sc0 src | |
acl abuse src_http_req_rate ge 5 | |
http-request sc-set-gpt0(0) 1 if abuse | |
http-request deny if { sc0_get_gpt0 ge 1 } |
As mentioned, the above stick table can store up to 1 million entries, and each entry will contain 3 values: an IP address, the determined average HTTP request rate over 10 seconds, and a general purpose tag “GPT0”, which will have a value of “1” if the requests are to be denied. Entries in the table will be removed to not occupy space if the client IP makes no further requests within the configured expiration period (in our example, 15 minutes). Requests coming from the whitelisted IPs will be passed through without imposing any restrictions on them.
All of this will work automatically and require no manual intervention, thanks just to the configuration lines above.
However, we might still want to be able to manipulate the stick table during runtime, for example to change the entries’ GPT0 flag, to remove entries altogether, or to list currently blocked IPs. This can again be done using the Runtime API:
# List the contents of the stick table | |
> show table ft_web | |
# table: ft_web, type: ip, size:1048576, used:2 | |
0x1c1efe0: key=127.0.0.1 use=0 exp=5600 gpt0=1 http_req_rate(10000)=50 | |
0x1c0d8e0: key=192.168.122.1 use=0 exp=7713 gpt0=0 http_req_rate(10000)=1 | |
# Set an entry's GPT0 value to 0 | |
> set table ft_web key 127.0.0.1 data.gpt0 0 | |
# Delete the entry | |
> clear table ft_web key 127.0.0.1 | |
# Verify successful deletion | |
> show table ft_web | |
# table: ft_web, type: ip, size:1048576, used:2 | |
0x1c0d8e0: key=192.168.122.1 use=0 exp=7700 gpt0=0 http_req_rate(10000)=0 | |
# List remaining blocked client IPs (those with tag GPT0 value greater than 0) | |
> show table ft_web data.gpt0 gt 0 |
Updating ACLs
To extend the above example of rate-limiting the incoming HTTP requests, let’s assume we would now want to change the existing rate limit from 10 to 60 requests per second.
To do so, the HAProxy Runtime API will certainly allow us to change the ACLs during runtime. We will execute the command “show acl” to see the configured ACLs, then identify the one we want to modify, and finally perform the actual modification. The modification will consist of adding a new value and then deleting the old one to complete the transition.
A complete session could look like the following:
> show acl | |
# id (file) description | |
0 () acl 'path_beg' file '/etc/hapee/hapee-lb.cfg' line 60 | |
1 () acl 'nbsrv' file '/etc/hapee/hapee-lb.cfg' line 61 | |
2 (/etc/hapee/whitelist.lst) pattern loaded from file '/etc/hapee/whitelist.lst' used by acl at file '/etc/hapee/hapee-lb.cfg' line 67 | |
3 () acl 'src' file '/etc/hapee/hapee-lb.cfg' line 67 | |
4 () acl 'src_http_req_rate' file '/etc/hapee/hapee-lb.cfg' line 69 | |
5 () acl 'sc0_get_gpt0' file '/etc/hapee/hapee-lb.cfg' line 71 | |
6 () acl 'path_beg' file '/etc/hapee/hapee-lb.cfg' line 80 | |
7 () acl 'always_true' file '/etc/hapee/hapee-lb.cfg' line 106 | |
# We are interested in the 5th ACL (ID #4) | |
# Display the ACL's current settings | |
> show acl #4 | |
0xc9a3e0 10 | |
# Add a new rate of 60 | |
> add acl #4 60 | |
# Verify that both values are now present | |
> show acl #4 | |
0xc9a3e0 10 | |
0xce8f10 60 | |
# Delete the old value in any of the following two ways - by value, or by using the reference number | |
> del acl #4 10 | |
> del acl #4 #0xc9a3e0 | |
# Verify that only one value now remains in effect | |
> show acl #4 | |
0xce8f10 60 |
Updating Whitelists and Blacklists
Again extending our original rate limiting example, please notice that we have used the following configuration directive to provision for whitelisted IP addresses:
http-request allow if { src -f /etc/hapee/whitelist.lst } |
This configuration line tells HAProxy to fetch the source IP, check it against the list of IP addresses that were loaded from whitelist.lst, and pass the request through without further checks if the IP address is found in the list.
In this example we are going to modify the contents of the whitelist by using the Runtime API – we will be changing an IP address entry from “192.168.1.6” to “192.168.1.9”. As usual, to do so we are going to list the ACLs, identify the entries we want to modify, and then perform the actual modification. The modification will consist of adding the new value and then deleting the old one to complete the transition.
A complete session could look like the following:
> show acl | |
# id (file) description | |
0 () acl 'path_beg' file '/etc/hapee/hapee-lb.cfg' line 60 | |
1 () acl 'nbsrv' file '/etc/hapee/hapee-lb.cfg' line 61 | |
2 (/etc/hapee/whitelist.lst) pattern loaded from file '/etc/hapee/whitelist.lst' used by acl at file '/etc/hapee/hapee-lb.cfg' line 67 | |
3 () acl 'src' file '/etc/hapee/hapee-lb.cfg' line 67 | |
4 () acl 'src_http_req_rate' file '/etc/hapee/hapee-lb.cfg' line 69 | |
5 () acl 'sc0_get_gpt0' file '/etc/hapee/hapee-lb.cfg' line 71 | |
6 () acl 'path_beg' file '/etc/hapee/hapee-lb.cfg' line 80 | |
7 () acl 'always_true' file '/etc/hapee/hapee-lb.cfg' line 106 | |
# We are interested in the third entry (ID #2) | |
# Display its content | |
> show acl #2 | |
0x24de920 191.168.1.1 | |
0x24de960 191.168.1.2 | |
0x24de9a0 191.168.1.3 | |
0x24de9e0 191.168.1.4 | |
0x24dcf10 191.168.1.5 | |
0x24de300 191.168.1.6 | |
0x24de340 191.168.1.7 | |
# Add the new entry | |
> add acl #2 192.168.1.9 | |
# Delete the old entry | |
> del acl #2 #0x24de300 | |
# Verify the result | |
> show acl #2 | |
0x24de920 191.168.1.1 | |
0x24de960 191.168.1.2 | |
0x24de9a0 191.168.1.3 | |
0x24de9e0 191.168.1.4 | |
0x24dcf10 191.168.1.5 | |
0x24de340 191.168.1.7 | |
0x24de300 192.168.1.9 |
SSL/TLS
Updating TLS tickets
HAProxy uses tls-ticket-keys to avoid the expensive key renegotiation when an existing client wants to start a new session after closing the previous one. If a client supports session tickets, HAProxy will send it a new session ticket record containing all of the negotiated session data (cipher suite, master secret, etc.). The record will be encrypted with a secret key only known to the HAProxy process, ensuring that the data won’t be read or modified by the client.
The secrets used to encrypt the TLS tickets are generated on HAProxy startup. If an active-active HAProxy cluster is set up and a client moves between nodes, it will have to renegotiate the keys often because the tickets, encrypted with node-specific keys, will not be understood by other nodes in the cluster. In order to avoid this, it is possible to place secrets in a file and also periodically rotate them to maintain perfect forward secrecy. That way all nodes in the cluster will use exactly the same set of secrets and clients will be able to move between HAProxy nodes without any side effects.
This could be done by updating the file containing the keys and reloading HAProxy manually, but it can also be done during runtime by using the HAProxy Runtime API.
A complete session could look like the following:
# List keys files | |
> show tls-keys | |
# id (file) | |
0 (/dev/shm/hapee_ticket_keys.txt) | |
# List keys in a particular file | |
> show tls-keys #0 | |
# id secret | |
# 0 (/dev/shm/hapee_ticket_keys.txt) | |
0.0 vaL65b3ns0mAJbiQRGiQZ84H4C9N1dZAyDYrHXVqG6KRDuXCo8mpwfk6+xPtlM1m | |
0.1 fQxhSJT8sBKNb6JAFZT11UkzplfXEI1uUijPQUTBysZpNqzT26s2RVARxCoo5E52 | |
0.2 QtxrjBbPrX6z/PljdHIFqmHMH2/Rc5zZzIKklcfBPJa01G6PU9Dp9ixcibeisZxU | |
# Update key | |
> set ssl tls-keys #0 CaF7HpWr0gUByzxDqlbvYXCFT2zqmhnKFAdbM4MyQHfty974QO31Pn1OLJIR92rk | |
TLS ticket key updated! | |
# Verify successful update | |
> show tls-keys #0 | |
# id secret | |
# 0 (/dev/shm/hapee_ticket_keys.txt) | |
0.0 fQxhSJT8sBKNb6JAFZT11UkzplfXEI1uUijPQUTBysZpNqzT26s2RVARxCoo5E52 | |
0.1 QtxrjBbPrX6z/PljdHIFqmHMH2/Rc5zZzIKklcfBPJa01G6PU9Dp9ixcibeisZxU | |
0.2 CaF7HpWr0gUByzxDqlbvYXCFT2zqmhnKFAdbM4MyQHfty974QO31Pn1OLJIR92rk |
Configuring OCSP responses
HAProxy also supports the TLS Certificate Status Request extension, also known as “OCSP stapling”. For more information on configuring the OCSP responses, please see the documentation section 5.1 – crt. OCSP stapling allows HAProxy to send certificate status information to the clients. The information is a signed OCSP response and will avoid the need for a client to send a separate request to the OCSP service hosted by the issuing Certificate Authority. Here is an example of a command used to display the OCSP response from a server:
$ openssl s_client -servername hapee-mh -connect 127.0.0.1:443 -tlsextdebug -status | |
CONNECTED(00000003) | |
TLS server extension "server name" (id=0), len=0 | |
TLS server extension "renegotiation info" (id=65281), len=1 | |
0001 - <SPACES/NULS> | |
TLS server extension "EC point formats" (id=11), len=4 | |
0000 - 03 00 01 02 .... | |
TLS server extension "session ticket" (id=35), len=0 | |
TLS server extension "status request" (id=5), len=0 | |
TLS server extension "heartbeat" (id=15), len=1 | |
0000 - 01 . | |
depth=2 C = FR, ST = France, O = MMH-LAB, OU = SERVER, CN = MMH-CA | |
verify error:num=19:self signed certificate in certificate chain | |
verify return:0 | |
OCSP response: | |
====================================== | |
OCSP Response Data: | |
OCSP Response Status: successful (0x0) | |
Response Type: Basic OCSP Response | |
Version: 1 (0x0) | |
Responder Id: C = FR, ST = France, O = MMH-LAB, OU = SERVER, CN = MMH-OCSP | |
Produced At: Nov 20 16:00:00 2017 GMT | |
Responses: | |
Certificate ID: | |
Hash Algorithm: sha1 | |
Issuer Name Hash: 1A7A927EC43135F4B6FF62065A09E1C7FD02557B | |
Issuer Key Hash: A45FE1C0DDC03B42C34306A08DF14ED33B74B86C | |
Serial Number: 1000 | |
Cert Status: good | |
This Update: Nov 20 16:00:00 2017 GMT | |
Next Update: Nov 30 16:00:00 2017 GMT |
Near the end of the output, we can see the information “Cert Status: Good”. We can also see in the last line of output that the next update is expected in 10 days.
Now, if we would want to update the OCSP response in order to change the next update time from 10 to 20 days, we could prepare the response and then load it into the running HAProxy instance by using the Runtime API:
$ openssl ocsp -CAfile intermediate/certs/ca-chain.cert.pem -issuer intermediate/certs/intermediate.cert.pem -cert intermediate/certs/hapee-mmh.cert.pem -url http://localhost:8080 -respout /tmp/ocsp-resp.der | |
Response verify OK | |
intermediate/certs/hapee-mmh.cert.pem: good | |
This Update: Nov 20 16:00:00 2017 GMT | |
Next Update: Dec 10 16:00:00 2017 GMT | |
$ echo "set ssl ocsp-response $(base64 -w 10000 /tmp/ocsp-resp.der)" | socat stdio /var/run/hapee-lb.sock | |
OCSP Response updated! |
And using the same approach as above, we can verify that HAProxy is now serving the updated response:
$ openssl s_client -servername hapee-mh -connect 127.0.0.1:443 -tlsextdebug -status | |
CONNECTED(00000003) | |
... | |
... | |
OCSP response: | |
====================================== | |
... | |
... | |
Cert Status: good | |
This Update: Nov 20 16:00:00 2017 GMT | |
Next Update: Dec 10 16:00:00 2017 GMT |
Troubleshooting
When it comes to troubleshooting the configuration or observed application behavior, looking into the HAProxy log files is probably one of the most common courses of action. And it is certainly a good one – HAProxy logs contain all sorts of information, including data about timers, sizes, connection counters, etc. But from time to time we may come across complicated issues or even bugs that need more detailed debugging output than even logs can provide.
Displaying errors
As mentioned, HAProxy logs errors to log files, as documented in the section 1.3.1. The Response line. For example, if we send it simple, invalid traffic such as the following:
$ echo "This is invalid traffic\n\nMore invalid traffic" | nc -v hapee-lab 80 | |
Connection to hapee-lab 80 port [tcp/http] succeeded! | |
HTTP/1.0 400 Bad request | |
Cache-Control: no-cache | |
Connection: close | |
Content-Type: text/html | |
400 Bad Request | |
Your browser sent an invalid request. |
Then the logs will show the following error:
hapee-lab hapee-lb[17817]: 192.168.122.1:53918 [20/Nov/2017:13:29:11.305] fe_main fe_main/ -1/-1/-1/-1/+0 400 +187 - - PR-- 0/0/0/0/0 0/0 "" |
But to get even more information about the request in question and even see the contents of the request, we can use the Runtime API command “show errors”:
> show errors | |
Total events captured on [10/Nov/2017:13:41:58.421] : 3 | |
[10/Nov/2017:13:41:43.328] frontend fe_main (#2): invalid request | |
backend (#-1), server (#-1), event #2 | |
src 192.168.122.1:54154, session #15, session flags 0x00000080 | |
HTTP msg state MSG_RQVER(6), msg flags 0x00000000, tx flags 0x00000000 | |
HTTP chunk len 0 bytes, HTTP body len 0 bytes | |
buffer flags 0x00808002, out 0 bytes, total 46 bytes | |
pending 46 bytes, wrapping at 16384, error at position 8: | |
00000 This is invalid traffic\n | |
00024 \n | |
00025 More invalid traffic\n |
Dumping sessions
Depending on the logging configuration, all connections can be logged to HAProxy’s log files. But, as usual, they are logged only after HAProxy gets a reply from the backend servers or when one of its timers expires. In situations where long timeouts are involved or where sessions are taking long to complete, this might cause the logs to never seem to arrive. In such situations, the Runtime API command “show sess” may be used to dump all current sessions and their related information:
> show sess | |
0xd38210: proto=tcpv4 src=192.168.122.1:55234 fe=fe_main be= srv= ts=02 age=11s calls=1rq[f=400000h,i=0,an=34h,rx=,wx=,ax=] rp[f=80000000h,i=0,an=00h,rx=,wx=,ax=] s0=[7,8h,fd=1,ex=] s1=[0,10h,fd=-1,ex=] exp= | |
0xd24dd0: proto=unix_stream src=unix:1 fe=GLOBAL be= srv= ts=02 age=0s calls=1 rq[f=c08202h,i=0,an=00h,rx=10m,wx=,ax=] rp[f=80008002h,i=0,an=00h,rx=,wx=,ax=] s0=[7,8h,fd=2,ex=] s1=[7,4018h,fd=-1,ex=] exp=10m |
In the above example, we can see two sessions: the first one is an IPv4 session; the second is a session related to our invoking of the Runtime API. From looking into the output, we can, for example, identify that the IPv4 session is in an early stage of processing because no backend (“be=”) was selected yet. Also, to help us further in troubleshooting complex issues, we may use the command “show sess” with a session ID provided as an argument (“show sess ID”) to get even more details about a particular session.
Closing sessions
In addition to displaying active sessions using “show sess”, we can also use the Runtime API to close sessions at will by using “shutdown session”:
> shutdown session 0xd38210 |
Such sessions will appear in the logs containing the flag “K”, indicating they were shut down.
Updating GeoIP databases
GeoIP databases map IP address ranges to geographical locations. Such databases can be used in HAProxy for different purposes. Often times, they are used for performing GeoIP lookups natively within HAProxy and serving the data to backend servers. The backend servers can then depend on the information being available as part of incoming requests, requiring no specific GeoIP code nor causing any slowdowns in the application code itself.
More information about using GeoIP with HAProxy can be found in one of our previous blog posts, Using GeoIP Databases with HAProxy. Another common use case for GeoIP is to include the client country code in the HAProxy logs. This could be achieved by using the following log-format directive:
log-format "%ci:%cp_%[src,map_ip(/etc/hapee/ip-country.lst)] [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r" |
The above line will use the map_ip
converter in order to get the country code from the map file ip-country.lst. Logs based on this format will then look like the following:
192.168.122.1:51000_FR [20/Nov/2017:19:34:11.688] fe_main be_app/websrv1 0/0/0/0/0 200 5720 - - --NI 1/1/0/1/0 0/0 "GET / HTTP/1.1" |
Now, in terms of updating the GeoIP databases, let’s assume we have the following new GeoIP entries in a map file:
2.0.0.0/12 FR | |
2.16.0.0/24 FR | |
2.16.2.0/23 FR | |
2.16.10.0/24 FR |
We could easily add them to the running configuration using the Runtime API command add map
. Also, instead of adding entries one by one, we are going to use a little bit of bash shell scripting to automate the data import:
# Loop through the map entries on disk and add them to the running configuration | |
$ IFS=$'\n' | |
$ for ip_country in $(cat /tmp/more-ip-countries.lst); do | |
echo "add map /tmp/ip-country.lst $ip_country" | |
done | socat stdio /var/run/hapee-lb.sock |
Where to Go From Here
We’ve written several other blog posts that will give you even more examples of using the HAProxy Runtime API. Be sure to read:
Conclusion
We hope you have enjoyed this blog post providing an introduction to the HAProxy Runtime API and showing some of its most common and practical use cases. The complete HAProxy Runtime API documentation can be found in the HAProxy Management Guide, section 9.3.
If you have a subscription to HAProxy Enterprise, we can provide you with authoritative support, scripts, and modules that will help you make the best use of the Runtime API, including its most advanced features listed among the commands but not specifically elaborated in this blog post.
The Runtime API is evolving along with all other features and improvements that we are adding to HAProxy. One of the planned Runtime API improvements is an HTTP REST interface to complement the existing access methods. Let us know what other features and improvements you would like to see included!
Subscribe to our blog. Get the latest release updates, tutorials, and deep-dives from HAProxy experts.