[lxc-devel] [lxc/master] RFC: Resource Limits

Blub on Github lxc-bot at linuxcontainers.org
Fri Nov 4 11:42:42 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1109 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20161104/bb0c7860/attachment.bin>
-------------- next part --------------
From b83d686ba16afdff778a7a2ad092ca8545b1d1c3 Mon Sep 17 00:00:00 2001
From: Wolfgang Bumiller <w.bumiller at proxmox.com>
Date: Fri, 4 Nov 2016 10:19:07 +0100
Subject: [PATCH 1/3] conf: implement resource limits

Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
 configure.ac      |   2 +-
 src/lxc/conf.c    | 132 ++++++++++++++++++++++++++++++++++++++++++
 src/lxc/conf.h    |  24 ++++++++
 src/lxc/confile.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 326 insertions(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index dcb33ee..7d83382 100644
--- a/configure.ac
+++ b/configure.ac
@@ -617,7 +617,7 @@ AC_CHECK_DECLS([PR_SET_NO_NEW_PRIVS], [], [], [#include <sys/prctl.h>])
 AC_CHECK_DECLS([PR_GET_NO_NEW_PRIVS], [], [], [#include <sys/prctl.h>])
 
 # Check for some headers
-AC_CHECK_HEADERS([sys/signalfd.h pty.h ifaddrs.h sys/capability.h sys/personality.h utmpx.h sys/timerfd.h])
+AC_CHECK_HEADERS([sys/signalfd.h pty.h ifaddrs.h sys/capability.h sys/personality.h utmpx.h sys/timerfd.h sys/resource.h])
 
 # Check for some syscalls functions
 AC_CHECK_FUNCS([setns pivot_root sethostname unshare rand_r confstr faccessat])
diff --git a/src/lxc/conf.c b/src/lxc/conf.c
index b2e0fd9..9f5f978 100644
--- a/src/lxc/conf.c
+++ b/src/lxc/conf.c
@@ -183,6 +183,11 @@ struct caps_opt {
 	int value;
 };
 
+struct limit_opt {
+	char *name;
+	int value;
+};
+
 /*
  * The lxc_conf of the container currently being worked on in an
  * API call
@@ -315,6 +320,57 @@ static struct caps_opt caps_opt[] = {
 static struct caps_opt caps_opt[] = {};
 #endif
 
+static struct limit_opt limit_opt[] = {
+#ifdef RLIMIT_AS
+	{ "as",          RLIMIT_AS          },
+#endif
+#ifdef RLIMIT_CORE
+	{ "core",        RLIMIT_CORE        },
+#endif
+#ifdef RLIMIT_CPU
+	{ "cpu",         RLIMIT_CPU         },
+#endif
+#ifdef RLIMIT_DATA
+	{ "data",        RLIMIT_DATA        },
+#endif
+#ifdef RLIMIT_FSIZE
+	{ "fsize",       RLIMIT_FSIZE       },
+#endif
+#ifdef RLIMIT_LOCKS
+	{ "locks",       RLIMIT_LOCKS       },
+#endif
+#ifdef RLIMIT_MEMLOCK
+	{ "memlock",     RLIMIT_MEMLOCK     },
+#endif
+#ifdef RLIMIT_MSGQUEUE
+	{ "msgqueue",    RLIMIT_MSGQUEUE    },
+#endif
+#ifdef RLIMIT_NICE
+	{ "nice",        RLIMIT_NICE        },
+#endif
+#ifdef RLIMIT_NOFILE
+	{ "nofile",      RLIMIT_NOFILE      },
+#endif
+#ifdef RLIMIT_NPROC
+	{ "nproc",       RLIMIT_NPROC       },
+#endif
+#ifdef RLIMIT_RSS
+	{ "rss",         RLIMIT_RSS         },
+#endif
+#ifdef RLIMIT_RTPRIO
+	{ "rtprio",      RLIMIT_RTPRIO      },
+#endif
+#ifdef RLIMIT_RTTIME
+	{ "rttime",      RLIMIT_RTTIME      },
+#endif
+#ifdef RLIMIT_SIGPENDING
+	{ "sigpending",  RLIMIT_SIGPENDING  },
+#endif
+#ifdef RLIMIT_STACK
+	{ "stack",       RLIMIT_STACK       },
+#endif
+};
+
 static int run_buffer(char *buffer)
 {
 	struct lxc_popen_FILE *f;
@@ -2396,6 +2452,54 @@ static int setup_network(struct lxc_list *network)
 	return 0;
 }
 
+static int parse_resource(const char *res) {
+	char *ptr = NULL;
+	size_t i;
+	int resid = -1;
+
+	for (i = 0; i < sizeof(limit_opt)/sizeof(limit_opt[0]); ++i) {
+		if (strcmp(res, limit_opt[i].name))
+			continue;
+
+		resid = limit_opt[i].value;
+		break;
+	}
+
+	if (resid < 0) {
+		/* try to see if it's numeric, so the user may specify
+		 * resources that the running kernel knows about but
+		 * we don't */
+		errno = 0;
+		resid = strtol(res, &ptr, 10);
+		if (!ptr || *ptr != '\0' || errno != 0)
+			resid = -1;
+	}
+
+	return resid;
+}
+
+static int setup_resource_limits(struct lxc_list *limits) {
+	struct lxc_list *it;
+	struct lxc_limit *lim;
+	int resid;
+
+	lxc_list_for_each(it, limits) {
+		lim = it->elem;
+
+		resid = parse_resource(lim->resource);
+		if (resid < 0) {
+			ERROR("unknown resource %s", lim->resource);
+			return -1;
+		}
+
+		if (setrlimit(resid, &lim->limit) != 0) {
+			ERROR("failed to set limit %s: %s", lim->resource, strerror(errno));
+			return -1;
+		}
+	}
+	return 0;
+}
+
 /* try to move physical nics to the init netns */
 void lxc_restore_phys_nics_to_netns(int netnsfd, struct lxc_conf *conf)
 {
@@ -2486,6 +2590,7 @@ struct lxc_conf *lxc_conf_init(void)
 	lxc_list_init(&new->includes);
 	lxc_list_init(&new->aliens);
 	lxc_list_init(&new->environment);
+	lxc_list_init(&new->limits);
 	for (i=0; i<NUM_LXC_HOOKS; i++)
 		lxc_list_init(&new->hooks[i]);
 	lxc_list_init(&new->groups);
@@ -3828,6 +3933,11 @@ int lxc_setup(struct lxc_handler *handler)
 		return -1;
 	}
 
+	if (!lxc_list_empty(&lxc_conf->limits) && setup_resource_limits(&lxc_conf->limits)) {
+		ERROR("failed to setup resource limits for '%s'", name);
+		return -1;
+	}
+
 	if (!lxc_list_empty(&lxc_conf->keepcaps)) {
 		if (!lxc_list_empty(&lxc_conf->caps)) {
 			ERROR("Simultaneously requested dropping and keeping caps");
@@ -4037,6 +4147,27 @@ int lxc_clear_cgroups(struct lxc_conf *c, const char *key)
 	return 0;
 }
 
+int lxc_clear_limits(struct lxc_conf *c, const char *key)
+{
+	struct lxc_list *it, *next;
+	bool all = false;
+	const char *k = key + 10;
+
+	if (strcmp(key, "lxc.limit") == 0)
+		all = true;
+
+	lxc_list_for_each_safe(it, &c->limits, next) {
+		struct lxc_limit *lim = it->elem;
+		if (!all && strcmp(lim->resource, k) != 0)
+			continue;
+		lxc_list_del(it);
+		free(lim->resource);
+		free(lim);
+		free(it);
+	}
+	return 0;
+}
+
 int lxc_clear_groups(struct lxc_conf *c)
 {
 	struct lxc_list *it,*next;
@@ -4179,6 +4310,7 @@ void lxc_conf_free(struct lxc_conf *conf)
 	lxc_clear_includes(conf);
 	lxc_clear_aliens(conf);
 	lxc_clear_environment(conf);
+	lxc_clear_limits(conf, "lxc.limit");
 	free(conf);
 }
 
diff --git a/src/lxc/conf.h b/src/lxc/conf.h
index 842e4dc..a01617c 100644
--- a/src/lxc/conf.h
+++ b/src/lxc/conf.h
@@ -30,6 +30,9 @@
 #include <net/if.h>
 #include <sys/param.h>
 #include <sys/types.h>
+#if HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
 #include <stdbool.h>
 
 #include "list.h"
@@ -149,6 +152,23 @@ struct lxc_cgroup {
 	char *value;
 };
 
+#if !HAVE_SYS_RESOURCE_H
+# define RLIM_INFINITY ((unsigned long)-1)
+struct rlimit {
+	unsigned long rlim_cur;
+	unsigned long rlim_max;
+};
+#endif
+/*
+ * Defines a structure to configure resource limits to set via setrlimit().
+ * @resource : the resource name in lowercase without the RLIMIT_ prefix
+ * @limit    : the limit to set
+ */
+struct lxc_limit {
+	char *resource;
+	struct rlimit limit;
+};
+
 enum idtype {
 	ID_TYPE_UID,
 	ID_TYPE_GID
@@ -385,6 +405,9 @@ struct lxc_conf {
 
 	/* Whether PR_SET_NO_NEW_PRIVS will be set for the container. */
 	bool no_new_privs;
+
+	/* RLIMIT_* limits */
+	struct lxc_list limits;
 };
 
 #ifdef HAVE_TLS
@@ -428,6 +451,7 @@ extern int lxc_clear_hooks(struct lxc_conf *c, const char *key);
 extern int lxc_clear_idmaps(struct lxc_conf *c);
 extern int lxc_clear_groups(struct lxc_conf *c);
 extern int lxc_clear_environment(struct lxc_conf *c);
+extern int lxc_clear_limits(struct lxc_conf *c, const char *key);
 extern int lxc_delete_autodev(struct lxc_handler *handler);
 
 extern int do_rootfs_setup(struct lxc_conf *conf, const char *name,
diff --git a/src/lxc/confile.c b/src/lxc/confile.c
index 8f370f6..c7870e3 100644
--- a/src/lxc/confile.c
+++ b/src/lxc/confile.c
@@ -115,6 +115,7 @@ static int config_init_uid(const char *, const char *, struct lxc_conf *);
 static int config_init_gid(const char *, const char *, struct lxc_conf *);
 static int config_ephemeral(const char *, const char *, struct lxc_conf *);
 static int config_no_new_privs(const char *, const char *, struct lxc_conf *);
+static int config_limit(const char *, const char *, struct lxc_conf *);
 
 static struct lxc_config_t config[] = {
 
@@ -189,6 +190,7 @@ static struct lxc_config_t config[] = {
 	{ "lxc.ephemeral",            config_ephemeral            },
 	{ "lxc.syslog",               config_syslog               },
 	{ "lxc.no_new_privs",	      config_no_new_privs	  },
+	{ "lxc.limit",                config_limit                },
 };
 
 struct signame {
@@ -1468,6 +1470,110 @@ static int config_cgroup(const char *key, const char *value,
 	return -1;
 }
 
+static bool parse_limit_value(const char **value, unsigned long *res) {
+	char *endptr = NULL;
+
+	if (strncmp(*value, "unlimited", sizeof("unlimited")-1) == 0) {
+		*res = RLIM_INFINITY;
+		*value += sizeof("unlimited")-1;
+		return true;
+	}
+
+	errno = 0;
+	*res = strtoul(*value, &endptr, 10);
+	if (errno || !endptr)
+		return false;
+	*value = endptr;
+
+	return true;
+}
+
+static int config_limit(const char *key, const char *value,
+			 struct lxc_conf *lxc_conf)
+{
+	struct lxc_list *limlist = NULL;
+	struct lxc_limit *limelem = NULL;
+	struct lxc_list *iter;
+	struct rlimit limit;
+	unsigned long limit_value;
+
+	if (!value || strlen(value) == 0)
+		return lxc_clear_limits(lxc_conf, key);
+
+	if (strncmp(key, "lxc.limit.", sizeof("lxc.limit.")-1) != 0)
+		return -1;
+
+	key += sizeof("lxc.limit.")-1;
+
+	/* soft limit comes first in the value */
+	if (!parse_limit_value(&value, &limit_value))
+		return -1;
+	limit.rlim_cur = limit_value;
+
+	/* skip spaces and a colon */
+	while (isspace(*value))
+		++value;
+	if (*value == ':')
+		++value;
+	else if (*value) /* any other character is an error here */
+		return -1;
+	while (isspace(*value))
+		++value;
+
+	/* optional hard limit */
+	if (*value) {
+		if (!parse_limit_value(&value, &limit_value))
+			return -1;
+		limit.rlim_max = limit_value;
+		/* check for trailing garbage */
+		while (isspace(*value))
+			++value;
+		if (*value)
+			return -1;
+	} else {
+		/* a single value sets both hard and soft limit */
+		limit.rlim_max = limit.rlim_cur;
+	}
+
+	/* find existing list element */
+	lxc_list_for_each(iter, &lxc_conf->limits) {
+		limelem = iter->elem;
+		if (!strcmp(key, limelem->resource)) {
+			limelem->limit = limit;
+			return 0;
+		}
+	}
+
+	/* allocate list element */
+	limlist = malloc(sizeof(*limlist));
+	if (!limlist)
+		goto out;
+	
+	limelem = malloc(sizeof(*limelem));
+	if (!limelem)
+		goto out;
+	memset(limelem, 0, sizeof(*limelem));
+
+	limelem->resource = strdup(key);
+	if (!limelem->resource)
+		goto out;
+	limelem->limit = limit;
+
+	limlist->elem = limelem;
+
+	lxc_list_add_tail(&lxc_conf->limits, limlist);
+
+	return 0;
+
+out:
+	free(limlist);
+	if (limelem) {
+		free(limelem->resource);
+		free(limelem);
+	}
+	return -1;
+}
+
 static int config_idmap(const char *key, const char *value, struct lxc_conf *lxc_conf)
 {
 	char *token = "lxc.id_map";
@@ -2201,6 +2307,63 @@ static int lxc_get_cgroup_entry(struct lxc_conf *c, char *retv, int inlen,
 	return fulllen;
 }
 
+/*
+ * If you ask for a specific value, i.e. lxc.limit.nofile, then just the value
+ * will be printed. If you ask for 'lxc.limit', then all limit entries will be
+ * printed, in 'lxc.limit.resource = value' format.
+ */
+static int lxc_get_limit_entry(struct lxc_conf *c, char *retv, int inlen,
+				const char *key)
+{
+	int fulllen = 0, len;
+	int all = 0;
+	struct lxc_list *it;
+
+	if (!retv)
+		inlen = 0;
+	else
+		memset(retv, 0, inlen);
+
+	if (strcmp(key, "all") == 0)
+		all = 1;
+
+	lxc_list_for_each(it, &c->limits) {
+		char buf[64]; /* 2 64 bit integers or the word 'unlimited' twice */
+		int partlen;
+		struct lxc_limit *lim = it->elem;
+
+		if (lim->limit.rlim_cur == lim->limit.rlim_max) {
+			if (lim->limit.rlim_cur == RLIM_INFINITY) {
+				memcpy(buf, "unlimited", sizeof("unlimited"));
+				partlen = sizeof("unlimited")-1;
+			} else {
+				partlen = sprintf(buf, "%lu", lim->limit.rlim_cur);
+			}
+		} else {
+			if (lim->limit.rlim_cur == RLIM_INFINITY) {
+				memcpy(buf, "unlimited:", sizeof("unlimited:")-1);
+				partlen = sizeof("unlimited:")-1;
+			} else {
+				partlen = sprintf(buf, "%lu:", lim->limit.rlim_cur);
+			}
+
+			if (lim->limit.rlim_max == RLIM_INFINITY) {
+				memcpy(buf+partlen, "unlimited", sizeof("unlimited"));
+			} else {
+				sprintf(buf+partlen, "%lu", lim->limit.rlim_max);
+			}
+		}
+
+		if (all) {
+			strprint(retv, inlen, "lxc.limit.%s = %s\n", lim->resource, buf);
+		} else if (strcmp(lim->resource, key) == 0) {
+			strprint(retv, inlen, "%s", buf);
+		}
+	}
+
+	return fulllen;
+}
+
 static int lxc_get_item_hooks(struct lxc_conf *c, char *retv, int inlen,
 			      const char *key)
 {
@@ -2566,6 +2729,10 @@ int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv,
 		v = c->syslog;
 	else if (strcmp(key, "lxc.no_new_privs") == 0)
 		return lxc_get_conf_int(c, retv, inlen, c->no_new_privs);
+	else if (strcmp(key, "lxc.limit") == 0) // all limits
+		return lxc_get_limit_entry(c, retv, inlen, "all");
+	else if (strncmp(key, "lxc.limit.", 10) == 0) // specific limit
+		return lxc_get_limit_entry(c, retv, inlen, key + 10);
 	else return -1;
 
 	if (!v)
@@ -2599,6 +2766,8 @@ int lxc_clear_config_item(struct lxc_conf *c, const char *key)
 		return lxc_clear_environment(c);
 	else if (strncmp(key, "lxc.id_map", 10) == 0)
 		return lxc_clear_idmaps(c);
+	else if (strncmp(key, "lxc.limit", 9) == 0)
+		return lxc_clear_limits(c, key);
 	return -1;
 }
 

From 8aea67e2dcc8edccd6e2a86edda77715a2c82bd7 Mon Sep 17 00:00:00 2001
From: Wolfgang Bumiller <w.bumiller at proxmox.com>
Date: Fri, 4 Nov 2016 12:03:28 +0100
Subject: [PATCH 2/3] doc: add lxc.limit to lxc.container.conf

Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
 doc/lxc.container.conf.sgml.in | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/doc/lxc.container.conf.sgml.in b/doc/lxc.container.conf.sgml.in
index fcccd8b..43f3139 100644
--- a/doc/lxc.container.conf.sgml.in
+++ b/doc/lxc.container.conf.sgml.in
@@ -1184,6 +1184,40 @@ proc proc proc nodev,noexec,nosuid 0 0
     </refsect2>
 
     <refsect2>
+      <title>Resource limits</title>
+      <para>
+        The soft and hard resource limits for the container can be changed.
+        Unprivileged containers can only lower them. Resources which are not
+        explicitly specified will be inherited.
+      </para>
+      <variablelist>
+        <varlistentry>
+          <term>
+            <option>lxc.limit.[limit name]</option>
+          </term>
+          <listitem>
+            <para>
+              Specify the resource limit to be set. A limit is specified as two
+              colon separated values which are either numeric or the word
+              'unlimited'. A single value can be used as a shortcut to set both
+              soft and hard limit to the same value. The permitted names the
+              "RLIMIT_" resource names in lowercase without the "RLIMIT_"
+              prefix, eg. RLIMIT_NOFILE should be specified as "nofile". See
+              <citerefentry>
+                <refentrytitle><command>setrlimit</command></refentrytitle>
+                <manvolnum>2</manvolnum>
+              </citerefentry>.
+              If used with no value, lxc will clear the resource limit
+              specified up to this point. A resource with no explicitly
+              configured limitation will be inherited from the process starting
+              up the container.
+            </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2>
       <title>Apparmor profile</title>
       <para>
         If lxc was compiled and installed with apparmor support, and the host

From 3a82465d95179b7240b364d7273f3761d2e180d0 Mon Sep 17 00:00:00 2001
From: Wolfgang Bumiller <w.bumiller at proxmox.com>
Date: Fri, 4 Nov 2016 11:45:47 +0100
Subject: [PATCH 3/3] test: resource limit config entries

Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
 src/tests/get_item.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/src/tests/get_item.c b/src/tests/get_item.c
index eb2274e..cb4ba42 100644
--- a/src/tests/get_item.c
+++ b/src/tests/get_item.c
@@ -174,6 +174,70 @@ int main(int argc, char *argv[])
 	}
 	printf("lxc.mount.entry returned %d %s\n", ret, v2);
 
+	ret = c->get_config_item(c, "lxc.limit", v3, 2047);
+	if (ret != 0) {
+		fprintf(stderr, "%d: get_config_item(limit) returned %d\n", __LINE__, ret);
+		goto out;
+	}
+
+	if (!c->set_config_item(c, "lxc.limit.nofile", "1234:unlimited")) {
+		fprintf(stderr, "%d: failed to set limit.nofile\n", __LINE__);
+		goto out;
+	}
+	ret = c->get_config_item(c, "lxc.limit.nofile", v2, 255);
+	if (ret < 0) {
+		fprintf(stderr, "%d: get_config_item(lxc.limit.nofile) returned %d\n", __LINE__, ret);
+		goto out;
+	}
+	if (strcmp(v2, "1234:unlimited")) {
+		fprintf(stderr, "%d: lxc.limit.nofile returned wrong value: %d %s not 14 1234:unlimited\n", __LINE__, ret, v2);
+		goto out;
+	}
+	printf("lxc.limit.nofile returned %d %s\n", ret, v2);
+
+	if (!c->set_config_item(c, "lxc.limit.stack", "unlimited")) {
+		fprintf(stderr, "%d: failed to set limit.stack\n", __LINE__);
+		goto out;
+	}
+	ret = c->get_config_item(c, "lxc.limit.stack", v2, 255);
+	if (ret < 0) {
+		fprintf(stderr, "%d: get_config_item(lxc.limit.stack) returned %d\n", __LINE__, ret);
+		goto out;
+	}
+	if (strcmp(v2, "unlimited")) {
+		fprintf(stderr, "%d: lxc.limit.stack returned wrong value: %d %s not 9 unlimited\n", __LINE__, ret, v2);
+		goto out;
+	}
+	printf("lxc.limit.stack returned %d %s\n", ret, v2);
+
+#define LIMIT_STACK "lxc.limit.stack = unlimited\n"
+#define ALL_LIMITS "lxc.limit.nofile = 1234:unlimited\n" LIMIT_STACK
+	ret = c->get_config_item(c, "lxc.limit", v3, 2047);
+	if (ret != sizeof(ALL_LIMITS)-1) {
+		fprintf(stderr, "%d: get_config_item(limit) returned %d\n", __LINE__, ret);
+		goto out;
+	}
+	if (strcmp(v3, ALL_LIMITS)) {
+		fprintf(stderr, "%d: lxc.limit returned wrong value: %d %s not %d %s\n", __LINE__, ret, v3, (int)sizeof(ALL_LIMITS)-1, ALL_LIMITS);
+		goto out;
+	}
+	printf("lxc.limit returned %d %s\n", ret, v3);
+
+	if (!c->clear_config_item(c, "lxc.limit.nofile")) {
+		fprintf(stderr, "%d: failed clearing limit.nofile\n", __LINE__);
+		goto out;
+	}
+	ret = c->get_config_item(c, "lxc.limit", v3, 2047);
+	if (ret != sizeof(LIMIT_STACK)-1) {
+		fprintf(stderr, "%d: get_config_item(limit) returned %d\n", __LINE__, ret);
+		goto out;
+	}
+	if (strcmp(v3, LIMIT_STACK)) {
+		fprintf(stderr, "%d: lxc.limit returned wrong value: %d %s not %d %s\n", __LINE__, ret, v3, (int)sizeof(LIMIT_STACK)-1, LIMIT_STACK);
+		goto out;
+	}
+	printf("lxc.limit returned %d %s\n", ret, v3);
+
 	if (!c->set_config_item(c, "lxc.aa_profile", "unconfined")) {
 		fprintf(stderr, "%d: failed to set aa_profile\n", __LINE__);
 		goto out;


More information about the lxc-devel mailing list