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

TL;DR: Docker = CLI → Daemon → containerd → runc → ядро Linux. Контейнер — это обычный процесс, изолированный namespaces и ограниченный cgroups. Не VM.

Docker — это не монолитное приложение, а платформа, работающая по клиент-серверной архитектуре. Она абстрагирует управление примитивами ядра Linux (namespaces, cgroups) для запуска процессов в изолированных окружениях.

Понимание архитектуры необходимо для отладки проблем с правами доступа, сетью и производительностью, а также для понимания отличий Docker от виртуальных машин.

Client-Server Модель

Работа Docker строится на взаимодействии трех компонентов:

  1. Docker Client (docker): Утилита командной строки (CLI), с которой взаимодействует пользователь. Клиент не запускает контейнеры самостоятельно; он конвертирует команды (например, docker run) в REST API запросы.
  2. Docker Host (Daemon dockerd): Фоновой процесс, работающий на хост-системе. Он слушает запросы от клиента, управляет объектами (образы, контейнеры, сети, тома) и делегирует задачи низкоуровневым рантаймам.
  3. Registry: Хранилище образов (Docker Hub, GHCR, Private Registry).

Docker Socket и безопасность

Взаимодействие между Клиентом и Демоном происходит через сокет.

  • UNIX Socket (/var/run/docker.sock): Основной канал связи. Файл принадлежит пользователю root и группе docker.

Security Warning: Любой пользователь, включенный в группу docker, фактически получает полные права root на хосте. Через сокет можно запустить привилегированный контейнер (--privileged), смонтировать корневую файловую систему хоста (-v /:/host) и изменить любые системные файлы.

Rootless Mode (режим без root)

Для минимизации рисков безопасности современный Docker поддерживает Rootless mode.

  • Принцип: Daemon и контейнеры запускаются от имени обычного пользователя, без использования sudo.
  • User Namespaces: Используется маппинг UID/GID. Внутри контейнера процесс может “думать”, что он root (UID 0), но ядро Linux отображает его на обычного пользователя (например, UID 1000) на хосте.
  • Изоляция: Если злоумышленник совершит побег из контейнера (Container Breakout), он окажется на хосте с правами обычного пользователя, что предотвращает компрометацию всей системы.

Стек выполнения (The OCI Stack)

Начиная с версии 1.11, Docker перешел на модульную архитектуру, основанную на стандартах OCI (Open Container Initiative). Демон dockerd больше не создает контейнеры напрямую.

1. containerd (High-Level Runtime)

Индустриальный стандарт управления жизненным циклом контейнеров.

  • Роль: Управляет образами (pull/push), хранилищем и сетевыми интерфейсами.
  • Задача: Получить команду от dockerd и подготовить всё необходимое (слои файловой системы, спецификацию OCI Bundle) для запуска.
  • containerd используется и в Kubernetes (через CRI), что делает Docker и K8s совместимыми на уровне образов.

2. runc (Low-Level Runtime)

Референсная реализация спецификации OCI Runtime.

  • Роль: Непосредственное взаимодействие с ядром Linux.
  • Задача: Создать системный процесс, применив к нему пространства имен (namespaces) и лимиты (cgroups).
  • После запуска контейнера процесс runc завершается, передавая управление процессу приложения. Это позволяет обновлять Docker/containerd без остановки запущенных контейнеров.

3. containerd-shim

Прослойка между containerd и процессом контейнера.

  • Позволяет runc завершиться после запуска контейнера (daemonless containers).
  • Держит открытыми потоки ввода-вывода (STDIN/STDOUT/STDERR).
  • Отслеживает код выхода (exit code) контейнера и сообщает его демону.

Примитивы ядра Linux

Docker не использует виртуализацию железа (как VMware или KVM). Изоляция достигается средствами ядра Linux.

Namespaces (Пространства имен)

Обеспечивают изоляцию видимости. Процесс в контейнере “думает”, что он единственный (или главный) в системе.

  • PID: Свое дерево процессов. PID 1 внутри контейнера изолирован от PID 1 хоста.
  • NET: Свой сетевой стек (интерфейсы, IP, порты, iptables).
  • MNT: Изолированная файловая система (mount points).
  • IPC: Изоляция межпроцессного взаимодействия (Shared Memory).
  • UTS: Свое имя хоста (hostname).
  • USER: Маппинг UID/GID (позволяет быть root внутри контейнера, но обычным пользователем снаружи).

Cgroups (Control Groups)

Обеспечивают изоляцию ресурсов.

  • Ограничивают потребление CPU, RAM, Disk I/O.
  • Позволяют демону убить контейнер при превышении лимита памяти (OOM Killer).

Capabilities

Docker по умолчанию запускает контейнеры с урезанным набором привилегий root. Механизм Linux Capabilities позволяет дробить права суперпользователя.

  • Например, контейнеру разрешено открывать порты (NET_BIND_SERVICE), но запрещено менять системное время (SYS_TIME) или загружать модули ядра.

Подводные камни

ЗаблуждениеРеальность
«Docker — это лёгкая VM»Контейнеры делят ядро с хостом. Уязвимость в ядре = компрометация всех контейнеров
«Контейнер изолирован полностью»Без user namespaces root в контейнере = root на хосте
«Docker socket безопасен»Доступ к /var/run/docker.sock = полный root на хосте
«Можно обновить Docker без последствий»Благодаря shim контейнеры продолжают работать при обновлении daemon/containerd