---
tags: [linux, tooling]
verified: 2026-09-02
---

# Linux — контейнеризация: namespaces, cgroups, Docker, Podman

## Обзор

Контейнеры в Linux — не отдельная технология, а комбинация двух ядерных механизмов: namespaces (изоляция ресурсов) и cgroups (ограничение ресурсов). Docker, Podman, LXC и другие — лишь userspace-инструменты, которые оркестрируют эти примитивы ядра, добавляя image management, сетевую конфигурацию и удобный CLI.

Принципиальное отличие контейнеров от виртуальных машин: контейнер разделяет ядро хоста. Нет гипервизора, нет эмуляции оборудования, нет отдельной копии ядра. Это даёт минимальный overhead при запуске (миллисекунды vs секунды для VM), но и ограничивает изоляцию — контейнер не может использовать другое ядро или другую ОС. В FreeBSD аналог контейнеров — jails, появившиеся в 2000 году (за 13 лет до Docker), но без инструментария для image management и оркестрации.

## Namespaces: изоляция ресурсов

Namespace партиционирует ядерный ресурс так, что процессы в одном namespace видят свою копию ресурса, а процессы в другом — свою. Каждый процесс принадлежит ровно одному экземпляру каждого типа namespace. Текущие namespaces процесса видны как симлинки в /proc/PID/ns/.

Linux (6.x) предоставляет 8 типов namespaces:

**PID** (с ядра 2.6.24) — изоляция дерева процессов. Первый процесс в новом PID namespace получает PID 1 и становится «init» этого namespace: orphaned процессы перепривязываются к нему, его завершение убивает все процессы в namespace. PID namespaces вложенные: процесс виден в своём namespace и во всех родительских (с разными PID).

**NET** (2.6.29) — изолированный сетевой стек: собственные интерфейсы, IP-адреса, маршруты, iptables/nftables-правила, сокеты, connection tracking. Физический интерфейс может принадлежать только одному namespace; для связи между namespaces используются veth-пары (подробнее — статья 3).

**MNT** (2.4.19, самый первый namespace) — изолированное дерево mount points. Контейнер видит свою файловую систему; mount/umount внутри не затрагивает хост.

**UTS** (2.6.19) — изоляция hostname и NIS domain name. Каждый контейнер может иметь свой hostname.

**IPC** (2.6.19) — изоляция System V IPC (shared memory, semaphores, message queues) и POSIX message queues.

**USER** (3.8) — маппинг UID/GID: root (UID 0) внутри контейнера может быть маппирован на непривилегированного пользователя (например, UID 100000) на хосте. Фундамент rootless-контейнеров. Единственный namespace, не требующий CAP_SYS_ADMIN для создания, — но дистрибутивы это ограничивают. Апстримный рычаг — sysctl user.max_user_namespaces (ноль запрещает создание вовсе); kernel.unprivileged_userns_clone — Debian-специфичная заплатка, а Ubuntu с 24.04 по умолчанию режет непривилегированные user namespaces средствами AppArmor (kernel.apparmor_restrict_unprivileged_userns).

**CGROUP** (4.6) — изолирует видимость cgroup-иерархии: процесс в контейнере видит свою cgroup как корневую, не зная о cgroup-структуре хоста.

**TIME** (5.6) — изоляция CLOCK_MONOTONIC и CLOCK_BOOTTIME. Позволяет контейнеру иметь другое «время с момента загрузки».

### Syscalls

Три системных вызова управляют namespaces: `clone()` (создать дочерний процесс в новых namespaces, с флагами CLONE_NEWPID, CLONE_NEWNET и т.д.), `unshare()` (текущий процесс переходит в новые namespaces), `setns()` (процесс входит в существующий namespace по файловому дескриптору).

Userspace-утилиты `unshare` и `nsenter` (из util-linux) — обёртки над этими syscalls:

```bash
# Создать изолированное окружение вручную (минимальный «контейнер»)
sudo unshare --fork --pid --mount-proc --uts --ipc --net /bin/bash
# Внутри: PID 1, свой hostname, изолированная сеть, свои mount points

# Войти в namespaces существующего контейнера
nsenter --target <PID> --mount --uts --ipc --net --pid /bin/bash
# Или через docker/podman:
docker exec -it <container> /bin/bash
```

## Cgroups: ограничение ресурсов

Namespaces изолируют видимость, cgroups ограничивают потребление. Control groups — механизм ядра для учёта и лимитирования CPU, памяти, I/O, количества процессов для группы процессов.

### cgroup v1 vs v2

**cgroup v1** (с 2.6.24) — каждый контроллер (cpu, memory, blkio, pids) монтируется как отдельная иерархия. Процесс мог принадлежать разным cgroup в разных иерархиях. Сложно, непоследовательно, с проблемами в corner cases (например, memory accounting). В ядре механизм ещё есть, но userspace от него отказался: systemd 258 (сентябрь 2025) удалил поддержку legacy- и hybrid-иерархий целиком, так что загрузка с systemd.unified_cgroup_hierarchy=0 больше не работает.

**cgroup v2** (с 4.5, unified hierarchy) — единая иерархия, все контроллеры в одном дереве. Смонтирована в /sys/fs/cgroup/. Современные дистрибутивы (Fedora 31+, Ubuntu 21.10+, Debian 11+, RHEL 9) по умолчанию используют v2. Проверка: `stat -fc %T /sys/fs/cgroup` — cgroup2fs означает v2.

### Контроллеры cgroup v2

Взаимодействие через файлы в /sys/fs/cgroup/:

```bash
# Создать cgroup
mkdir /sys/fs/cgroup/myapp

# Включить контроллеры для дочерних cgroup
echo "+cpu +memory +io +pids" > /sys/fs/cgroup/cgroup.subtree_control

# Ограничить память (100MB hard limit)
echo "100M" > /sys/fs/cgroup/myapp/memory.max
# Soft limit (рекомендация для reclaim)
echo "80M" > /sys/fs/cgroup/myapp/memory.high

# Ограничить CPU (50% одного ядра)
echo "50000 100000" > /sys/fs/cgroup/myapp/cpu.max  # quota period (µs)

# Ограничить количество процессов (защита от fork bomb)
echo "100" > /sys/fs/cgroup/myapp/pids.max

# Добавить процесс в cgroup
echo $PID > /sys/fs/cgroup/myapp/cgroup.procs

# Мониторинг
cat /sys/fs/cgroup/myapp/memory.current      # текущее потребление
cat /sys/fs/cgroup/myapp/cpu.stat            # usage_usec, system_usec, etc.
cat /sys/fs/cgroup/myapp/memory.pressure     # PSI (Pressure Stall Information)
```

**memory.max** vs **memory.high**: max — жёсткий лимит (OOM при превышении), high — мягкий лимит (ядро агрессивно рекламирует память, процесс замедляется, но не убивается). Рекомендуется устанавливать оба: high = рабочий лимит, max = аварийный потолок.

**PSI (Pressure Stall Information)** — с ядра 4.20, файлы memory.pressure, cpu.pressure, io.pressure в каждой cgroup. Показывают, какой процент времени процессы были заблокированы из-за нехватки ресурса. systemd-oomd использует PSI для proactive OOM (убивает процессы до того, как ядерный OOM killer вступит в действие).

### systemd и cgroups

systemd тесно интегрирован с cgroups v2. Каждый systemd unit (service, scope, slice) автоматически получает свою cgroup. `systemd-cgls` показывает дерево, `systemd-cgtop` — top-подобный мониторинг ресурсов по cgroup. Ресурсные лимиты в unit-файлах (MemoryMax=, CPUQuota=, TasksMax= — см. статью 2) транслируются напрямую в cgroup v2 файлы.

## OCI: стандарт контейнеров

Open Container Initiative (OCI) определяет три спецификации: Image Spec (формат образа: layers, config, manifest), Runtime Spec (как запускать контейнер: namespaces, cgroups, rootfs, mounts), Distribution Spec (как хранить и передавать образы через registry).

OCI — результат стандартизации Docker-формата. Благодаря OCI образы портабельны между runtime'ами: образ, собранный Docker, запускается в Podman, containerd, CRI-O. Два основных OCI-совместимых low-level runtime: **runc** (Go, reference implementation, дефолт для Docker) и **crun** (C, Red Hat, быстрее, меньше памяти, дефолт для Podman).

## Docker

### Архитектура

Docker использует клиент-серверную модель:

docker CLI → Docker daemon (dockerd) → containerd → runc

**dockerd** — фоновый демон, работающий как root. Управляет containers, images, networks, volumes. Единая точка отказа: если dockerd падает, все контейнеры останавливаются. **containerd** — выделенный container runtime (CNCF-проект, extracted из Docker в 2016). Управляет lifecycle контейнеров: pull images, create/start/stop containers, manage snapshots. Kubernetes может использовать containerd напрямую (без Docker). **runc** — low-level runtime, создающий namespaces/cgroups и exec'ающий процесс контейнера.

### Сеть

Docker по умолчанию создаёт bridge-сеть (docker0): каждый контейнер получает veth-пару (один конец в контейнере, другой подключён к bridge), IP из подсети bridge (обычно 172.17.0.0/16), NAT для выхода наружу (masquerade через iptables/nftables).

Альтернативные сетевые режимы: host (контейнер разделяет сетевой namespace хоста — нет изоляции, но нет overhead NAT), none (никакой сети), macvlan/ipvlan (контейнер получает адрес в физической сети), overlay (multi-host сеть для Swarm/Kubernetes).

### Storage

Образ — стек read-only слоёв (layers). При запуске контейнера поверх добавляется writable layer. Union filesystem объединяет слои в единое дерево. Storage drivers: overlay2 (классический, наиболее совместимый), btrfs, zfs, vfs (для тестов); aufs удалён в Docker 24.0, devicemapper — в 25.0. С Docker Engine 29.0 на новых установках место классических graph drivers по умолчанию занимает containerd image store (снапшоттеры containerd); обновлённые с прежних версий демоны остаются на старом драйвере, пока не мигрируют вручную. Volumes (/var/lib/docker/volumes/) — предпочтительный способ persistent storage, bind mounts — прямое монтирование пути хоста.

### Практика

```bash
docker run -d --name web -p 8080:80 --memory=256m --cpus=1.5 nginx:alpine
docker exec -it web /bin/sh
docker logs -f web
docker stats                    # real-time ресурсы всех контейнеров
docker inspect web              # полная конфигурация (JSON)
docker system df                # использование диска
docker system prune -a          # очистка неиспользуемых ресурсов

# Compose (multi-container)
docker compose up -d
docker compose logs -f
docker compose down -v          # включая volumes
```

## Podman

### Архитектура: daemonless, rootless

Podman принципиально отличается от Docker архитектурно:

**Нет daemon.** Каждый контейнер — дочерний процесс podman (fork/exec модель). Если podman-процесс завершится, контейнер продолжает работать под conmon (container monitor). Нет единой точки отказа.

**Rootless по умолчанию.** Контейнеры запускаются от обычного пользователя с использованием user namespaces. Root внутри контейнера маппится на непривилегированный UID хоста (subuid/subgid, настраиваются в /etc/subuid, /etc/subgid). Нет привилегированного демона — если контейнер скомпрометирован, атакующий получает права обычного пользователя, не root.

**CLI-совместимость.** `podman run` ≈ `docker run`. Многие проекты используют `alias docker=podman`. Podman поддерживает Docker-совместимый REST API через podman.socket.

### Buildah и Skopeo

Podman-экосистема разделяет ответственность: **Podman** — запуск контейнеров, **Buildah** — сборка образов (без daemon, с тонким контролем над слоями), **Skopeo** — копирование и инспекция образов между registry (без pull на локальную машину).

### Pods

Podman нативно поддерживает pods — группы контейнеров, разделяющих namespaces (как в Kubernetes). Это позволяет генерировать Kubernetes YAML из запущенных pods: `podman generate kube mypod > pod.yaml` и наоборот: `podman play kube pod.yaml`.

### systemd-интеграция

Podman умеет генерировать systemd unit-файлы для контейнеров (`podman generate systemd --new --name web > web.service`), и контейнер дальше управляется systemd как обычный сервис. Но эта команда объявлена устаревшей — она получает только срочные багфиксы, новых возможностей в ней не будет; рекомендуемый путь — Quadlet. Quadlet (с Podman 4.4+) — декларативный формат: .container файлы в /etc/containers/systemd/ автоматически транслируются в systemd units. Кроме .container поддерживаются .pod, .volume, .network, .image, .kube, .build и .artifact, а с Podman 5.6 появилась и CLI-обвязка `podman quadlet install|list|print|rm`.

```ini
# /etc/containers/systemd/web.container (Quadlet)
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
Volume=web-data.volume:/usr/share/nginx/html:ro

[Service]
Restart=always

[Install]
WantedBy=default.target
```

## LXC/LXD и systemd-nspawn

**LXC** (Linux Containers) — предшественник Docker, ориентирован на system containers (полноценная OS внутри контейнера, с init, сервисами, cron). В отличие от application containers (Docker/Podman — один процесс на контейнер), LXC-контейнер ближе к лёгкой VM.

**LXD** — высокоуровневый менеджер поверх LXC с REST API, image management, storage backends (ZFS, btrfs, LVM), live migration. С 2023 года LXD разрабатывает Canonical, а сообщество Linux Containers ведёт собственный форк **Incus**; проекты давно разошлись и развиваются независимо.

**systemd-nspawn** — контейнерный runtime, встроенный в systemd. Минималистичный, без image management. Используется для тестирования, сборки пакетов (mock/pbuilder), запуска полных OS-деревьев. Управление через machinectl.

## Практика: что происходит при docker run

Пошаговая декомпозиция `docker run -d --name web nginx:alpine`:

1. docker CLI отправляет запрос dockerd через Unix socket (/var/run/docker.sock).
2. dockerd проверяет наличие образа; если нет — pull из registry (Docker Hub по умолчанию).
3. dockerd запрашивает containerd создать контейнер.
4. containerd подготавливает rootfs (overlay mount из слоёв образа).
5. containerd создаёт OCI bundle (config.json + rootfs) и вызывает runc.
6. runc создаёт namespaces (PID, NET, MNT, UTS, IPC), настраивает cgroups, монтирует файловые системы, применяет seccomp-фильтры и AppArmor/SELinux-профили.
7. runc exec'ает entrypoint контейнера (nginx) как PID 1 в новых namespaces.
8. runc передаёт контроль containerd (через shim процесс) и завершается.

В Podman шаги 1-3 заменяются на прямое взаимодействие podman CLI → conmon → crun/runc, без daemon.

## Нюансы и trade-off'ы

**Контейнер — не VM.** Контейнеры разделяют ядро хоста. Это означает: уязвимость в ядре (container escape) компрометирует хост, kernel tunables (sysctl) часто глобальны (не per-namespace), seccomp и LSM (AppArmor/SELinux — см. статью 7) — критический дополнительный слой защиты.

**Docker daemon как single point of failure.** dockerd перезапуск = перезапуск всех контейнеров (хотя с live-restore=true containerd может сохранить контейнеры). В production Kubernetes использует containerd или CRI-O напрямую, минуя Docker (dockershim объявлен устаревшим в K8s 1.20 и удалён в 1.24).

**Rootless trade-offs.** Rootless контейнеры (Podman default, Docker optional) безопаснее, но с ограничениями: нет доступа к привилегированным портам (<1024) без CAP_NET_BIND_SERVICE, сеть идёт через userspace-стек и потому медленнее kernel bridge (в Podman с 6.0 это только pasta: slirp4netns и опция --network-cmd-path удалены; в Docker с 29.5 дефолтный драйвер — gvisor-tap-vsock, а slirp4netns больше не кладётся в пакеты), overlay в rootless давно работает нативно (Docker считает fuse-overlayfs ненужным с ядра 5.11, Podman требует 5.12.9), и fuse-overlayfs (чуть медленнее kernel overlay2) нужен только на более старых ядрах. Для production web-серверов rootful с правильной конфигурацией (user namespaces, seccomp, no-new-privileges) может быть практичнее.

**cgroup v1 vs v2 в контейнерах.** Вопрос давно перевернулся: беспокоиться нужно не о поддержке v2, а о том, что v1 уходит. Podman 6.0 на cgroup v1 не запускается вовсе, kubelet начиная с Kubernetes 1.35 по умолчанию отказывается стартовать на узле с v1 (обход — failCgroupV1: false в конфиге kubelet, но код планируют выкинуть не раньше 1.38). Дольше всех тянут Docker и containerd: оба объявили cgroup v1 устаревшим, но обещают поддерживать его как минимум до мая 2029.

**Image layers и security.** Каждый слой образа — immutable. Если секрет (пароль, ключ) попал в слой, он остаётся в образе навсегда, даже если удалён в следующем слое. Multi-stage builds решают это: промежуточные слои со сборочными зависимостями не попадают в финальный образ.

**Docker Compose vs Kubernetes.** Compose (docker-compose.yml) — инструмент для локальной разработки и простых deployments (один хост). Kubernetes — production-grade оркестратор (multi-host, auto-scaling, rolling updates, service discovery). Podman Compose и podman play kube размывают эту границу.

## Сравнение с FreeBSD

**Jails** — предшественник контейнеров (FreeBSD 4.0, 2000). Jail предоставляет: изолированное дерево файловой системы (chroot на стероидах), изолированный набор процессов, опционально изолированный сетевой стек (VNET, аналог network namespace). В отличие от Linux-контейнеров, jails — единый механизм ядра, а не комбинация независимых namespaces. Это проще концептуально, но менее гранулярно: нельзя изолировать только PID без файловой системы, например.

**VNET** (виртуализированный сетевой стек в jail) — аналог network namespace. Даёт jail'у собственные интерфейсы, маршруты, firewall (PF). Service jails (FreeBSD 15.0+) — одна строка в rc.conf для запуска сервиса в изолированном jail (см. статью 2).

**Отсутствие cgroups.** FreeBSD использует RCTL (Resource Limits) для ограничения ресурсов jail'ов и процессов: CPU %, memory, max processes, open files, wallclock time. RCTL менее гранулярен, чем cgroups v2 (нет I/O bandwidth control, нет PSI), но покрывает основные сценарии.

**Image management.** Главное отличие: FreeBSD jails не имеют стандартизированного формата образов (аналога OCI). Создание jail'а — это подготовка directory tree (bsdinstall, poudriere, или bastillebsd). Bastille и pot — инструменты для управления jail'ами с элементами image management, но они не достигают зрелости Docker/Podman экосистемы. Это фундаментальная причина, по которой Docker-style контейнеризация доминирует в production.

**Bhyve** (FreeBSD hypervisor) — альтернатива, когда нужна полная изоляция (разное ядро, другая ОС). В Linux аналог — KVM/QEMU. Гибриды: Kata Containers, gVisor, Firecracker запускают контейнеры в micro-VM для усиленной изоляции с overhead, близким к контейнерам.

## Источники

- [namespaces(7)](https://man7.org/linux/man-pages/man7/namespaces.7.html) — man page по Linux namespaces
- [cgroups(7)](https://man7.org/linux/man-pages/man7/cgroups.7.html) — man page по cgroups
- [Control Group v2 — kernel.org](https://docs.kernel.org/admin-guide/cgroup-v2.html) — документация ядра по cgroup v2
- [OCI Runtime Specification](https://github.com/opencontainers/runtime-spec) — спецификация OCI runtime
- [Docker Architecture](https://docs.docker.com/get-started/docker-overview/#docker-architecture) — официальная документация
- [Podman documentation](https://docs.podman.io/) — официальная документация Podman
- [Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) — systemd-интеграция Podman
- [Container security fundamentals — Datadog](https://securitylabs.datadoghq.com/articles/container-security-fundamentals-part-2/) — глубокий разбор namespaces
- [What are namespaces and cgroups — NGINX](https://blog.nginx.org/blog/what-are-namespaces-cgroups-how-do-they-work) — практическое руководство
- FreeBSD Handbook: [Jails](https://docs.freebsd.org/en/books/handbook/jails/) — для сравнения

---

## Связанные заметки

- [Linux — systemd: init, сервисы, таймеры](./linux-systemd.md) — cgroup v2 и делегирование ресурсов
- [Linux — безопасность: hardening, SELinux/AppArmor](./linux-security-hardening.md) — capabilities, seccomp и MAC для контейнеров
- [Linux — справочник команд: контейнеры и Kubernetes](./linux-tldr-containers-k8s.md) — диагностика контейнеров и cgroup
- [FreeBSD — Jails](./freebsd-jails.md) — изоляция на уровне ОС во FreeBSD, для сравнения
- [Linux — сводный cheatsheet](./linux-map-cheatsheet.md) — быстрая справка по docker и podman
- [FreeBSD — bhyve: виртуализация после jails](./freebsd-bhyve.md) — та же граница «общее ядро против своего» со стороны FreeBSD
- [Локальный инференс LLM](./llm-local-inference.md) — движки инференса запускают в контейнере с проброшенным GPU
- [Адресное пространство: зачем трансляция и какие есть альтернативы](./os-address-space-models.md) — аппаратная изоляция, поверх которой namespaces изолируют только имена
- [Структура ядра: монолит, микроядро и что между](./os-kernel-structure.md) — почему изоляция имён не уменьшает доверенную базу и чем от неё отличается вынос драйвера наружу
