☰ Конспект ← Курс
ЛЕКЦІЯ 2

ПРОСТОРИ ІМЕН, ГРУПИ КЕРУВАННЯ (CGROUPS), ПРИВІЛЕЇ

Ізоляція ядром без віртуалізації заліза

ВТФК · Системне програмування · 2026

План лекції

  • Контейнер — це процес, а не машина
  • Простори імен (namespaces): що ізолює кожен
  • clone / unshare / setns — три системні виклики
  • cgroups v2: обмеження ресурсів
  • Привілеї: capabilities і user namespace
  • Як це збирається у Docker

Фокус: ізоляція ядром

Віртуальна машина ↔ контейнер

Віртуальна машина
  • Гіпервізор віртуалізує залізо
  • Окреме ядро гостьової ОС
  • Старт — секунди, образ — гігабайти
  • Ізоляція — на межі заліза
vs
Контейнер
  • Звичайний процес на ядрі хоста
  • Ядро одне — спільне з хостом
  • Старт — мілісекунди, образ — мегабайти
  • Ізоляція — namespaces + cgroups + capabilities

Контейнеру змінили «видимість світу», а не дали окрему машину

Простори імен (*namespaces*): що ізолює кожен

PIDвласна нумерація процесів; усередині — PID 1
NETвласний мережевий стек: інтерфейси, IP, маршрути
MNTвласне дерево точок монтування
UTSвласне ім'я хоста (hostname)
IPCчерги повідомлень, семафори, спільна пам'ять System V
USERвідображення UID: root усередині ≠ root на хості

Кожен простір — окрема «версія» глобального ресурсу ядра

Сьомий простір і успадкування

  • CGROUP namespace ізолює видимість власної ієрархії cgroup
  • Процес бачить своє піддерево як корінь /sys/fs/cgroup
  • Docker за замовчуванням: PID, NET, MNT, UTS, IPC, CGROUP; USER — з --userns-remap
  • Є ще TIME (ядро 5.6+); новий процес успадковує простори батька
КОНТЕЙНЕР = ПРОЦЕС + 7 ПРОСТОРІВ ІМЕН ХОСТ · спільне ядро КАПСУЛА (PID 1) PIDMNTNETUTSIPCUSERCGROUP свій погляд на систему — але ядро одне на всіх

Три системні виклики для просторів імен

ВикликЩо робитьХто використовує
clone(flags)новий процес одразу в нових просторахdocker run → runc
unshare(flags)відв'язує наявний процес від поточного просторуутиліта unshare(1)
setns(fd, type)приєднує процес до чужого просторуnsenter, docker exec

docker exec — це setns() у простори вже запущеного контейнера, не новий clone()

unshare: власний PID-простір

bash
sudo unshare --pid --fork --mount-proc bash
# усередині:
ps aux
echo $$

Той самий /bin/bash, але echo $$ покаже PID 1 — процеси хоста невидимі

Що робить unshare --pid --fork --mount-proc bash

  1. 1
    unshare(CLONE_NEWPID)поточний процес відв'язано у новий PID-простір
  2. 2
    fork()перша дитина в новому просторі стає PID 1
  3. 3
    mount -t procновий /proc, інакше ps читав би /proc хоста
  4. 4
    execve(bash)той самий бінарник — інша картина світу
  5. 5
    echo $$ → 1процеси хоста невидимі; exit повертає назад

Без --mount-proc ps показує «чужі» процеси — типова помилка

Живе демо: unshare проти хоста

  1. На хості: ps aux | wc -l (десятки процесів)
  2. sudo unshare --pid --fork --mount-proc bash
  3. Усередині: ps aux — видно тільки bash і ps
  4. echo $$ — PID дорівнює 1
  5. exit — повернення в звичайний простір, хост незмінний

Очікувано: Один і той самий бінарник bash поводиться по-різному залежно від простору імен, у якому він запущений

setns(): як docker exec заходить у контейнер

bash
PID=$(docker inspect -f '{{.State.Pid}}' b7)
ls -l /proc/$PID/ns/
# pid -> pid:[4026532xxx]  net -> net:[4026532yyy] ...
nsenter --target $PID --pid --net --mount ps aux

nsenter обгортає setns() — заходить у namespaces процесу за його PID

Простори імен ↔ групи керування

namespaces
  • Що процес БАЧИТЬ
  • Власні PID, мережа, ФС, hostname
  • Не обмежують кількість ресурсів
  • Механізм: clone / unshare / setns
+
cgroups
  • Скільки процес МОЖЕ спожити
  • CPU, пам'ять, I/O, кількість процесів
  • Не ховають нічого від процесу
  • Механізм: файли у /sys/fs/cgroup

Без cgroups ізольований процес міг би з'їсти всю пам'ять хоста

cgroups v2 — контроль апетиту

  • Групи керування (*cgroups*) обмежують і обліковують CPU, пам'ять, I/O, процеси
  • Ієрархія у /sys/fs/cgroup/: група — каталог, ліміт — файл (memory.max, cpu.max)
  • cgroups v2 — єдина уніфікована ієрархія; v1 мала окрему на кожен контролер
  • Docker записує --memory / --cpus у ці файли перед execve()
CGROUPS v2 · ЛІМІТИ РЕСУРСІВ CPU 40% MEMORY 512M / 2G IO 10 MB/s PIDS 128 ядро гарантує: процес не зʼїсть більше, ніж дозволено

Контролери cgroups v2

КонтролерФайлПрикладОзначає
cpucpu.max50000 10000050 мс з кожних 100 мс — половина ядра
memorymemory.max104857600100 МБ; далі — OOM killer у групі
ioio.max8:0 rbps=1048576010 МБ/с читання з диска
pidspids.max128захист від fork-бомби

Значення — звичайний текст; ліміт змінюється echo у файл

cgroups наживо

bash
docker run -d --name b7 --memory=100m --cpus=0.5 nginx
cat /sys/fs/cgroup/system.slice/docker-*.scope/memory.max
docker stats --no-stream

docker run --memory / --cpus транслюється у файли cgroups v2

cgroups v2: файли лімітів зсередини

bash
cat /sys/fs/cgroup/.../memory.max   # 104857600 (100 МБ)
cat /sys/fs/cgroup/.../cpu.max      # 50000 100000 (0.5 CPU)
cat /sys/fs/cgroup/.../pids.max     # захист від fork-бомб

cpu.max записаний як "quota period" у мікросекундах: 50000/100000 = половина ядра

Привілеї: root у контейнері ≠ root на хості

  • Linux capabilities дроблять «суперправа root» на окремі дозволи
  • Docker за замовчуванням відкидає більшість capabilities навіть для root
  • USER namespace відображає UID 0 контейнера на непривілейований UID хоста
  • Два незалежні механізми — подвійний захист при втечі з ізоляції

USER-простір: чому root у контейнері небезпечний

USER-ПРОСТІР: ХТО ТАКИЙ ROOT УСЕРЕДИНІ UID У КОНТЕЙНЕРІ uid 0 · root uid 1000 · app процес щиро вважає себе root UID НА ХОСТІ uid 0 · root усе можна uid 100000 · nobody нічого вирішує лише ця колонка 0 → 0 0 → 100000 БЕЗ userns типовий Docker --userns-remap вмикається вручну vs root у капсулі, що змонтувала /etc хоста, — це root над /etc хоста

Docker не вмикає USER-простір за замовчуванням — це і є та «сьома брама», що лишається відчиненою

Capabilities: приклади

CapabilityЩо дозволяєDocker за замовч.
CAP_NET_BIND_SERVICEпорт < 1024 без повного rootтак
CAP_CHOWNзмінювати власника файлівтак
CAP_KILLсигнали чужим процесамтак
CAP_NET_ADMINінтерфейси, маршрути, фаєрволні
CAP_SYS_PTRACEстежити за чужими процесамині
CAP_SYS_ADMINmount та десятки адмін-операційні

--cap-add / --cap-drop — точково; --privileged — вимикає майже все одразу

Привілеї в цифрах

~40
capabilities у ядрі Linux
замість бінарного root / не-root
14
лишає Docker за замовчуванням
CAP_SYS_ADMIN, CAP_NET_ADMIN — відкинуті
0 → 100000
UID при userns-remap
root усередині — звичайний користувач зовні
1
прапорець --privileged
усі caps + вимкнений seccomp + пристрої хоста

Разом: з чого складається "контейнер"

  • namespaces — що процес БАЧИТЬ (ізоляція видимості)
  • cgroups — скільки процес МОЖЕ споживати (ізоляція ресурсів)
  • capabilities — що процес МАЄ ПРАВО робити (ізоляція привілеїв)
  • Docker — це зручна обгортка над цими трьома примітивами ядра Linux
процес звичайний namespaces ізоляція cgroups + caps ліміти/права КОНТЕЙНЕР все разом З ЧОГО СКЛАДАЄТЬСЯ «КОНТЕЙНЕР»

Як docker run збирає контейнер

docker runCLI → dockerd
containerd → runcOCI-рантайм
clone(flags)нові простори імен
cgroups + capsзапис лімітів, скидання прав
execve()процес контейнера, PID 1

Три механізми незалежні й не знають один про одного — комбінує їх runc

Кожна програма і кожен користувач системи мають працювати з найменшим набором привілеїв, необхідним для виконання роботи.

— Джером Зальцер, Майкл Шредер — принцип найменших привілеїв (1975)

Наперед: з чого складається образ

  • Сьогодні — про запущений контейнер (runtime-ізоляція)
  • Наступного разу — про сам образ: шари, Dockerfile, кеш, багатоетапна збірка
  • Питання на подив: чому образ ubuntu:24.04 важить ~80 МБ, а не гігабайти?

Підводні камені: namespaces і привілеї

  • unshare --pid без --mount-proc лишає старий /proc — ps показує «чужі» процеси
  • --privileged вимикає майже всю ізоляцію, а не додає одну можливість
  • root без user namespace — реальний root на хості при втечі з ізоляції
  • cgroups v1 і v2 мають різні шляхи й формат файлів

Підсумок

  • Контейнер = namespaces (видимість) + cgroups (ресурси) + capabilities (привілеї)
  • unshare показує: та сама програма поводиться по-різному в іншому просторі імен
  • root у контейнері — не root на хості, якщо налаштовано user namespace
  • Наступна лекція: як усе це пакується в образ

🥚 У прикладах контейнер названо b7, а процес усередині нього завжди бачить себе як PID 1. Двосимвольне ім'я обране не випадково — воно ще трапиться.

VTFK · TERMLINK · L02← → · space · f — на весь екран