Архитектура Docker: dockerd, containerd, runc и OCI

Рейтинг: 57.8% · 13 голосов
Практический курс по Docker: образы, контейнеры, тома, сети, Compose и продакшен. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
Marina_DevOps
Сообщения: 54
Зарегистрирован: 11 май 2026, 05:31

Архитектура Docker: dockerd, containerd, runc и OCI

Сообщение Marina_DevOps »

Оглавление курса (46)
  1. Что такое Docker и какие задачи он решает
  2. Установка Docker и запуск первого контейнера
  3. Образы: слои, теги и реестр Docker Hub
  4. Пишем свой Dockerfile
  5. Тома и хранение данных: volumes и bind mounts
  6. Сети в Docker: связываем контейнеры между собой
  7. Переменные окружения и конфигурация контейнеров
  8. Docker Compose: поднимаем многоконтейнерное приложение
  9. Оптимизация образов: multi-stage сборка, размер и кэш слоёв
  10. Логи, отладка и мониторинг контейнеров
  11. Базовая безопасность контейнеров
  12. Подготовка к продакшену: что важно учесть
  13. Dockerfile глубже: ENTRYPOINT и CMD, HEALTHCHECK, .dockerignore, запуск не от root
  14. Реестры образов: приватные registry, push и pull, теги и digest, imagePullSecrets
  15. BuildKit и buildx: multi-arch сборки, секреты сборки, экспорт кэша
  16. Docker в CI/CD: автосборка, сканирование образов (Trivy, Docker Scout), публикация
  17. Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
  18. Архитектура Docker: dockerd, containerd, runc и OCI (вы здесь)
  19. Как работает изоляция: namespaces и cgroups
  20. Образы изнутри: слои, OverlayFS и storage driver
  21. Жизненный цикл контейнера и политики перезапуска
  22. Отладка контейнеров: exec, attach, nsenter, debug
  23. Сети Docker глубже: bridge, overlay, macvlan и iptables
  24. Docker Compose глубже: profiles, healthcheck-зависимости, override
  25. Данные и тома глубже: драйверы, бэкап, права
  26. Секреты и конфигурация: как не светить пароли
  27. Ограничение ресурсов: CPU, память, OOM и cgroups
  28. Здоровье и автозапуск: HEALTHCHECK, init и сигналы
  29. Логирование контейнеров: драйверы, ротация, сбор
  30. Мониторинг контейнеров: stats, cAdvisor, Prometheus
  31. Безопасность глубже: rootless, user namespaces, seccomp, capabilities
  32. Supply chain: сканирование, SBOM и подпись образов
  33. Глубокая оптимизация образов: distroless, scratch, кэш
  34. Multi-arch и сборка под ARM: buildx и эмуляция
  35. Реестры в продакшене: Harbor и облачные registry
  36. Docker без Docker: Podman, containerd, nerdctl, Buildah
  37. От Compose к оркестрации: когда контейнеров становится много
  38. Docker Swarm: встроенная оркестрация
  39. Сборка образов в CI без демона: kaniko, BuildKit, Buildah
  40. Траблшутинг Docker: типичные ошибки и как чинить
  41. Производительность и эксплуатация Docker-хоста
  42. Docker Desktop, WSL2 и альтернативы на Windows и Mac
  43. Контейнеризация приложений: бэкенд, фронтенд, БД
  44. Docker и Kubernetes: containerd, миграция, kompose
  45. Лучшие практики и антипаттерны Docker
  46. Сквозной проект: production-ready стек и путь дальше
Ты набираешь docker run nginx, жмёшь Enter - и через секунду внутри ядра живёт изолированный процесс. Кажется, что это монолит: одна программа сделала всю магию. На самом деле за этой секундой стоит цепочка из четырёх независимых компонентов, которые передают работу друг другу как эстафету. Пока всё работает, эта внутренняя кухня тебя не волнует. Но в день, когда контейнер завис в статусе Created, демон лёг, а процессы остались висеть, или Kubernetes отказался видеть твои образы - ты будешь чинить вслепую, если не понимаешь, кто за что отвечает.

Этот урок - про устройство docker архитектуры под капотом. Разберём, что такое dockerd, containerd, runc, зачем нужны shim-процессы, что стоит за аббревиатурой OCI и почему Kubernetes выкинул docker daemon и теперь ходит в containerd напрямую. Это не абстрактная теория - это карта, по которой ты будешь искать, где именно сломалось.

Слои: от docker CLI до системного вызова

Главное, что нужно усвоить: docker - это не одна программа, а стек. Запрос проходит сверху вниз через несколько слоёв, каждый из которых решает свою задачу и общается только с соседом.
  • docker (CLI) - тонкий клиент. Сам ничего не запускает. Он берёт твою команду, превращает её в HTTP-запрос к REST API и отправляет демону. По умолчанию - через unix-сокет /var/run/docker.sock. Клиент и демон - разные процессы, и они даже могут жить на разных машинах (переменная DOCKER_HOST).
  • dockerd (демон) - тот самый docker daemon, фоновый сервис, который слушает API. Он отвечает за высокоуровневые вещи: сборку образов, сети, volume-тома, аутентификацию в реестрах, REST API. Но сами контейнеры он своими руками не запускает - делегирует ниже.
  • containerd - менеджер жизненного цикла контейнеров и образов. Тянет образы из реестра, распаковывает слои в снапшоты на диске, управляет состоянием контейнеров (создан, запущен, остановлен), отдаёт метрики. Это отдельный демон со своим сокетом /run/containerd/containerd.sock и своим CLI - ctr.
  • runc - низкоуровневый runtime. Эталонная реализация OCI Runtime Spec. Маленькая утилита командной строки, которая берёт спецификацию (config.json + распакованный rootfs) и через примитивы ядра Linux - namespaces, cgroups, capabilities, seccomp - создаёт изолированный процесс. После запуска контейнера runc завершается. Это важная деталь, к ней вернёмся.
  • ядро Linux - финальный исполнитель. Никакого docker внутри ядра нет. Контейнер - это обычный процесс, которому ядро через namespaces показало урезанную картину мира (свою файловую систему, сеть, список процессов), а через cgroups ограничило ресурсы.
Аналогия: docker CLI - официант, который принял заказ. dockerd - администратор зала, который ведёт учёт, но к плите не подходит. containerd - шеф-повар, координирующий все блюда. runc - повар, который непосредственно поставил сковородку на огонь и ушёл. Ядро - огонь. Каждый занят своим делом, и подмена любого звена не ломает остальные.

Изображение

Что такое OCI и зачем три спецификации

Раньше Docker был и форматом образа, и средой запуска, и протоколом - всё проприетарное и слитое в один проект. Это пугало индустрию: никто не хотел, чтобы целый мир контейнеров зависел от одной компании. Так в 2015 году появилась OCI - Open Container Initiative, которая вынесла ключевые вещи в открытые стандарты. Связка oci docker - это и есть фундамент, на котором стоит совместимость.

OCI - это три отдельные спецификации:
  • Image Spec - как устроен образ. Манифест, конфиг, слои (tar-архивы со своими SHA256-дайджестами). Именно поэтому образ, собранный Docker, спокойно запускается через Podman или containerd - формат общий.
  • Runtime Spec - как из распакованного образа запустить контейнер. Описывает config.json: какой процесс стартовать, какие namespaces создать, какие монтирования сделать, какие лимиты cgroups выставить. runc реализует именно этот стандарт. Поэтому его можно заменить на crun (написан на C, быстрее стартует), gVisor (runsc, песочница с перехватом сисколлов) или Kata (микро-VM) - все они понимают один и тот же config.json.
  • Distribution Spec - как образы хранятся и передаются по сети. Протокол registry: docker pull и docker push общаются с Docker Hub, Yandex Container Registry или VK Cloud Container Registry по одному и тому же стандарту. Для RU-реалий это удобно - переключаешь реестр на зеркало, и весь инструментарий работает без переделки.
Вывод: OCI превратил монолит в набор сменных деталей. docker - лишь одна из реализаций поверх общих стандартов, а не их владелец.

Shim-процессы: почему контейнер не умирает вместе с runc

Теперь та самая деталь, которую все упускают. runc запускает контейнер и сразу выходит. Возникает вопрос: если runc ушёл, кто остаётся родителем контейнерного процесса? Кто соберёт его код возврата, кто удержит открытыми его stdout/stderr, кто заметит, что он упал?

Ответ - shim (полное имя процесса - containerd-shim-runc-v2). Это процесс-прокладка, который containerd ставит между собой и контейнером для каждого запущенного контейнера. Его задачи:
  • Стать родителем процесса контейнера, чтобы не плодить зомби-процессы и корректно собирать exit code.
  • Держать каналы ввода-вывода (stdout/stderr), чтобы логи продолжали течь даже если демон занят.
  • Отвязать жизнь контейнера от жизни демона. Это ключевое: shim не дочерний для dockerd. Поэтому ты можешь сделать systemctl restart docker или обновить containerd - и работающие контейнеры не упадут. Демон вернётся и через shim снова подцепится к ним.
Это свойство называется live-restore, и без shim оно было бы невозможно. Запомни картину одного работающего контейнера: dockerd -> containerd -> по одному containerd-shim-runc-v2 на контейнер -> сам процесс приложения. runc в этой картине уже отсутствует - он сделал своё дело на старте и вышел.

Проверим руками. Запустим контейнер и посмотрим дерево процессов:

Код: Выделить всё

$ docker run -d --name web nginx
$ ps -ef --forest | grep -E 'dockerd|containerd|nginx'

root  1180     1  /usr/bin/dockerd -H fd://
root  1205     1  /usr/bin/containerd
root  3410  1205   \_ /usr/bin/containerd-shim-runc-v2 -namespace moby -id a1b2c3...
root  3431  3410       \_ nginx: master process nginx -g daemon off;
Разбор вывода по полям. Колонки - UID, PID, PPID. Смотри на PPID (родитель):
  • dockerd (PID 1180) и containerd (PID 1205) оба имеют родителя 1 - это systemd. Они независимые демоны, а не родитель-потомок.
  • shim (PID 3410) - дочерний для containerd (PPID 1205), namespace moby (так Docker называет своё пространство в containerd).
  • nginx (PID 3431) - дочерний для shim (PPID 3410), а не для dockerd. Вот оно, отвязывание. Убей dockerd - nginx останется жить под shim.
Почему Kubernetes выкинул dockerd и пошёл в containerd напрямую

Самый громкий сюжет последних лет, который до сих пор пугает новичков заголовками вроде Kubernetes drops Docker. Разберём без паники.

Kubernetes общается с runtime через стандартный интерфейс CRI - Container Runtime Interface. Это gRPC-контракт: kubelet говорит запусти под, останови контейнер, дай логи - и ждёт ответа по стандарту. containerd CRI реализует нативно. А dockerd - нет: Docker появился раньше CRI и никогда его не поддерживал.

Чтобы как-то скрестить kubelet с Docker, в Kubernetes держали костыль - dockershim, переходник CRI -> Docker API. Но это был лишний слой, который надо было сопровождать, тестировать и чинить. А самое нелепое - под капотом dockerd всё равно вызывал containerd. То есть путь был издевательский: kubelet -> dockershim -> dockerd -> containerd -> shim -> runc. Два лишних звена, которые ничего не добавляли для Kubernetes.

Логичный итог: dockershim объявили устаревшим в Kubernetes 1.20 и полностью удалили в 1.24. Теперь путь короткий и прямой: kubelet -> containerd (через CRI) -> shim -> runc. dockerd выброшен из цепочки как лишний посредник.

Что это значит на практике, и где паника лишняя:
  • Твои образы продолжают работать. Они в формате OCI Image Spec, containerd понимает их напрямую. Ничего пересобирать не надо.
  • Это не значит, что Docker мёртв. На рабочей машине docker build, docker compose, локальная отладка - всё на месте и удобно.
  • Изменилось одно: в проде кластера за запуск контейнеров отвечает не docker daemon, а containerd сам по себе. Если делаешь docker ps на ноде Kubernetes - ты ничего не увидишь, потому что Docker там просто нет. Контейнеры смотрят через crictl ps или ctr.
containerd и nerdctl как самостоятельная альтернатива

Раз containerd умеет всё сам, возникает идея: а зачем вообще dockerd на сервере, где не нужны сборка и compose? Так появился nerdctl - CLI поверх containerd с интерфейсом, идентичным docker. Команды один в один:

Код: Выделить всё

# то же, что docker, но напрямую через containerd, без dockerd
$ nerdctl run -d -p 8080:80 --name web nginx
$ nerdctl ps
$ nerdctl build -t myapp:1.0 .       # сборка через встроенный BuildKit
$ nerdctl compose up -d              # да, compose тоже есть
nerdctl - не игрушка: он поддерживает BuildKit для сборки, compose-файлы, rootless-режим, ленивую подгрузку слоёв образа (lazy pulling). Рядом стоят родственники из той же OCI-экосистемы: nerdctl/ctr/crictl для управления, BuildKit/buildx для сборки, Podman и Buildah как полностью независимая от демона альтернатива. Для production-ноды, где не нужна локальная разработка, связка containerd + nerdctl даёт более тонкий и предсказуемый стек, чем полный Docker Engine.

docker info: где всё это лежит на диске

Теория без практики бесполезна. Команда docker info показывает, какие именно runtime собраны у тебя в стеке и где их данные:

Код: Выделить всё

$ docker info
 Server Version: 27.x.x
 Storage Driver: overlay2
  Backing Filesystem: extfs
 Cgroup Driver: systemd
 Cgroup Version: 2
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 containerd version: ...
 runc version: ...
 Docker Root Dir: /var/lib/docker
Разбор полей, которые реально пригодятся:
  • Storage Driver: overlay2 - механизм слоёв образа. overlay2 - современный дефолт, объединяет read-only слои образа и writable-слой контейнера через overlayfs ядра.
  • Cgroup Version: 2 - на современных системах это cgroup v2 (единая иерархия). Если видишь v1 - система старая, часть лимитов работает иначе.
  • Runtimes / Default Runtime: runc - какой OCI runtime используется по умолчанию. Сюда можно добавить crun или runsc и переключать через docker run --runtime.
  • Docker Root Dir: /var/lib/docker - корень всех данных Docker.
Загляни в /var/lib/docker - это карта хранилища:

Код: Выделить всё

$ sudo ls /var/lib/docker
buildkit/   containers/   image/   network/   overlay2/   volumes/
  • overlay2/ - слои образов и writable-слои контейнеров (обычно самое тяжёлое).
  • containers/ - метаданные и логи каждого контейнера (тут лежат те самые json-логи).
  • image/ - метаданные образов, дерево слоёв, дайджесты.
  • volumes/ - named volumes с данными.
Запомни: docker system prune чистит именно отсюда. Когда диск под завязку забит и сервер встаёт - чаще всего виноват разросшийся /var/lib/docker/overlay2.

Зачем инженеру знать это под капотом

Не ради эрудиции. Это прямые навыки эксплуатации:
  • Диагностика. Контейнер висит в Created, docker run молчит? Проблема не в образе, а на стыке dockerd-containerd-runc. Зная слои, ты лезешь в journalctl -u containerd, а не гадаешь.
  • Устойчивость. Понимаешь, что перезапуск демона не убьёт контейнеры (благодаря shim), и не боишься обновлять Docker на проде с живой нагрузкой.
  • Переносимость. Знаешь, что образ - это OCI, и он переедет в Kubernetes/Podman/nerdctl без переделки.
  • Безопасность и тюнинг. Можешь осознанно сменить runc на gVisor для недоверенного кода или на crun ради скорости старта.
Грабли и антипаттерны
  • Паника вокруг Kubernetes drops Docker. Это про runtime на ноде, не про твои образы. Образы в OCI-формате работают везде. Не надо ничего пересобирать под containerd.
  • Ожидать docker ps на ноде Kubernetes. Там Docker нет. Используй crictl ps или ctr -n k8s.io containers list.
  • Думать, что рестарт dockerd убивает контейнеры. При включённом live-restore - нет, shim их удержит. Но если демон уронили kill -9 без живого containerd, можно осиротить ресурсы.
  • Лечить зависший контейнер рестартом всего демона. Сначала смотри логи containerd. Часто проблема локальна - застрявший shim или upstream-сбой реестра, а ты ребутом роняешь всё хозяйство.
  • Путать ctr, crictl и nerdctl. ctr - сырой отладочный CLI containerd (не для продакшен-скриптов), crictl - инструмент CRI для нод Kubernetes, nerdctl - docker-совместимый пользовательский CLI. Это три разные утилиты для трёх задач.
Мини-лаба: проследи всю цепочку руками
  • Запусти docker run -d --name lab nginx.
  • Выполни ps -ef --forest | grep -E 'dockerd|containerd|nginx' и найди в дереве shim. Убедись, что родитель nginx - это containerd-shim-runc-v2, а не dockerd.
  • Посмотри docker info и выпиши: Storage Driver, Cgroup Version, Default Runtime, Docker Root Dir.
  • Загляни в sudo ls /var/lib/docker/overlay2 и sudo ls /var/lib/docker/containers - найди папку своего контейнера и его лог-файл.
  • Сделай sudo systemctl restart docker и сразу docker ps. Контейнер lab всё ещё Up? Поздравляю - ты только что увидел работу shim вживую.
  • Бонус: если есть containerd-tools, выполни sudo ctr -n moby containers list и найди свой контейнер на уровне containerd, минуя dockerd.
Контрольные вопросы
  • Перечисли четыре слоя docker архитектуры от CLI до ядра и назови, за что отвечает каждый.
  • runc после запуска контейнера выходит. Кто тогда остаётся родителем процесса и почему это позволяет перезапускать docker daemon без падения контейнеров?
  • Какие три спецификации входят в OCI и за что отвечает каждая?
  • Почему Kubernetes удалил dockershim и как теперь выглядит путь от kubelet до контейнера?
Итог

docker - это стек, а не монолит: docker CLI -> dockerd -> containerd -> runc -> ядро. dockerd ведёт высокоуровневую работу, containerd управляет контейнерами и образами, runc запускает их по OCI Runtime Spec и выходит, а shim удерживает контейнер живым отдельно от демона. OCI с тремя спецификациями сделала всё это сменным и переносимым - поэтому Kubernetes спокойно выкинул лишний docker daemon и ходит в containerd напрямую через CRI. Понимая эту карту, ты чинишь контейнеры осознанно, а не наугад.
👍1 ❤️2 🔥 😄 🤔1
Аватара пользователя
cephpilot
Сообщения: 1
Зарегистрирован: 27 май 2026, 10:18

Re: Архитектура Docker: dockerd, containerd, runc и OCI

Сообщение cephpilot »

Вот это перевернуло картину - всегда думал что docker run сам всё запускает, а оказывается runc просто выходит и контейнер держит shim. Теперь понятно почему рестарт демона не роняет прод.
👍3 ❤️ 🔥 😄 🤔1
Аватара пользователя
pythonveteran
Сообщения: 1
Зарегистрирован: 22 май 2026, 10:43

Re: Архитектура Docker: dockerd, containerd, runc и OCI

Сообщение pythonveteran »

А подскажи, на ноде кубера если докера нет, чем тогда логи контейнера смотреть и образы чистить? crictl это умеет или надо ctr городить?
👍1 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
Следующая глава →
Как работает изоляция: namespaces и cgroups

Все главы курса «Docker: контейнеризация от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: как установить docker на ubuntu и linuxdocker команды шпаргалка для новичкаdocker run как запустить контейнер с флагамиdocker images как посмотреть и удалить образыdocker desktop и wsl2 на windows как настроитьdocker no space left on device как очистить

Вернуться в «Docker с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость