Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке Docker namespace і cgroups?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Linux namespaces** ізолюють те, що процес бачить (PID, mount points, мережа тощо). **cgroups** лімітують те, що процес може використовувати (CPU, пам'ять, I/O). Разом вони роблять container можливими, container це процес з застосованими namespaces + cgroups. ```bash # Всередині container lsns показує namespaces; cgroup-ліміти через /sys/fs/cgroup docker run --rm alpine lsns docker run --rm --memory=256m alpine cat /sys/fs/cgroup/memory.max ``` **Головне:** namespaces = ізоляція (видимість); cgroups = контроль ресурсів (споживання). Docker переважно це обгортка навколо налаштування цих kernel-фіч для процесів.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Linux namespaces і cgroups** це два kernel-механізми, що роблять container можливими. Без них у тебе процеси; з ними у тебе container. Docker переважно це інструмент для налаштування цих фіч на масштабі. ## Теорія ### TL;DR - **Namespaces** відповідають на «що цей процес бачить?». Сім типів: PID, mount, network, IPC, UTS, user, cgroup. - **cgroups** відповідають на «що цей процес може використовувати?». Лімітують CPU, пам'ять, I/O, PID. - Обидва це kernel-фічі, що передували Docker роками (LXC популяризував; Docker зробив mainstream). - Container концептуально: `unshare()` для створення namespaces + cgroups-config + `chroot` (або pivot_root) у rootfs + `exec()` твого бінаря. - Сучасний Docker на Linux використовує **cgroups v2** (unified hierarchy). Legacy v1 мала окремі ієрархії на controller. ### Namespaces — сім типів | Namespace | Що ізолює | |---|---| | **PID** | process ID — container має свій PID 1 | | **mount (mnt)** | mount points — container має свою в'юху filesystem | | **network (net)** | мережеві інтерфейси, routing-таблиці, сокети, порти | | **IPC** | shared memory, семафори, message queues | | **UTS** | hostname і domain name | | **user** | UID/GID (з remap) | | **cgroup** | в'юха cgroup root (cgroups v2) | Кожен namespace це kernel-ресурс, до якого можна приєднати процес через `unshare(2)` або `clone(2)`. Docker їх створює при старті container. ### Верифікація namespaces у running container ```bash # Namespaces container (кожен має унікальний inode) $ docker run -it --rm alpine sh / # ls -la /proc/self/ns lrwxrwxrwx ... cgroup -> 'cgroup:[4026532840]' lrwxrwxrwx ... ipc -> 'ipc:[4026532838]' lrwxrwxrwx ... mnt -> 'mnt:[4026532836]' lrwxrwxrwx ... net -> 'net:[4026532842]' lrwxrwxrwx ... pid -> 'pid:[4026532839]' lrwxrwxrwx ... user -> 'user:[4026531837]' lrwxrwxrwx ... uts -> 'uts:[4026532837]' # Порівняй з host (різні inode для усього, окрім, можливо, user) $ ls -la /proc/self/ns ``` Різні inode = різні namespaces = ізольовані в'юхи. ### cgroups — що вони лімітують У cgroups v2 (unified hierarchy, сучасна норма) controllers включають: ``` cpu — CPU-час (cpus, cpu.weight) memory — RAM і swap-використання io — block-I/O bandwidth і IOPS pids — кількість процесів rdma — RDMA-bandwidth hugetlb — huge pages ``` ```bash # Всередині container з --memory=256m $ docker run --rm --memory=256m alpine cat /sys/fs/cgroup/memory.max 268435456 # 256 * 1024 * 1024 байт $ docker run --rm --cpus=0.5 alpine cat /sys/fs/cgroup/cpu.max 50000 100000 # 50мс quota на 100мс period (= 0.5 CPU) ``` Docker транслює `--memory`, `--cpus` тощо у cgroup-файли у `/sys/fs/cgroup/...`. ### Як docker run використовує обидва ``` docker run --memory=256m --cpus=1 myapp │ ├── containerd → runc │ │ │ ├── unshare(CLONE_NEW{PID,NS,NET,IPC,UTS,USER,CGROUP}) │ │ → 7 свіжих namespaces │ │ │ ├── write cgroup-файли │ │ → memory.max=256MB, cpu.max=100000 100000 │ │ │ ├── pivot_root у image rootfs │ │ │ └── exec(your-binary) │ └── ти бачиш 'container' ``` Namespaces дають приватні в'юхи; cgroups лімітують, що може робити; pivot_root + image-шари дають кастомну filesystem; exec крутить твій бінар як PID 1 у цьому маленькому світі. ### User namespace: security-фронтир ```bash # Дефолт (без user namespace remap): root всередині = root на host (якщо не capability-restricted) $ docker run --rm alpine id uid=0(root) gid=0(root) # Всередині container ти root; kernel знає. # З userns-remap: root всередині мапиться на non-root UID на host $ # /etc/docker/daemon.json: { "userns-remap": "default" } $ docker run --rm alpine id uid=0(root) gid=0(root) # Всередині, ще root. Але на host процеси як some-non-root-UID. ``` User namespace remap це одне з найсильніших container-hardening. Container-escape більше не означає root на host. ### cgroups v1 vs v2 - **v1 (legacy):** окремі ієрархії на controller (`/sys/fs/cgroup/memory`, `/sys/fs/cgroup/cpu` тощо). Кожен процес належить одному cgroup на controller. - **v2 (modern):** єдина unified-ієрархія. Кожен cgroup контролює усі enabled-ресурси. Простіше, послідовніше. - Більшість сучасних Linux-дистро (Fedora, Debian 11+, Ubuntu 22+) дефолтно v2. Docker обробляє обидві. Перевір, яка у твого host: ```bash stat -fc %T /sys/fs/cgroup # 'cgroup2fs' = v2, 'tmpfs' = v1 ``` ### Типові помилки **Сприймати namespaces як security-межі** Namespaces ховають речі від процесу; вони не зупиняють процес з правильними capabilities від break-out. У комбінації з capabilities (default-drop CAP_SYS_ADMIN тощо) і seccomp (syscall-фільтрація) утворюють захист, але самі namespaces не impenetrable. Реальна ізоляція потребує повного Docker security-стеку (або microVM як Kata/Firecracker). **Забути, що cgroups лімітують пам'ять, але не OOM-поведінку** ```bash docker run --memory=256m greedy-app # Коли greedy-app намагається алокувати 257-й MB, kernel OOM-kill. ``` OOM-kill процеси всередині cgroup виходять 137. Твій supervisor / restart-policy вирішує, що далі. **Плутати user namespace з `--user`** - `--user 1000:1000` = крути процес як конкретний UID всередині user-namespace container (все одно root всередині, якщо не remap). - `userns-remap` = remap UID з container на host. Окрема рукоятка. **Припускати, що PID-namespaces працюють як container скрізь** Kubernetes-pod можуть ділити PID-namespaces між container (флаг `shareProcessNamespace: true`). У звичайному Docker два `docker run` container завжди мають окремі PID-namespaces. Інший mental-model. ### Реальне застосування - **Кожен container, скрізь.** Namespaces і cgroups лежать в основі кожного Docker, Podman, K8s-pod, Lambda-функції (через Firecracker microVM), Cloud Run-task. - **Hardened multi-tenant:** user namespace remap + dropped capabilities + seccomp-профіль + read-only filesystem → strong-ish ізоляція. - **Resource governance:** cgroup-ліміти запобігають заморюванню одним tenant іншими на shared-infra. - **Debugging container-внутрішнього:** `lsns`, `nsenter`, `/proc/<pid>/ns/*` для inspect або join namespaces ззовні. ### Питання для поглиблення **Q:** Чи namespaces або cgroups Linux-специфічні? **A:** Так. Обидва це Linux kernel-фічі. Docker на Mac/Windows крутить Linux VM під капотом саме тому. **Q:** Що таке `unshare`? **A:** Syscall (і CLI-tool), що створює новий namespace і приєднує calling-процес. `unshare --pid --fork --mount-proc /bin/bash` дає тобі shell у свіжих PID + mount namespaces, найпростіше «побудуй container руками» демо. **Q:** Яка різниця між cgroups і ulimits? **A:** ulimits (resource-ліміти через PAM, `setrlimit`) per-process. cgroups застосовуються до дерева процесів і переживають exec. Обидва можуть лімітувати ресурси; cgroups це сучасна, ієрархічна kernel-відповідь. **Q:** Чи можу побачити, у якому cgroup host-процес? **A:** Так: `cat /proc/<pid>/cgroup`. Для Docker-процесу це вказує у `/sys/fs/cgroup/<docker-cgroup-path>`. **Q:** (Senior) Як побудувати мінімальний container руками лише через namespaces + cgroups? **A:** `unshare --pid --net --mount --uts --ipc --user --fork chroot /path/to/rootfs /bin/sh` дає тобі процес з ізольованими namespaces і кастомним rootfs, голий кістяк container. Додай cgroups руками, пишучи у `/sys/fs/cgroup/<your-cgroup>/...`. Це приблизно те, що `runc` робить за тебе, автоматизовано і OCI-spec-сумісно. Корисна вправа, щоб демістифікувати, що таке container. ## Приклади ### Демонстрація namespace-ізоляції ```bash # Host $ ps -ef | wc -l 312 # Всередині container $ docker run --rm alpine ps -ef | wc -l 2 # Container бачить лише свої процеси (PID 1 + сам ps) ``` Той самий kernel, дві в'юхи, ось і PID-namespace у дії. ### Демонстрація cgroups ```bash # Memory-жадібна програма $ docker run --rm --memory=64m alpine sh -c 'dd if=/dev/zero of=/dev/null bs=1G count=1' Killed # Container OOM-kill на 64MB ліміті. $ docker run --rm --cpus=0.1 alpine sh -c 'time -- timeout 5 sh -c "yes > /dev/null"' real 0m 5.00s user 0m 0.50s sys 0m 0.00s # user time = 0.5s у 5s wall = 10% CPU = cap ``` Kernel enforce'їть, Docker лише ставить параметри. ### Mapping --user vs userns-remap ```bash # --user: змінює UID всередині (без remap) $ docker run --rm --user 1000:1000 alpine id uid=1000 gid=1000 # Зовні все одно UID 1000 (без ізоляції між in/out namespaces). # З userns-remap (daemon-level config): # /etc/docker/daemon.json: { "userns-remap": "default" } $ docker run --rm alpine id uid=0(root) gid=0(root) # Всередині root. $ ps -ef | grep <container-pid> UID 165536 ... # Але на host той самий процес UID 165536 (offset). ``` Друге значно сильніша ізоляція. Дуже рекомендовано для multi-tenant кластерів.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.