10

There is similar question: Cgroups, limit memory per user, but the solution doesn't work in "modern" systems, where cgroups hierarchy is managed by systemd.

Straightforward solution — templating user-UID.slice — won't work, because it is not supported, see https://github.com/systemd/systemd/issues/2556.

Is there any way to achieve the desired effect — manage CPU and/or memory resources on a per-user basis?

UPD: I'll keep my solution for the sake of history, but systemctl set-property should be called at login time, using pam_exec, see https://github.com/hashbang/shell-etc/pull/183. In this approach, there is no time window between the user's login and setting of limits.

My solution. Interface org.freedesktop.login1.Manage of /org/freedesktop/login1 object emits UserNew(u uid, o object_path) signal. I've written a simple daemon which listens to the signal and every time it is emitted set CPUAccounting=true for just-logged-in-user's slice.

intelfx
  • 151
  • 1
  • 1
  • 11
  • I'm not very fond on cgroups, I have sucesfully configured with cgconfig/crules, but is not the right way for per-user limits. It's not clear to me what I need to install and edit (limits.sh ?) in Ubuntu 20, in order to implement the systemctl solution. Do you have any tip/link/wlakthru, can you elaborate? Regards – Fabiano Tarlao Apr 27 '21 at 09:30

5 Answers5

5

Starting with systemd v239, you can use drop-ins https://github.com/systemd/systemd/commit/5396624506e155c4bc10c0ee65b939600860ab67

# mkdir -p /etc/systemd/system/user-.slice.d
# cat > /etc/systemd/system/user-.slice.d/50-memory.conf << EOF
[Slice]
MemoryMax=1G
EOF
# systemctl daemon-reload
ValdikSS
  • 91
  • 1
  • 1
  • on my Ubuntu 20 server, what should I need to install in order to this to work well? Shoudl I also activate CPU accounting somewhere? I tried by simply creating this file (and perdormed daemon-reload) but my user is still able to execute tress-ng --vm-bytes 5000000000 -m 1 with no errors... I think I miss something in the way sustemd or cgroup work. Any tip is appreciated – Fabiano Tarlao Apr 27 '21 at 10:21
  • Perhaps It's my fault, it is working, stress-ng respects the max amount of memory I set. (I expected an error) – Fabiano Tarlao Apr 27 '21 at 10:28
  • Another sub-question, I configured user-slice.d as suggested (for all user) and forced a different cgroup setting for user-1000.slide.d (more resources), it seems to work well. It looks like specific cgroup settings for user overrides more general user-* configs. Is this right? Or may there be subtle problems that my tests have not highlighted? – Fabiano Tarlao May 06 '21 at 09:47
4

UPD: I'll keep my solution for the sake of history, but systemctl set-property should be called at login time, using pam_exec, see https://github.com/hashbang/shell-etc/pull/183. In this approach, there is no time window between the user's login and setting of limits.

Old solution

Here is a very simple script which does the job

#!/bin/bash

STATE=1 # 1 -- waiting for signal; 2 -- reading UID

dbus-monitor --system "interface=org.freedesktop.login1.Manager,member=UserNew" |
while read line
do
    case $STATE in
    1) [[ $line =~ member=UserNew ]] && STATE=2 ;;
    2) read dbus_type ID <<< $line
       systemctl set-property user-$ID.slice CPUAccounting=true
       STATE=1
    ;;
    esac
done

It can be easily extended to support per-user memory limits.

Tested it on a VM with 2 CPUs and 2 users. The first user run dd if=/dev/zero of=/dev/null | dd if=/dev/zero of=/dev/null command and the second one run only one instance of dd. Without this script running, each instance of dd used around 70% of CPU.

Then I started the script, relogged users, and starded dd commands again. This time two dd processes of the first user took only 50% of CPU each and the process of the second user took 100% of CPU. Also, systemd-cgtop showed, that /user.slice/user-UID1.slice and /user.slice/user-UID2.slice take 100% of CPU time each, but the first slice has 6 tasks and the second one only 5 tasks.

When I kill dd task of the second user, the first user starts consuming 200% of CPU time. So, we have fair resource allocation without artificial restrictions like "each user may use only one core".

intelfx
  • 151
  • 1
  • 1
  • 11
2

The issue you mentioned is still open, but this works for me.

sudo systemctl edit --force user-1234.slice

Then type and save this:

[Slice]
CPUQuota=10%

I'm not sure why it works.

Andrew Schulman
  • 8,561
  • 21
  • 31
  • 47
0

Another possibility of achieving CPU or memory limitations through cgroups is to use libpam-cgroup, which can select a profile at login from /etc/cgconfig.conf. A commonly seen example is the following:

group limited {
   cpu {
     cpu.cfs_period_us = "1000000";
     cpu.cfs_quota_us = "500000";
   }
   memory {
      memory.limit_in_bytes = "1G";
      memory.memsw.limit_in_bytes = "1G";
   }
}

This creates an interactive group limiting resources to 1 GB memory and 50% of a CPU core (500 ms per second of total CPU time). Then the /etc/cgrules.conf file can be set to put specific group or users under this group:

root       cpu,memory      /
@nolimits  cpu,memory      /
*          cpu,memory      /limited

In this case, root and everybody belonging to the nolimits group get the default (unlimited) cgroup, while all other users will be limited by the policy set above.

The first matching line is used, therefore it is possible to have a default unlimited policy while limiting a specific user or group like:

spam       cpu,memory      /limited
*          cpu,memory      /

In this case we are limiting the spam user, and no one else.

For this to work, pam_cgroup.so has to be enabled, for example in the /etc/pam.d/sshd PAM profile:

session    optional     pam_cgroup.so

Finally, the cgrules.conf file has to be parsed at boot. This can be achieved with a simple systemd service:

[Unit]
Description=Load cgroup profiles

[Service]
Type=oneshot
ExecStart=/usr/sbin/cgconfigparser -l /etc/cgconfig.conf

[Install]
WantedBy=multi-user.target

This can be saved for example to /etc/systemd/system/loadcg.service and then enabled with systemctl enable loadcg.

Ale
  • 1,613
  • 17
  • 25
0

On Ubuntu I needed to increase the TasksMax for a single user, the CI service user (UID 2000) that runs all of my group's tests, from the default limit of 10813 to something higher. I checked the old limit with sudo systemctl status user-2000.slice, then set a new limit by typing sudo systemctl edit --force user-2000.slice and entering:

[Slice]
TasksMax=50000

That updates the limit and creates the file /etc/systemd/system/user-2000.slice.d/override.conf which contains the above settings. I added the file to my Ansible playbooks and now it gets deployed to all of our machines so the limit stays set even if we rebuild a server.

Earl Ruby
  • 369
  • 3
  • 5