Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Які основні компоненти Docker-архітектури?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Архітектура Docker** це client-server стек: CLI `docker` спілкується з daemon `dockerd` по REST API. Daemon делегує життєвий цикл container на `containerd`, що використовує `runc` для реального старту процесу. Image приходять з registry (Docker Hub або іншого). ``` docker (клієнт) → REST → dockerd → containerd → runc → процес container ↓ registry (зберігання image) ``` **Головне:** п'ять компонентів, клієнт, daemon, containerd, runc, registry. У кожного чітка задача і стандартні контракти (OCI, REST). Це дозволяє підміняти частини (Podman замість dockerd; nerdctl напряму до containerd).Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Docker це не один бінарь**, а шарова структура компонентів, у кожного з яких чітка відповідальність і стандартний контракт між ними. Розуміння того, що робить кожна частина, відрізняє людину, яка *використовує* Docker, від того, хто може його дебажити. ## Теорія ### TL;DR - **П'ять шарів:** client → daemon → containerd → runc → процес container. Плюс registry збоку для image. - **`docker` CLI** це лише 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 типові альтернативи. ### Швидкий приклад ```bash $ docker run hello-world ``` Цей один рядок зачіпає кожен шар. Прохід: 1. `docker` CLI парсить команду, шле `POST /containers/create`, потім `POST /containers/{id}/start` на `dockerd` через `/var/run/docker.sock`. 2. `dockerd` тягне `hello-world`, якщо не закешовано: б'є в registry, завантажує шари, зберігає їх. 3. `dockerd` кличе `containerd` через gRPC: «створи container з цим rootfs і config». 4. `containerd` кличе `runc` (через containerd-shim-процес): «запусти тут процес». 5. `runc` ставить namespaces, cgroups, mounts і `exec`'ує CMD container. Потім `runc` виходить. 6. Процес 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_HOST` env-змінну) для віддалених 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 стався з двох причин: 1. **Стандартизація (OCI)**, витягти runtime-частину у спецификацію, яку всі можуть реалізувати. Тепер Podman, Kata, gVisor, Firecracker всі реалізують той самий OCI-runtime контракт. 2. **Перевикористання**, 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 робить роботу** ```bash $ docker stop dockerd # Daemon зупинився. CLI тепер безкорисний. $ docker ps Cannot connect to the Docker daemon at unix:///var/run/docker.sock ``` CLI це лише фронтенд. Весь стан у 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 був би несумісним з кожним іншим. ## Приклади ### Інспекція запущеного стеку ```bash # На 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` ```bash # Спілкуємось з 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 ```bash # 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.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.