Перейти к содержанию

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

  1. Пользователь выполняет docker run nginx.
  2. Docker CLI парсит команду, подставляет значения по умолчанию (например, -it при необходимости) и отправляет запрос создать и запустить контейнер в Docker daemon (через API).
  3. Docker daemon проверяет наличие образа nginx локально. Если образа нет — даёт команду containerd скачать образ (pull) из registry.
  4. Docker daemon создаёт метаданные контейнера (имя, конфиг сети, тома, переменные и т.д.) и просит containerd создать контейнер.
  5. containerd подготавливает root filesystem (слои образа + слой контейнера через overlay), генерирует OCI Runtime Spec (config.json) и вызывает runc.
  6. runc создаёт namespaces, cgroups, монтирует ФС, запускает процесс (в образе nginx — это процесс, указанный в ENTRYPOINT/CMD). Этот процесс на хосте и есть «контейнер»; runc остаётся родительским процессом.
  7. Демон регистрирует контейнер как «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 на хосте. Метаданные и привязка «имя ↔ контейнер» хранятся демоном.


Дополнительные материалы