[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