Namespaces и cgroups
| Подсистема | ядро Linux |
|---|---|
| Namespaces | с 2002 (mount), полный набор к 2013 (user), cgroup ns 2016, time ns 2020 |
| cgroups | Google (2006), в ядре с 2.6.24 (2008) |
| cgroup v2 | стабильна с ядра 4.5 (2016) |
| Видов namespaces | 8 (mnt, pid, net, ipc, uts, user, cgroup, time) |
| Утилиты | unshare, nsenter, lsns, systemd-cgls, systemd-cgtop |
Namespaces (пространства имён) и cgroups (control groups, контрольные группы) — два механизма ядра Linux, из которых собирается то, что мы называем контейнером. Пространства имён отвечают на вопрос «что процесс видит»: свой список процессов, свою сеть, свою файловую систему, своё имя хоста. Контрольные группы отвечают на вопрос «сколько процесс может потребить»: процессорного времени, памяти, операций ввода-вывода. Контейнер — это обычный процесс, запущенный в отдельных namespaces и помещённый в свою cgroup; никакой виртуальной машины при этом нет.
История
Первым в 2002 году появилось пространство имён точек монтирования (mount namespace, ядро 2.4.19), вдохновлённое ОС Plan 9. Остальные добавлялись постепенно, и набор, достаточный для полноценных контейнеров, сложился к ядру 3.8 (2013) с появлением user namespaces.[6] Механизм cgroups инженеры Google начали разрабатывать в 2006 году под названием «process containers»; в ядро он вошёл в 2.6.24 (2008). Из-за запутанной иерархии первой версии была создана cgroup v2 с единым деревом, стабильная с ядра 4.5 (2016).[3] На этих механизмах построены LXC (2008), Docker (2013) и все современные среды выполнения.
Пространства имён
Каждый процесс принадлежит одному пространству имён каждого вида; по умолчанию — общим «корневым».[1]
| Вид | Флаг | Что изолирует |
|---|---|---|
| mnt | CLONE_NEWNS |
дерево точек монтирования (своя корневая ФС) |
| pid | CLONE_NEWPID |
номера процессов: внутри контейнера приложение — PID 1 |
| net | CLONE_NEWNET |
сетевые интерфейсы, маршруты, правила iptables, порты |
| ipc | CLONE_NEWIPC |
очереди сообщений и разделяемую память System V / POSIX |
| uts | CLONE_NEWUTS |
имя хоста и домена |
| user | CLONE_NEWUSER |
UID/GID: root внутри может быть обычным пользователем снаружи |
| cgroup | CLONE_NEWCGROUP |
видимую часть дерева cgroups |
| time | CLONE_NEWTIME |
смещения часов CLOCK_MONOTONIC и CLOCK_BOOTTIME |
Пространства создаются системными вызовами clone() и unshare(), присоединиться к существующему можно через setns(). Они видны как файлы в /proc/<PID>/ns/.
User namespaces особенно важны для безопасности: они позволяют запускать контейнеры без прав root на хосте (rootless-режим Podman и Docker).[4]
Контрольные группы
cgroup — это узел иерархии в виртуальной файловой системе /sys/fs/cgroup, к которому привязаны процессы и контроллеры ресурсов.[2] В cgroup v2 основные контроллеры такие:
- cpu — веса (
cpu.weight) и жёсткие квоты (cpu.max, например50000 100000= половина ядра); - memory — лимит (
memory.max); при его превышении внутри группы срабатывает OOM killer; - io — ограничение пропускной способности и IOPS для блочных устройств;
- pids — максимальное число процессов (защита от fork-бомбы);
- cpuset — привязка к конкретным ядрам и NUMA-узлам.
В современных дистрибутивах деревом cgroups управляет systemd: каждая служба, пользовательская сессия и контейнер получают свою группу. Kubernetes переводит requests и limits из описания Pod в настройки cgroup на узле; начиная с версии 1.25 поддержка cgroup v2 стабильна, а cgroup v1 объявлена устаревшей.[5]
Попробовать вручную
# новая оболочка в отдельных pid-, mount- и uts-пространствах
sudo unshare --pid --fork --mount-proc --uts bash
hostname sandbox; ps aux # видны только процессы этой оболочки
# пространства имён процесса и вход в них (как docker exec)
lsns -p <PID>
sudo nsenter -t <PID> -n ip addr # сеть контейнера
# cgroups
systemd-cgls # дерево групп
systemd-run --scope -p MemoryMax=200M -p CPUQuota=50% ./heavy_job
cat /sys/fs/cgroup/system.slice/nginx.service/memory.current
Изоляция и её пределы
Все контейнеры на хосте делят одно ядро. Уязвимость в ядре или опасные привилегии (--privileged, монтирование сокета Docker, лишние capabilities) позволяют выбраться из контейнера. Поэтому namespaces и cgroups дополняют другими механизмами: capabilities (дробление прав root), seccomp (фильтр системных вызовов), AppArmor и SELinux. Для более жёсткой изоляции применяют песочницы вроде gVisor и микро-VM вроде Kata Containers и Firecracker.