MINOR: init: add global setting "fd-hard-limit" to bound system limits
On some systems, the hard limit for ulimit -n may be huge, in the order
of 1 billion, and using this to automatically compute maxconn doesn't
work as it requires way too much memory. Users tend to hard-code maxconn
but that's not convenient to manage deployments on heterogenous systems,
nor when porting configs to developers' machines. The ulimit-n parameter
doesn't work either because it forces the limit. What most users seem to
want (and it makes sense) is to respect the system imposed limits up to
a certain value and cap this value. This is exactly what fd-hard-limit
does.
This addresses github issue #1622.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index c1ba462..9461b9b 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1003,6 +1003,7 @@
- deviceatlas-properties-cookie
- expose-experimental-directives
- external-check
+ - fd-hard-limit
- gid
- grace
- group
@@ -1334,6 +1335,26 @@
See "option external-check", and "insecure-fork-wanted", and
"insecure-setuid-wanted".
+fd-hard-limit <number>
+ Sets an upper bound to the maximum number of file descriptors that the
+ process will use, regardless of system limits. While "ulimit-n" and "maxconn"
+ may be used to enforce a value, when they are not set, the process will be
+ limited to the hard limit of the RLIMIT_NOFILE setting as reported by
+ "ulimit -n -H". But some modern operating systems are now allowing extremely
+ large values here (in the order of 1 billion), which will consume way too
+ much RAM for regular usage. The fd-hard-limit setting is provided to enforce
+ a possibly lower bound to this limit. This means that it will always respect
+ the system-imposed limits when they are below <number> but the specified
+ value will be used if system-imposed limits are higher. In the example below,
+ no other setting is specified and the maxconn value will automatically adapt
+ to the lower of "fd-hard-limit" and the system-imposed limit:
+
+ global
+ # use as many FDs as possible but no more than 50000
+ fd-hard-limit 50000
+
+ See also: ulimit-n, maxconn
+
gid <number>
Changes the process's group ID to <number>. It is recommended that the group
ID is dedicated to HAProxy or to a small set of similar daemons. HAProxy must
@@ -2121,12 +2142,15 @@
ulimit-n <number>
Sets the maximum number of per-process file-descriptors to <number>. By
default, it is automatically computed, so it is recommended not to use this
- option.
+ option. If the intent is only to limit the number of file descriptors, better
+ use "fd-hard-limit" instead.
Note that the dynamic servers are not taken into account in this automatic
resource calculation. If using a large number of them, it may be needed to
manually specify this value.
+ See also: fd-hard-limit, maxconn
+
unix-bind [ prefix <prefix> ] [ mode <mode> ] [ user <user> ] [ uid <uid> ]
[ group <group> ] [ gid <gid> ]
@@ -2318,7 +2342,9 @@
"ulimit -n" command, possibly reduced to a lower value if a memory limit
is enforced, based on the buffer size, memory allocated to compression, SSL
cache size, and use or not of SSL and the associated maxsslconn (which can
- also be automatic).
+ also be automatic). In any case, the fd-hard-limit applies if set.
+
+ See also: fd-hard-limit, ulimit-n
maxconnrate <number>
Sets the maximum per-process number of connections per second to <number>.
diff --git a/include/haproxy/global-t.h b/include/haproxy/global-t.h
index 186968d..c188cb3 100644
--- a/include/haproxy/global-t.h
+++ b/include/haproxy/global-t.h
@@ -123,6 +123,7 @@
char *pidfile;
char *node, *desc; /* node name & description */
int localpeer_cmdline; /* whether or not the commandline "-L" was set */
+ int fd_hard_limit; /* hard limit on ulimit-n : 0=unset */
struct buffer log_tag; /* name for syslog */
struct list logsrvs;
char *log_send_hostname; /* set hostname in syslog header */
diff --git a/src/cfgparse-global.c b/src/cfgparse-global.c
index a9e1b94..c9b7d6e 100644
--- a/src/cfgparse-global.c
+++ b/src/cfgparse-global.c
@@ -711,7 +711,21 @@
goto out;
}
}
-
+ else if (strcmp(args[0], "fd-hard-limit") == 0) {
+ if (alertif_too_many_args(1, file, linenum, args, &err_code))
+ goto out;
+ if (global.fd_hard_limit != 0) {
+ ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]);
+ err_code |= ERR_ALERT;
+ goto out;
+ }
+ if (*(args[1]) == 0) {
+ ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ global.fd_hard_limit = atol(args[1]);
+ }
else if (strcmp(args[0], "ulimit-n") == 0) {
if (alertif_too_many_args(1, file, linenum, args, &err_code))
goto out;
diff --git a/src/haproxy.c b/src/haproxy.c
index f61a3ab..ddb23e2 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -1362,6 +1362,9 @@
* - two FDs per connection
*/
+ if (global.fd_hard_limit && remain > global.fd_hard_limit)
+ remain = global.fd_hard_limit;
+
/* subtract listeners and checks */
remain -= global.maxsock;
@@ -1439,6 +1442,9 @@
struct rlimit orig_limit, test_limit;
int ret;
+ if (global.fd_hard_limit && maxsock > global.fd_hard_limit)
+ return 0;
+
if (getrlimit(RLIMIT_NOFILE, &orig_limit) != 0)
return 1;
@@ -3049,8 +3055,12 @@
limit.rlim_cur = global.rlimit_nofile;
limit.rlim_max = MAX(rlim_fd_max_at_boot, limit.rlim_cur);
- if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
+ if ((global.fd_hard_limit && limit.rlim_cur > global.fd_hard_limit) ||
+ setrlimit(RLIMIT_NOFILE, &limit) == -1) {
getrlimit(RLIMIT_NOFILE, &limit);
+ if (global.fd_hard_limit && limit.rlim_cur > global.fd_hard_limit)
+ limit.rlim_cur = global.fd_hard_limit;
+
if (global.tune.options & GTUNE_STRICT_LIMITS) {
ha_alert("[%s.main()] Cannot raise FD limit to %d, limit is %d.\n",
argv[0], global.rlimit_nofile, (int)limit.rlim_cur);
@@ -3059,6 +3069,9 @@
else {
/* try to set it to the max possible at least */
limit.rlim_cur = limit.rlim_max;
+ if (global.fd_hard_limit && limit.rlim_cur > global.fd_hard_limit)
+ limit.rlim_cur = global.fd_hard_limit;
+
if (setrlimit(RLIMIT_NOFILE, &limit) != -1)
getrlimit(RLIMIT_NOFILE, &limit);