Які основні компоненти Docker-архітектури?
Docker це не один бінарь, а шарова структура компонентів, у кожного з яких чітка відповідальність і стандартний контракт між ними. Розуміння того, що робить кожна частина, відрізняє людину, яка використовує Docker, від того, хто може його дебажити.
Теорія
TL;DR
- П'ять шарів: client → daemon → containerd → runc → процес container. Плюс registry збоку для image.
dockerCLI це лише REST-клієнт. Інтелект у daemon.dockerdприймає запити, управляє image, мережами, volume; делегує container lifecycle на containerd.containerdце production-grade container runtime. Управляє станом container, тягне image, спілкується з нижчими runtime.runcце реалізація OCI-spec, що реально створює процес з namespaces і cgroups. Тоді виходить.- Registry зберігає і віддає image. Docker Hub дефолтний; ECR, GCR, GHCR, Harbor типові альтернативи.
Швидкий приклад
$ docker run hello-worldЦей один рядок зачіпає кожен шар. Прохід:
dockerCLI парсить команду, шлеPOST /containers/create, потімPOST /containers/{id}/startнаdockerdчерез/var/run/docker.sock.dockerdтягнеhello-world, якщо не закешовано: б'є в registry, завантажує шари, зберігає їх.dockerdкличеcontainerdчерез gRPC: «створи container з цим rootfs і config».containerdкличеrunc(через containerd-shim-процес): «запусти тут процес».runcставить namespaces, cgroups, mounts іexec'ує CMD container. Потімruncвиходить.- Процес container тепер живий, з containerd-shim як батько.
Чотири процеси задіяні (CLI, dockerd, containerd, containerd-shim) плюс твій container.
П'ять компонентів детально
1. Docker client (docker CLI)
Тонка обгортка. Парсить команди, формує REST-виклики, шле на daemon. Власного стану нуль. Підключається через:
- Unix socket (дефолт на Linux/Mac):
/var/run/docker.sock - TCP socket (через
DOCKER_HOSTenv-змінну) для віддалених daemon
Клієнт можна підмінити: nerdctl мімікрує під docker CLI, але спілкується напряму з containerd, оминаючи dockerd.
2. Docker daemon (dockerd)
Мозок системи. Довгоживучий процес, зазвичай стартує systemd. Володіє:
- REST API (Engine API), з ним спілкується клієнт.
- Управління image: pull, build, tag, зберігання у
/var/lib/docker/. - Мережа: bridge, overlay, host networks; iptables-правила.
- Volumes: named volume, bind mount.
- Build engine: BuildKit-підпроцес для
docker build. - Plugin-система: storage, network, log drivers.
Daemon САМ container не запускає, делегує це на containerd.
3. containerd
Production-grade container runtime, спочатку витягнутий з Docker, тепер CNCF graduated project. Те, що реально управляє container lifecycle:
- Тягне і розпаковує image.
- Створює container-об'єкти (rootfs + config).
- Стартує і зупиняє container через нижчі runtime.
- Стежить за станом, експозить події своїм клієнтам.
Kubernetes теж використовує containerd напряму як свій container runtime. Той самий компонент, інший оркестратор зверху.
4. runc
Reference-реалізація OCI Runtime Specification. Крихітний бінар (~10 MB). Єдина задача: за JSON-config і rootfs-директорією поставити namespaces і cgroups і exec()-нути entry-процес. Тоді runc виходить.
Довгоживучий процес, що ти бачиш за container, це не runc, це твій CMD, у якого батько containerd-shim (що тримає container живим, навіть якщо containerd рестартонули).
5. Registry
Зберігання і дистрибуція image. Реалізує OCI Distribution Spec. Daemon спілкується з ним по HTTPS, щоб тягти і пушити image.
- Docker Hub дефолтний.
docker pull nginxнасправді означаєdocker pull docker.io/library/nginx. - AWS ECR, Google Artifact Registry, GitHub GHCR, приватні registry, що використовуються у проді.
- Harbor / Distribution, self-hosted registry.
Як docker run nginx тече через все
+--------+ 1. CLI
| docker | --- REST/socket ---+
+--------+ |
v
+-------------------+
| dockerd |
| - парсить запит |
| - тягне image ---+--> registry
| - конфігурить мережу | (Docker Hub,
+-------------------+ ECR тощо)
| gRPC
v
+-------------------+
| containerd |
| - готує rootfs |
| - стежить за станом|
+-------------------+
| shim
v
+-------------------+
| containerd-shim |
| (один на container)|
+-------------------+
| spawns
v
+-------------------+
| runc |
| - ставить namespaces |
| - ставить cgroups |
| - exec() процес |
+-------------------+
| стає
v
+-------------------+
| процес nginx |
+-------------------+ Shim це те, що дозволяє рестартувати dockerd, не вбиваючи container, shim переживає рестарт daemon і реконнектиться, коли daemon знов піднявся.
Чому такий шаровий дизайн
Docker починався не таким. Оригінальний Docker (до 2015) був одним монолітним бінарем. Розпил на containerd + runc стався з двох причин:
- Стандартизація (OCI), витягти runtime-частину у спецификацію, яку всі можуть реалізувати. Тепер Podman, Kata, gVisor, Firecracker всі реалізують той самий OCI-runtime контракт.
- Перевикористання, Kubernetes хотів container runtime без решти Docker (без
docker build, безdocker network, без daemon REST API). Він обравcontainerd.
Шаровий дизайн дозволяє mix and match: Kubernetes + containerd + runc; Docker + containerd + Kata; Podman + crun; тощо.
Типові помилки
Плутати dockerd з containerd
Обидва daemon, але на різних шарах. dockerd user-facing (REST API, build engine, управління image). containerd шаром нижче (чистий container lifecycle). Зупинка dockerd не зупиняє твої container; зупинка containerd зупиняє.
Думати, що docker CLI робить роботу
$ docker stop dockerd
# Daemon зупинився. CLI тепер безкорисний.
$ docker ps
Cannot connect to the Docker daemon at unix:///var/run/docker.sockCLI це лише фронтенд. Весь стан у daemon. Без запущеного dockerd жодна docker-команда не працює.
Припускати, що архітектура має значення для коду застосунку
Не має. Твій застосунок всередині container бачить просто Linux kernel, namespaces, cgroups. Він не знає про Docker, containerd чи runc. Архітектура важлива для операторів, не для коду всередині.
Забувати, що Mac і Windows ховають Linux VM
Docker Desktop на macOS чи Windows крутить весь стек (dockerd, containerd, runc) всередині маленької Linux VM, бо все нижче dockerd потребує Linux kernel. CLI на твоєму Mac спілкується з dockerd через експозований socket VM. Цю VM ти бачиш як використання пам'яті у налаштуваннях Docker Desktop.
Реальне застосування
- Kubernetes: використовує containerd напряму через CRI (Container Runtime Interface), dockerd не в кадрі. Kubelet на кожному node спілкується з containerd, що спілкується з runc.
- GitHub Actions self-hosted runners: зазвичай крутять dockerd всередині VM; джоби використовують
docker runдля білдів. - CI/CD пайплайни (Jenkins, GitLab, CircleCI): dockerd доступний на кожному runner; джоби білдять і пушать image.
- Docker Desktop: всі компоненти разом плюс маленька Linux VM і GUI. Той самий стек dockerd/containerd/runc під капотом.
Питання для поглиблення
Q: Яка різниця між containerd і runc?
A: containerd це довгоживучий daemon, що управляє container-lifecycle (тягне image, стежить за станом, обробляє багато container). runc це one-shot CLI-бінар, що створює один container за OCI-spec, потім виходить. Containerd кличе runc як інструмент; runc не крутиться постійно.
Q: Що таке containerd-shim?
A: Невеликий процес, що сидить між containerd і твоїм container. Володіє stdio container (stdout, stderr) і чекає, поки той вийде. Мета shim, тримати container живим між рестартами containerd: можна апгрейдити containerd без вбивства всіх запущених container.
Q: Чи можна використовувати Docker взагалі без dockerd?
A: Так. Podman API-сумісний з Docker, але daemonless, без dockerd, кожна команда podman крутиться під твоїм користувачем. nerdctl тримає daemon (containerd), але оминає dockerd. Обидва виробляють/споживають OCI-image, тому існуючі image і registry працюють без змін.
Q: Куди вписується BuildKit?
A: BuildKit це сучасний build engine Docker. Коли ти робиш docker build, daemon породжує BuildKit-підпроцес, що робить реальну збірку (парсить Dockerfile, планує стейджі, монтує cache). BuildKit може також крутитися standalone для rootless або daemonless білдів.
Q: (Senior) Що стандартизує OCI-специфікація і чому це важливо?
A: Три специфікації: Image (формат: manifest, config, layers), Runtime (як реально запустити container з розпакованої rootfs, що реалізує runc) і Distribution (як registry віддають image по HTTP). Стандартизація означає, що image, зібраний у Docker, крутиться на Podman, Kubernetes, Kata, Firecracker, і registry, побудований раз, працює для всіх. Без OCI кожен runtime був би несумісним з кожним іншим.
Приклади
Інспекція запущеного стеку
# На Linux-хості з встановленим Docker
$ ps -ef | grep -E 'dockerd|containerd|runc' | grep -v grep
root 1234 1 dockerd
root 1235 1 containerd
root 4567 1235 containerd-shim-runc-v2 -id abc123 ...
# runc немає у списку, він пробіг швидко і вийшов.Shim це те, що залишається крутитися на кожен container. dockerd і containerd довгоживучі daemon.
Пряма взаємодія з containerd через ctr
# Спілкуємось з containerd напряму, оминаючи dockerd
$ ctr images pull docker.io/library/nginx:1.27-alpine
$ ctr run --rm docker.io/library/nginx:1.27-alpine my-nginxЖодного dockerd. Корисно для дебагу, коли dockerd зламано, а containerd здоровий. Це також рівно те, як Kubernetes взаємодіє з container.
Порівняння flow Docker і Podman
# Docker, daemon-based
$ docker run hello-world
# CLI -> dockerd -> containerd -> runc
# Podman, daemonless
$ podman run hello-world
# CLI -> conmon (per-container monitor) -> crun/runcТой самий OCI-image. Той самий результат (запущений container). Жодного dockerd, жодного client-server розпилу. Кожен виклик podman це свій процес. Корисно у CI, у rootless-сетапах і на хостах, де ти не хочеш довгоживучий root-daemon.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.
Коментарі
Ще немає коментарів