2. Архитектура Docker
Цель темы: понимать, что именно происходит под капотом при запуске контейнера: какие компоненты участвуют в цепочке от команды docker run до работающего процесса и где физически «живёт» контейнер.
Определения терминов
Docker CLI
Docker CLI (docker) — клиентская программа, с которой взаимодействует пользователь. Парсит команды (run, build, ps), формирует запросы и отправляет их Docker daemon через API (по умолчанию — Unix-сокет /var/run/docker.sock или TCP). Сам контейнеры не создаёт; вся работа выполняется на стороне демона.
Docker daemon (dockerd)
Docker daemon — долгоживущий процесс на хосте, который управляет образами, контейнерами, сетями и томами. Принимает запросы от CLI (и от Docker API), делегирует создание и запуск контейнеров containerd, а сборку образов, сеть и тома обрабатывает сам (или через плагины). Хранит метаданные и данные в каталоге установки (часто /var/lib/docker/).
containerd
containerd — runtime уровня контейнеров: управляет жизненным циклом контейнеров (create, start, stop, delete), образами (pull, push, хранение), не занимаясь сборкой образов или сетью Docker. Является отдельным проектом (CNCF); Docker daemon использует containerd как подсистему. Запуск контейнера в итоге сводится к вызову runc для создания процесса в namespaces и cgroups.
runc
runc — низкоуровневый OCI Runtime: по конфигурационному JSON (OCI Runtime Spec) создаёт изолированное окружение (namespaces, cgroups), монтирует корень ФС контейнера (слои образа + слой контейнера) и запускает в нём процесс (ENTRYPOINT/CMD). Один контейнер = один процесс runc, который держит этот процесс. «Где живёт контейнер» — это процесс на хосте, созданный runc, плюс данные в /var/lib/docker/ (слои, метаданные).
Docker API
Docker API — REST API демона (по умолчанию через Unix-сокет или TCP). Используется CLI, IDE, оркестраторами (Docker Compose, Kubernetes через cri-containerd), системами мониторинга. Включение TCP без защиты даёт полный доступ к хосту (создание контейнеров, монтирование каталогов) — в production API защищают TLS и доступом по сети.
OCI (Open Container Initiative)
OCI — стандарты формата образа и runtime контейнеров.
- Image Spec — как упакован образ: манифест, конфиг, слои (tarball'ы). Образы Docker совместимы с OCI Image Spec.
- Runtime Spec — JSON-конфиг (root filesystem, process, mounts, Linux namespaces/cgroups и т.д.). runc читает этот конфиг и создаёт контейнер. Любой runtime, реализующий OCI Runtime Spec (runc, crun, kata), может запускать такие контейнеры.
Daemonless (Podman)
Daemonless — подход, при котором клиент сам общается с containerd/runc (или с другим runtime) без отдельного долгоживущего демона-посредника. Podman — клиент, совместимый с командами Docker, но работающий без dockerd: напрямую с libpod/containers и OCI runtime. Подходит для окружений, где нежелателен постоянный демон с root-правами.
Цепочка: что происходит при docker run nginx
- Пользователь выполняет
docker run nginx. - Docker CLI парсит команду, подставляет значения по умолчанию (например,
-itпри необходимости) и отправляет запрос создать и запустить контейнер в Docker daemon (через API). - Docker daemon проверяет наличие образа
nginxлокально. Если образа нет — даёт команду containerd скачать образ (pull) из registry. - Docker daemon создаёт метаданные контейнера (имя, конфиг сети, тома, переменные и т.д.) и просит containerd создать контейнер.
- containerd подготавливает root filesystem (слои образа + слой контейнера через overlay), генерирует OCI Runtime Spec (config.json) и вызывает runc.
- runc создаёт namespaces, cgroups, монтирует ФС, запускает процесс (в образе nginx — это процесс, указанный в ENTRYPOINT/CMD). Этот процесс на хосте и есть «контейнер»; runc остаётся родительским процессом.
- Демон регистрирует контейнер как «running», обновляет сеть (bridge, veth, iptables при публикации портов). CLI получает ответ и может при необходимости привязать stdin/stdout к процессу контейнера.
Итого: контейнер «живёт» как процесс (и его дети) на хосте, созданный runc, плюс данные в /var/lib/docker/ (образы, слои контейнера, метаданные в БД демона).
Где реально «живёт» контейнер
- Процесс: на хосте у контейнера есть PID. Его можно увидеть, например:
docker inspect --format '{{.State.Pid}}' <container_id>и затемps -p <pid>или посмотреть в/proc/<pid>/. - Файловая система: корень ФС контейнера — это overlay (слои образа + верхний слой контейнера). Данные верхнего слоя лежат в каталоге хранилища Docker (например,
/var/lib/docker/overlay2/<id>/diff/). Тома смонтированы поверх этого. - Сеть: интерфейсы контейнера — пара veth; один конец в network namespace контейнера, другой — на bridge хоста.
- Метаданные: имена, конфиг, логи, состояние (created, running, exited) хранятся демоном (в БД/файлах под
/var/lib/docker/).
Если демон остановлен, контейнеры, созданные через него, перестают им управляться (процессы могут ещё работать, но docker ps их не покажет до перезапуска демона; при перезапуске демон восстанавливает состояние по своим метаданным).
Схема компонентов
[Пользователь]
|
v
[Docker CLI] <---> [Docker daemon (dockerd)]
| |
| +-- образы, сети, тома, плагины
| +-- запросы к containerd
| |
| v
| [containerd]
| |
| +-- образы (pull/store)
| +-- вызов runc
| |
| v
| [runc] --> namespaces, cgroups, процесс в контейнере
|
v
[Docker API] (тот же демон, другой способ доступа)
Docker vs Podman (кратко)
| Критерий | Docker | Podman |
|---|---|---|
| Демон | Есть (dockerd), root | Нет отдельного демона; можно rootless |
| CLI | docker |
podman (совместимые команды) |
| API | Docker API (REST) | REST API опционален (podman system service) |
| Совместимость | Образы OCI, Dockerfile | Те же образы, Dockerfile |
| Kubernetes | Не входит | Не входит (Kubernetes использует containerd/CRI-O) |
Podman удобен для разработки и в окружениях, где не хотят держать долгоживущий демон с привилегиями; в production-оркестрации (Kubernetes) обычно используется containerd или CRI-O, а не dockerd.
Паттерны использования
| Паттерн | Описание |
|---|---|
| Понимать цепочку CLI → daemon → containerd → runc | Помогает при отладке: где зависает (сеть, образ, runtime), куда смотреть логи. |
| Не открывать Docker API в интернет без TLS и аутентификации | Иначе возможен удалённый доступ к хосту и запуск контейнеров. |
| В оркестраторах знать, что используется не обязательно dockerd | В Kubernetes часто containerd/CRI-O; образы те же OCI. |
Антипаттерны
| Антипаттерн | Почему плохо | Что делать |
|---|---|---|
| Считать, что «контейнер в облаке» не процесс на хосте | Ресурсы и безопасность привязаны к хосту; один контейнер = процесс(ы) на ноде. | Учитывать лимиты ноды и изоляцию; мониторить процессы и ресурсы на хосте. |
| Монтировать docker.sock в контейнер «для удобства» | Контейнер получает полный доступ к демону и может запускать любые контейнеры на хосте. | Не монтировать socket в контейнер; использовать API/CLI с хоста с контролем доступа. |
| Игнорировать OCI при выборе runtime | Специфичные для Docker решения могут не перенестись на другой OCI runtime. | Опора на стандартные образы и OCI-совместимые практики. |
Примеры из production
Отладка: контейнер не стартует
Проверить логи демона (journalctl -u docker), логи containerd; убедиться, что образ скачан и слой ФС доступен. Если runc падает — смотреть OOM, права на каталоги, корректность конфига (например, неверный путь к rootfs).
Где смотреть потребление ресурсов контейнера
На хосте контейнер — процесс: CPU/память видны в ps, top, а также в docker stats (демон читает cgroups контейнера). В оркестраторах метрики часто собираются с cgroups через cAdvisor или аналог.
Переход с Docker на containerd в Kubernetes
В Kubernetes ноды могут работать на containerd без dockerd. Образы те же (OCI); отличия в настройке (config.toml, сокеты). Понимание того, что образ и runtime общие, упрощает миграцию.
Вопросы на собеседовании
Что произойдёт после docker run nginx?
Кратко: CLI отправит запрос демону; демон при необходимости скачает образ через containerd, создаст контейнер (rootfs, сеть, тома), containerd вызовет runc; runc создаст namespaces/cgroups, смонтирует ФС и запустит процесс из образа nginx. Контейнер будет в состоянии «running», процесс на хосте будет виден по PID.
Где реально живёт контейнер?
Контейнер — это процесс (и дочерние процессы) на хосте, созданный runc, плюс данные в /var/lib/docker/ (слои образа и контейнера, метаданные). Сеть — veth и bridge на хосте. Метаданные и привязка «имя ↔ контейнер» хранятся демоном.