[pve-devel] [PATCH container] add support for resource limits

Wolfgang Bumiller w.bumiller at proxmox.com
Tue Jun 13 14:44:30 CEST 2017


This whitelists lxc.limit.* and adds an 'rlimit' property
string exposing only a limited set of them, because not all
of them make sense, and some are simply inconvenient to
use. For instance some are accounted "per real user id",
for example setting RLIMIT_NPROC means the container can
only spawn new processes if the number of processes of all
containers of the current "real user" do not sum up to more
than the specified limit. This only makes sense in
combination with per-container user-id maps, so we'll leave
that to people who know what they're doing by still allowing
the raw "lxc.limit.nproc" value to be set.

Example:
  rlimits: nofile=4096:8192,core=5000000,stack=unlimited
---
This has been asked for a couple of times. I'm not sure how much sense
a GUI side for this makes. I think the only thing people have really
run into until now is RLIMIT_NOFILE.

 src/PVE/LXC.pm        |   7 ++-
 src/PVE/LXC/Config.pm | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 122 insertions(+), 1 deletion(-)

diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index 32b0318..41a2cf2 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -462,7 +462,12 @@ sub update_lxc_config {
 	}
 	$raw .= "lxc.cgroup.cpuset.cpus = ".$cpuset->short_string()."\n";
     }
-    
+
+    my $limits = PVE::LXC::Config->parse_rlimits($conf->{rlimits});
+    foreach my $k (sort keys %$limits) {
+	$raw .= "lxc.limit.$k = $limits->{$k}\n";
+    }
+
     File::Path::mkpath("$dir/rootfs");
 
     PVE::Tools::file_set_contents("$dir/config", $raw);
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index 4ac5c95..c9d0556 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -272,6 +272,92 @@ PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
     maxLength => 40,
 });
 
+PVE::JSONSchema::register_format('rlimit', \&verify_rlimit);
+my $RLIMIT_RE = qr/^(\d+|unlimited)(?::(\d+|unlimited))?$/;
+sub verify_rlimit {
+    my ($text, $noerr) = @_;
+    if ($text !~ $RLIMIT_RE) {
+	return undef if $noerr;
+	die "invalid resource limit specification: $text\n";
+    }
+    my ($soft, $hard) = ($1, $2);
+    if (defined($hard) && $hard ne 'unlimited' && $hard < $soft) {
+	die "hard limit must not be lower than the soft limit ($hard > $soft)\n";
+    }
+    return $text;
+}
+
+# They all default to system-inherited limits, which depends on whether the
+# containers were started via pvedaemon or 'pct start'. (In the latter case
+# they inherit limits from the shell.)
+my $rlimit_format_desc = 'soft[:hard]';
+my $rlimits_desc = {
+    as => {
+	type => 'string', format => 'rlimit', optional => 1,
+	format_description => $rlimit_format_desc,
+	description => "Maximum size of a process' virtual memory"
+	              .' (address space) in bytes.',
+    },
+    core => {
+	type => 'string', format => 'rlimit', optional => 1,
+	format_description => $rlimit_format_desc,
+	description => 'Maximum size of a core dump. When 0 no core dump files'
+	              .' are created. Otherwise they are truncated to this'
+		      .' size.',
+    },
+    #cpu => Really makes no sense for containers
+    data => {
+	type => 'string', format => 'rlimit', optional => 1,
+	format_description => $rlimit_format_desc,
+	description => "Maximum size of a process' data segment (initialized,"
+	              .' uninitialized and heap).',
+    },
+    fsize => { # Really something that should be left up to the container...
+	type => 'string', format => 'rlimit', optional => 1,
+	format_description => $rlimit_format_desc,
+	description => 'Maximum size a process is allowed to extend a file to.',
+    },
+    locks => { # Also something you don't want to change usually.
+	type => 'string', format => 'rlimit', optional => 1,
+	format_description => $rlimit_format_desc,
+	description => 'Limits on the number of flock() locks and fcntl()'
+	              .' locks of processes.',
+    },
+    memlock => {
+	type => 'string', format => 'rlimit', optional => 1,
+	format_description => $rlimit_format_desc,
+	description => 'Limit the amount of memory that may be locked into RAM'
+	              .' (not swappable).',
+    },
+    # msgqueue => Weird for containers
+    nice => {
+	type => 'string', format => 'rlimit', optional => 1,
+	format_description => $rlimit_format_desc,
+	description => 'Specifies the maximum allowed nice level.'
+	              .' In this case the niceness is inverted and mapped to'
+		      .' the range 1 to 40, with 1 being the equivalent of'
+		      .' niceness 19, 20 being the equivalent of 0 and 40'
+		      .' being the equivalent of -20 with regards to the'
+		      .' nice(1) command line utility.',
+    },
+    nofile => {
+	type => 'string', format => 'rlimit', optional => 1,
+	format_description => $rlimit_format_desc,
+	description => 'Maximum number of open file descriptors per process.'
+	              .' This cannot be set to "unlimited".',
+    },
+    # nproc => This is per real user, use cgroup.pids instead.
+    # rss => Has no effect according to the manpage?
+    # rtprio => Do we need this?
+    # rttime => Or this?
+    # sigpending => Do we need this? This, too, is per real-user (like nproc).
+    stack => {
+	type => 'string', format => 'rlimit', optional => 1,
+	format_description => $rlimit_format_desc,
+	description => "Maximum size of a process' stack in bytes.",
+    },
+};
+
 my $confdesc = {
     lock => {
 	optional => 1,
@@ -409,6 +495,11 @@ my $confdesc = {
 	description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
 	default => 0,
     },
+    rlimits => {
+	optional => 1,
+	type => 'string', format => $rlimits_desc,
+	description => "Set container resource limits.",
+    },
 };
 
 my $valid_lxc_conf_keys = {
@@ -969,6 +1060,10 @@ sub update_pct_config {
 	} elsif ($opt eq 'ostype') {
 	    next if $hotplug_error->($opt);
 	    $conf->{$opt} = $value;
+	} elsif ($opt eq 'rlimits') {
+	    next if $hotplug_error->($opt);
+	    my $limits = PVE::LXC::Config->parse_rlimits($value); # verify
+	    $conf->{$opt} = PVE::LXC::Config->print_rlimits($limits);
 	} else {
 	    die "implement me: $opt";
 	}
@@ -1120,6 +1215,27 @@ sub parse_lxc_network {
     return $res;
 }
 
+sub print_rlimits {
+    my ($class, $data) = @_;
+    foreach my $k (keys %$data) {
+	# 'nofile' cannot be set to unlimited
+	next if $k ne 'nofile';
+
+	my $value = $data->{$k};
+	my ($soft, $hard) = $value =~ $RLIMIT_RE;
+	$hard //= $soft;
+	die "rlimit '$k' cannot be set to unlimited\n"
+	    if $soft eq 'unlimited' || $hard eq 'unlimited';
+    }
+    return PVE::JSONSchema::print_property_string($data, $rlimits_desc);
+}
+
+sub parse_rlimits {
+    my ($class, $data) = @_;
+    return {} if !$data;
+    return PVE::JSONSchema::parse_property_string($rlimits_desc, $data);
+}
+
 sub option_exists {
     my ($class, $name) = @_;
 
-- 
2.11.0





More information about the pve-devel mailing list