Skip to content

Linux Control Groups & Namespaces — Container Isolation Deep Dive

DodaTech Updated 2026-06-24 9 min read

In this tutorial, you'll learn about Linux Control Groups & Namespaces. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Control groups (cgroups) and namespaces are the Linux kernel features that power all container technologies — Docker, Podman, LXC, and Kubernetes — by limiting resource usage and isolating Process views.

What You'll Learn

How cgroups v2 manages CPU, memory, and I/O limits for Process groups, how the six Linux namespace types isolate processes, filesystems, networks, and users, and how to manually create isolated environments without Docker.

Why Cgroups and Namespaces Matter

Every container you run depends on these kernel features. Understanding them at the system level lets you debug resource pressure, set custom limits, build minimal containers, and troubleshoot Kubernetes node issues. Durga Antivirus Pro uses cgroups to limit scan worker CPU usage during peak hours and namespaces to sandbox file analysis in isolated environments.

Learning Path

flowchart LR
  A[Systemd Deep Dive] --> B[cgroups & Namespaces
You are here] B --> C[LXC Containers] B --> D[Filesystems Comparison] style B fill:#f90,color:#fff

Control Groups (cgroups)

Cgroups limit and account for resources used by groups of processes. Linux has two versions: cgroups v1 (legacy, separate controllers per hierarchy) and cgroups v2 (unified hierarchy, default since kernel 4.5+).

Checking Cgroup Version

# Check which version is in use
stat -fc %T /sys/fs/cgroup/
# cgroup2fs → v2, tmpfs → v1

# List available controllers
cat /sys/fs/cgroup/cgroup.controllers
# cpuset cpu io memory hugetlb pids rdma misc

Expected output on a v2 system:

$ stat -fc %T /sys/fs/cgroup/
cgroup2fs

$ cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc

Managing Cgroups with systemd

Systemd exposes cgroups through service unit directives and the systemd-run command:

# Run a command with CPU and memory limits
sudo systemd-run --unit=limited-task \
    --scope -p CPUQuota=50% -p MemoryMax=100M -p TasksMax=10 \
    /usr/bin/python3 -c "
import time
x = [0] * 10000000  # Allocate ~80MB
time.sleep(30)
"

# Check resource usage
systemctl show limited-task -p MemoryCurrent
systemctl show limited-task -p CPUUsageNSec

Manual Cgroup Management (v2)

# Create a child cgroup
sudo mkdir /sys/fs/cgroup/mygroup

# Set memory limit (in bytes)
echo 100000000 | sudo tee /sys/fs/cgroup/mygroup/memory.max

# Set CPU quota (100000 = 1 core)
echo 50000 100000 | sudo tee /sys/fs/cgroup/mygroup/cpu.max

# Add current shell to the cgroup
echo $$ | sudo tee /sys/fs/cgroup/mygroup/cgroup.procs

# Verify limits
cat /sys/fs/cgroup/mygroup/memory.current
cat /sys/fs/cgroup/mygroup/cpu.stat

# Remove the cgroup (migrate all processes out first)
echo 1 | sudo tee /sys/fs/cgroup/cgroup.procs
sudo rmdir /sys/fs/cgroup/mygroup

Cgroup Controllers

Controller Purpose Key Files
cpu CPU bandwidth limits cpu.max, cpu.weight
memory Memory usage limit and swap memory.max, memory.current, memory.swap.max
io I/O bandwidth limits io.max, io.weight
pids Limit number of processes pids.max
cpuset CPU and memory node affinity cpuset.cpus, cpuset.mems
hugetlb Huge page limits hugetlb.2MB.max

Pressure Stall Information (PSI)

Cgroups v2 exposes resource pressure metrics:

# Memory pressure
cat /sys/fs/cgroup/memory.pressure

# CPU pressure
cat /sys/fs/cgroup/cpu.pressure

# I/O pressure
cat /sys/fs/cgroup/io.pressure

Expected output:

$ cat /sys/fs/cgroup/memory.pressure
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

some means at least one task is stalled. full means all tasks are stalled. High full values indicate severe resource contention.

Linux Namespaces

Namespaces wrap global system resources in an abstraction that makes processes within a namespace see their own isolated instance.

The Six Namespace Types

Namespace Flag Isolates Since Kernel
Mount CLONE_NEWNS Filesystem mount points 2.4.19
PID CLONE_NEWPID Process IDs 3.8
Network CLONE_NEWNET Network interfaces, IPs 2.6.29
IPC CLONE_NEWIPC System V IPC, POSIX Message Queues 2.6.19
UTS CLONE_NEWUTS Hostname, domain name 2.6.19
User CLONE_NEWUSER User and group IDs 3.8

Creating Namespaces with unshare

# Create a namespace with new UTS (hostname) and PID namespace
sudo unshare --uts --pid --mount --fork /bin/bash

# Inside the namespace:
# Change hostname without affecting host
hostname isolated-container
hostname
# isolated-container

# Mount a new proc filesystem
mount -t proc proc /proc

# List processes — only the namespace's processes appear
ps aux

# Exit to return to the host namespace
exit

Isolated Process Tree

# Run a command in its own PID namespace
sudo unshare --pid --fork --mount-proc /bin/bash

# Inside, PID 1 is this shell
echo $$
# 1

# Process IDs inside the namespace differ from host PIDs
# The host still sees the original PID via /proc

Network Namespace Example

# Create a network namespace
sudo ip netns add red
sudo ip netns add blue

# Connect them with a veth pair
sudo ip link add veth-red type veth peer name veth-blue
sudo ip link set veth-red netns red
sudo ip link set veth-blue netns blue

# Assign IPs
sudo ip netns exec red ip addr add 10.0.0.1/24 dev veth-red
sudo ip netns exec blue ip addr add 10.0.0.2/24 dev veth-blue

# Bring interfaces up
sudo ip netns exec red ip link set veth-red up
sudo ip netns exec blue ip link set veth-blue up

# Test connectivity
sudo ip netns exec red ping -c 2 10.0.0.2

Expected output:

PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.052 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.048 ms

User Namespace

User namespaces map non-root UIDs inside the namespace to root UID outside, giving a Process root privileges inside its namespace without actual host root:

# Map current user to root inside namespace
unshare --user --map-root-user /bin/bash

# Inside, we are root
whoami
# root

# But outside, we are still the regular user
# Touch a file owned by root inside
touch /tmp/test.txt
ls -l /tmp/test.txt
# -rw-r--r-- 1 nobody nogroup 0 ... test.txt
# (mapped to nobody outside, because the root inside maps to our UID outside)

Checking Namespace Membership

# Check which namespaces a process belongs to
ls -l /proc/$$/ns/
# lrwxrwxrwx 1 user user 0 ... cgroup -> 'cgroup:[4026531835]'
# lrwxrwxrwx 1 user user 0 ... ipc -> 'ipc:[4026531839]'
# lrwxrwxrwx 1 user user 0 ... net -> 'net:[4026531840]'
# lrwxrwxrwx 1 user user 0 ... pid -> 'pid:[4026531836]'

# Two processes sharing the same namespace number are in the same namespace

Combining Cgroups and Namespaces

Container runtimes combine both technologies. Here is a minimal manual container:

# Create a fully isolated environment
sudo mkdir -p /isolated/rootfs
# (Assume a minimal rootfs exists at /isolated/rootfs)

# Step 1: Create cgroup limits
sudo mkdir /sys/fs/cgroup/isolated
echo 200M | sudo tee /sys/fs/cgroup/isolated/memory.max
echo 50000 100000 | sudo tee /sys/fs/cgroup/isolated/cpu.max

# Step 2: Launch with all namespaces + cgroup
sudo unshare \
    --mount --pid --net --ipc --uts --user --fork \
    --mount-proc \
    --root=/isolated/rootfs \
    /bin/bash

# Step 3: In another terminal, add PID to cgroup
echo $PID_OF_UNSHARE | sudo tee /sys/fs/cgroup/isolated/cgroup.procs

Common Errors

1. cgroups v1 vs v2 Confusion

Some tools and configurations assume v1. Docker 20.10+ works with v2. If you see "cgroup mountpoint does not exist" errors, verify the cgroup version and mount: mount | grep cgroup.

2. Permission Denied Creating Cgroups

Creating cgroups requires root or delegated access. On systems with systemd, user units get delegated cgroup subtrees. Check /sys/fs/cgroup/user.slice/user-$(id -u).slice/.

3. Network Namespace DNS Not Working

Network namespaces do not inherit /etc/resolv.conf. Configure DNS manually or bind-mount the host resolv.conf: mount --bind /etc/resolv.conf /etc/netns/<name>/resolv.conf.

4. Unshare Fails with "Operation not permitted"

Some namespaces (PID, network, mount) require root or CAP_SYS_ADMIN. User namespaces can be created without root but have restrictions.

5. Cgroup OOM Killer Killing Wrong Process

When a cgroup hits the memory limit, the OOM killer kills processes within that cgroup. If the init Process inside a container dies, the entire container stops. Consider setting memory.oom.group=1 to kill all processes in the group atomically.

6. PID Namespace Shows All Host Processes

After unshare --pid, you must mount a new proc filesystem with --mount-proc or mount -t proc proc /proc for the PID namespace to show only its own processes.

7. User Namespace Root Cannot Write to Some Paths

Inside a user namespace, the mapped root still cannot write to filesystems mounted with root-squash (NFS) or to paths owned by unmapped UIDs/GIDs.

Practice Questions

1. What is the difference between cgroups v1 and v2? v1 had separate hierarchies per controller (e.g., one for memory, one for CPU). v2 uses a single unified hierarchy where all controllers coexist under /sys/fs/cgroup/.

2. How do you check which cgroup version is running on a system? stat -fc %T /sys/fs/cgroup/ returns cgroup2fs for v2 or tmpfs for v1.

3. What does the PID namespace isolate? Process ID numbering. Inside a PID namespace, the first Process gets PID 1. Processes in the namespace cannot see or signal processes outside it.

4. How do you run a command in a different network namespace? sudo ip netns exec <namespace-name> <command>, or sudo nsenter -t <PID> -n <command>.

5. What is pressure stall information (PSI)? Kernel metrics showing how long tasks are stalled waiting for CPU, memory, or I/O resources. Exposed in /sys/fs/cgroup/<cgroup>/<resource>.pressure.

Challenge: Create a fully isolated environment by hand: (1) create a cgroup limiting memory to 100MB and CPU to 25% of one core, (2) use unshare to create new PID, mount, and network namespaces, (3) set up a veth pair to give the namespace network access, (4) demonstrate that a Process inside the namespace cannot see host processes and is limited to 100MB memory.

Do I need cgroups to run containers?

Yes — Docker, Podman, and LXC all depend on cgroups for resource limits. Without cgroups, a container could consume all host memory.

Can namespaces be nested?

Yes — you can create a namespace inside another namespace. This is how nested containers work (e.g., running Docker inside a Docker container).

What happens when a PID namespace init Process (PID 1) dies?

All processes in that namespace are killed immediately. The namespace ceases to exist.

How do I list all network namespaces on a system?

ip netns list shows named namespaces. For all namespaces including unnamed ones, check /proc/*/ns/net and deduplicate the inode numbers.

What is the difference between a container and a namespace?

A namespace is a single kernel feature that isolates one resource. A container is a combination of multiple namespaces, cgroups, and a filesystem — managed by a runtime like Docker.

What's Next

Linux Containers — LXC & LXD Guide
Linux Filesystems — ext4, XFS, Btrfs & ZFS
Linux Swap Management Guide

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-24.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro