Cgroups provide kernel-level resource accounting and enforcement (CPU, memory, IO). ulimit (RLIMITs) is a per-process userspace interface via the shell/exec to limit things like address space (AS) and CPU time. As an SRE you use cgroups for robust container/service limits and ulimits for lightweight per-process caps.Systemd (cgroup v2) examples — set MemoryMax and CPUQuota in a unit:ini
[Service]
MemoryMax=512M # cap resident + anon memory for the unit
CPUQuota=50% # allow 50% of one CPU (or 50% of total CPU capacity)
Under the hood CPUQuota sets cpu.max (cfs_quota_us / cfs_period_us). MemoryMax maps to memory.max and triggers cgroup accounting.Docker examples:bash
# memory limit and CPU share/quota via high-level flags
docker run --name app --memory=512m --cpus="0.5" myimage
# lower-level equivalent using quota/period
docker run --memory=512m --cpu-quota=50000 --cpu-period=100000 myimage
ulimit examples (bash):bash
ulimit -v 524288 # limit virtual memory (KB)
ulimit -t 60 # CPU time (seconds)
OOM killer interaction:- When a cgroup's MemoryMax is exceeded, the kernel records an OOM event and will select processes to kill from that cgroup (or system-wide) based on oom_score and oom_score_adj. In cgroup v2 the cgroup that triggered the pressure is considered; systemd exposes memory.events and logs.- For processes hitting RLIMIT_AS, allocations may fail (malloc returns NULL / ENOMEM) rather than immediately invoking the global OOM killer. But if overall system memory pressure is high, the kernel OOM killer can still choose targets across cgroups—properly sized per-cgroup limits help contain and make kills predictable.Best practices:- Prefer cgroups/systemd for containers and services.- Set oom_score_adj for critical services to avoid being killed.- Monitor memory.events / OOM logs and use graceful OOM handling (OOM notifications, oom_kill_disable where appropriate) and safe defaults to avoid cascading failures.