Fundamentals 16 min read

How Docker Uses Linux cgroups to Allocate CPU Resources

This article explains how Docker containers rely on Linux cgroups and namespaces for resource isolation, details CPU share and quota scheduling, and shows practical commands to inspect cgroup assignments, helping developers optimize container performance on Kubernetes.

Open Source Linux
Open Source Linux
Open Source Linux
How Docker Uses Linux cgroups to Allocate CPU Resources

Moving micro‑services from traditional VMs to Docker containers on Kubernetes is a major trend. Docker containers package software and dependencies, acting like lightweight VMs, but understanding how they use Linux cgroups and namespaces is essential for performance tuning.

Docker container implementation principle

All containers on a host share the same kernel and resources. In Linux, a Docker container is simply a set of processes isolated by namespaces and cgroups. Cgroups provide resource isolation (CPU, memory, disk, network), while namespaces limit process visibility. The subsystems ipc, mnt, net, pid, user, cgroup, and uts are used for isolation.

Processes not assigned to a specific cgroup belong to the root cgroup, typically mounted at /sys/fs/cgroup. Users with sufficient privileges can create and manage cgroups using shell commands or tools like libcgroup-tools. The /sys/fs/cgroup/cpu, cpuacct subsystem tracks CPU usage; cpuacct collects runtime info, while cpu uses the Completely Fair Scheduler (CFS) or real‑time scheduler.

When running the Docker image quay.io/klynch/java-simple-http-server, Docker creates a container with a unique identifier (e.g.,

31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267

). Docker also creates a PID namespace for the container, making the Java process PID 1 inside that namespace, though it remains visible from the host's root namespace.

# cat /proc/30968/cgroup
11:cpuacct,cpu:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
10:net_prio,net_cls:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
9:freezer:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
8:memory:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
7:pids:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
6:perf_event:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
5:devices:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
4:blkio:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
3:cpuset:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
2:hugetlb:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
1:name=systemd:/docker/31dbff344530a41c11875346e3a2eb3c1630466173cf9f1e8bca498182c0c267
# ls -l /proc/30968/ns/*
lrwxrwxrwx 1 root root 0 Jun  7 14:16 ipc -> ipc:[4026532461]
lrwxrwxrwx 1 root root 0 Jun  7 14:16 mnt -> mnt:[4026532459]
lrwxrwxrwx 1 root root 0 Jun  7 15:41 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Jun  7 14:16 pid -> pid:[4026532462]
lrwxrwxrwx 1 root root 0 Jun  7 15:41 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jun  7 14:16 uts -> uts:[4026532460]

To view the process mapping inside the container, you can check /proc/30968/status:

# grep NSpid /proc/30968/status
NSpid:  30968    1

Running docker exec -it java-http bash gives an interactive shell sharing the same namespaces as the Java process:

# docker exec -it java-http bash

# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  5.8 18.6 4680796 724080 ?        Ssl  05:10  41:51 java SimpleHTTPServer

The cgroup namespace limits the container’s view of other system processes, allowing direct access to CPU scheduling and usage information:

# ls -l /sys/fs/cgroup/cpuacct,cpu
-rw-r--r-- 1 root root 0 Jun  7 05:10 cgroup.clone_children
--w--w--w- 1 root root 0 Jun  7 05:10 cgroup.event_control
-rw-r--r-- 1 root root 0 Jun  7 05:10 cgroup.procs
-rw-r--r-- 1 root root 0 Jun  7 05:10 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Jun  7 05:10 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Jun  7 05:10 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Jun  7 05:10 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Jun  7 05:10 cpu.shares
-r--r--r-- 1 root root 0 Jun  7 05:10 cpu.stat
-r--r--r-- 1 root root 0 Jun  7 05:10 cpuacct.stat
-rw-r--r-- 1 root root 0 Jun  7 05:10 cpuacct.usage
-rw-r--r-- 1 root root 0 Jun  7 05:10 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 Jun  7 05:10 notify_on_release
-rw-r--r-- 1 root root 0 Jun  7 05:10 tasks

Scheduling

Linux uses the Completely Fair Scheduler (CFS) to dynamically schedule processes. When containers are treated as lightweight VMs, CFS schedules cgroups based on time slices rather than fixed CPU counts. The CPU subsystem can enforce minimum guarantees via shares and hard limits via quotas.

CPU share

The cpu.shares file defines the relative amount of CPU time a cgroup receives. In CentOS, the root cgroup starts with 1024 shares (100% CPU). Child cgroups inherit and divide shares proportionally. For example, on a four‑core system, cgroups with shares 2048, 1024, and 1024 would receive two cores, one core, and one core respectively.

CPU quota

CPU quotas impose hard limits on CPU time. Setting cpu.cfs_quota_us to –1 disables quotas; otherwise, the cgroup can use at most cpu.cfs_quota_us microseconds every cpu.cfs_period_us (default 100 ms). This allows precise control over how many cores a cgroup may consume.

When running JVM workloads, the container’s CPU limits affect garbage‑collection threads and overall performance. Adjusting JVM flags such as -XX:ParallelGCThreads, -XX:ConcGCThreads, and -Djava.util.concurrent.ForkJoinPool.common.parallelism helps mitigate pauses. Overriding Runtime.getRuntime().availableProcessors() with a custom native library can provide the JVM with the correct limited core count.

Conclusion

This article examined how Linux cgroups allocate and schedule resources for Docker containers. While cgroups v1 remains widely used, cgroups v2 simplifies the hierarchy but is not yet universally available in RHEL kernels. Understanding cgroup behavior enables effective CPU share and quota tuning, allowing many Java micro‑services to run on a single host without performance loss, thereby accelerating container migration and simplifying deployment.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerKubernetesResource ManagementLinuxcgroupsCPU scheduling
Open Source Linux
Written by

Open Source Linux

Focused on sharing Linux/Unix content, covering fundamentals, system development, network programming, automation/operations, cloud computing, and related professional knowledge.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.