Rate limits in HAProxy are based on stick-tables. The concept of stick-tables is explained in this blog article. It covers all relevant parts and gives a general idea on how one could rate limit based on certain attributes.
HAProxy-boshrelease can be configured to enforce these rate limits based on requests and connections per second per IP.
See also jobs/haproxy/spec:
There are two rate limit configuration groups:
connections_rate_limitfor connection based rate limiting on OSI layer 4/TCPrequests_rate_limitfor request based rate limiting on OSI layer 7/HTTP
Both groups contain roughly the same attributes:
requests(forrequests_rate_limit) andconnections(forconnections_rate_limit): the number of requests/connections allowed within a time window (seewindow_size) before further incoming requests/connections are denied/blockedwindow_size: Window size for counting connectionstable_size: Size of the stick table in which the IPs and counters are stored.block: Whether or not to block connections. Ifblockis disabled (or not provided), incoming requests/connections will still be tracked in the respective stick-tables, but will not be denied.
Once a rate limit is reached, haproxy-boshrelease will no longer proxy incoming requests from the rate-limited client IP to a backend. Depending on the type of rate limiting, HAProxy will respond with one of the following:
HAProxy responds to the client with HTTP Status Code: 429: Too Many Requests.
The TCP connection will be rejected. This would for example show up as Empty reply from server for a curl-client.
This will not result in a log statement on the HAProxy side, which can make tracing issues more difficult.
Note: If both rate-limits are reached simultaneously (e.g. if they are configured identically and every incoming HTTP request uses a new TCP connection), connection based rate-limiting will come into effect first, resulting in a dropped TCP connection.
Note: The following examples assume only an
http-infrontend is configured; anhttps-infrontend would behave identically.
config:
# [...]
requests_rate_limit:
window_size: 10s
table_size: 1mbackend st_http_req_rate
stick-table type ipv6 size 1m expire 10s store http_req_rate(10s)
# [...]
frontend http-in
http-request track-sc1 src table st_http_req_rateconfig:
# [...]
requests_rate_limit:
requests: 10
window_size: 10s
table_size: 1m
block: truebackend st_http_req_rate
stick-table type ipv6 size 1m expire 10s store http_req_rate(10s)
# [...]
frontend http-in
http-request track-sc1 src table st_http_req_rate
http-request deny status 429 if { sc_http_req_rate(1) gt 10 }config:
# [...]
requests_rate_limit:
requests: 10
window_size: 10s
table_size: 1m
block: true
connections_rate_limit:
connections: 10
window_size: 10s
table_size: 1m
block: truebackend st_http_req_rate
stick-table type ipv6 size 1m expire 10s store http_req_rate(10s)
backend st_tcp_conn_rate
stick-table type ipv6 size 1m expire 10s store conn_rate(10s)
# [...]
frontend http-in
# [...]
http-request track-sc1 src table st_http_req_rate
http-request deny status 429 if { sc_http_req_rate(1) gt 10 }
tcp-request content track-sc0 src table st_tcp_conn_rate
tcp-request connection reject if { sc_conn_rate(0) gt 10}To get more insight into what is going on inside HAProxy regarding its rate limits, you can query the stats socket to get the raw table data:
$ echo "show table st_http_req_rate" | socat /var/vcap/sys/run/haproxy/stats.sock -
# table: st_http_req_rate, type: ip, size:10485760, used:1
0x56495f3dc3d0: key=172.18.0.1 use=0 exp=7618 http_req_rate(10000)=10Note: You will likely need
sudopermission to run socat.