]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: init: add global setting "fd-hard-limit" to bound system limits
authorWilly Tarreau <w@1wt.eu>
Mon, 25 Apr 2022 16:02:03 +0000 (18:02 +0200)
committerWilly Tarreau <w@1wt.eu>
Mon, 25 Apr 2022 16:04:49 +0000 (18:04 +0200)
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.

doc/configuration.txt
include/haproxy/global-t.h
src/cfgparse-global.c
src/haproxy.c

index c1ba462ec2d1ed0e20f87ad276303f77e737215e..9461b9b904bf55ac95923a81656babbbc16da1aa 100644 (file)
@@ -1003,6 +1003,7 @@ The following keywords are supported in the "global" section :
    - deviceatlas-properties-cookie
    - expose-experimental-directives
    - external-check
+   - fd-hard-limit
    - gid
    - grace
    - group
@@ -1334,6 +1335,26 @@ external-check
   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 @@ uid <number>
 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 @@ maxconn <number>
   "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>.
index 186968d45a0f7dd1f7d162ba4245c47d803a6735..c188cb38cbfcd7463ceb255f1b73f00ccd4090b1 100644 (file)
@@ -123,6 +123,7 @@ struct global {
        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 */
index a9e1b94808fd30af0dbb17bd333075a70fccab73..c9b7d6e0159c344f0997ba64420cd8a71ba0f1d5 100644 (file)
@@ -711,7 +711,21 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
                        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;
index f61a3abf97b2d9a6d6916be534bdb803d36c9b80..ddb23e2c8e6d797b5c5aae24b95ae4526aa4374e 100644 (file)
@@ -1362,6 +1362,9 @@ static int compute_ideal_maxconn()
         *   - 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 @@ static int check_if_maxsock_permitted(int maxsock)
        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 @@ int main(int argc, char **argv)
                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 @@ int main(int argc, char **argv)
                        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);