Skip to main content

Що таке Docker daemon і Docker client? Як вони взаємодіють?

Docker daemon і Docker client утворюють класичну client-server пару. Daemon робить реальну роботу; client це фронтенд, що говорить REST. Розуміння цього розпилу дозволяє дебажити «чому CLI висить» або «чому мій Mac спілкується з dockerd всередині VM».

Теорія

TL;DR

  • Daemon (dockerd) = довгоживучий root-процес. Управляє image, container, мережами, volume. Володіє ВСІМ станом.
  • Client (docker CLI) = stateless фронтенд. Переводить твої команди у REST API виклики.
  • Транспорт = Unix socket (/var/run/docker.sock) за замовчуванням; TCP socket, якщо ставиш DOCKER_HOST=tcp://....
  • Формат на проводі = HTTP + JSON. Engine API версіонований (/v1.46/...); daemon домовляється про версію з клієнтом.
  • Один до багатьох = можна спрямувати один CLI на кілька daemon через Docker-контексти (docker context use ...).

Швидкий приклад

bash
# Що `docker ps` насправді робить: $ curl --unix-socket /var/run/docker.sock \ http://docker/v1.46/containers/json | jq '.[0]' { "Id": "a3f9d2b8c1e4...", "Names": ["/web"], "Image": "nginx:1.27-alpine", "Status": "Up 5 minutes", "Ports": [{ "PrivatePort": 80, "PublicPort": 8080, "Type": "tcp" }] }

Це рівно те, що CLI зробив під капотом, коли ти набрав docker ps. GET у socket; daemon повертає JSON; CLI форматує його у таблицю.

Daemon (dockerd)

Linux-сервіс, зазвичай стартує systemd як root. Відповідальності:

  • Управління image: тягне з registry, білдить через BuildKit, зберігає у /var/lib/docker/.
  • Container lifecycle: делегує на containerd і runc, але володіє user-facing container API.
  • Мережа: створює віртуальні bridge, керує iptables-правилами, виділяє IP-адреси container.
  • Volume: named volume, bind mount, plugin-based storage driver.
  • API endpoint: слухає налаштовані socket'и.

Конфігурація живе у /etc/docker/daemon.json і command-line флагах. Типові налаштування: data-root (де зберігати image), storage-driver (overlay2 за замовчуванням), log-driver, dns, registry-mirrors.

Client (docker CLI)

Один бінар, без daemon, без власного стану. Коли ти набираєш команду:

  1. Парсить аргументи (docker run -p 8080:80 nginx).
  2. Дивиться на активний контекст (з яким daemon спілкуватися).
  3. Будує HTTP-запит з відповідними заголовками (Content-Type: application/json, API-версія).
  4. Шле через socket; читає відповідь.
  5. Форматує відповідь (таблиця, JSON, кастомний шаблон через --format).

CLI настільки тонкий, що його можна замінити на curl для будь-якої операції, і багато docs роблять рівно це.

Транспорт: Unix socket vs TCP

За замовчуванням dockerd слухає Unix socket: /var/run/docker.sock. Local-only, швидко, захищено permissions файлу.

bash
$ ls -l /var/run/docker.sock srw-rw---- 1 root docker 0 Apr 30 10:00 /var/run/docker.sock # Mode 660: owner=root, group=docker. Щоб юзати docker без sudo, # додай себе у групу `docker`.

Для remote-керування daemon може слухати TCP. Налаштовуєш у daemon.json:

json
{ "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"], "tls": true, "tlsverify": true, "tlscacert": "/etc/docker/ca.pem", "tlscert": "/etc/docker/server-cert.pem", "tlskey": "/etc/docker/server-key.pem" }

Тоді спрямуй remote-client на нього:

bash
$ DOCKER_HOST=tcp://docker.example.com:2376 docker ps

Завжди вмикай TLS для TCP-експозиції. Незахищений tcp://0.0.0.0:2376 це remote root shell, що чекає, коли йому скористаються, Docker API може запустити будь-який container з --privileged, монтувати шляхи з хоста і фактично отримати машину.

Docker-контексти

Один CLI, кілька daemon. Корисно, коли ти керуєш локальним dev плюс віддаленим prod або staging.

bash
$ docker context create staging \ --docker "host=ssh://user@staging.example.com" $ docker context ls NAME DESCRIPTION DOCKER ENDPOINT default Local Docker unix:///var/run/docker.sock staging ssh://user@staging.example.com $ docker context use staging $ docker ps # тепер показує container на staging, через SSH

Жодного більше жонглювання DOCKER_HOST=... env-змінними. Перемикання однією командою.

API-версіонування

Engine API версіонований. CLI шле версію типу /v1.46/...; daemon або приймає, або, якщо застара, повертає downgrade hint. Forward-сумісність означає, що клієнт v25 зазвичай говорить з daemon v23 (і навпаки в розумних межах). Mismatch:

bash
$ docker ps Error response from daemon: client version 1.47 is too new. Maximum supported API version is 1.45.

Фікс: апгрейдь daemon або пінься на клієнт через DOCKER_API_VERSION=1.45.

Типові помилки

Додавати себе у групу docker як security-«зручність»

bash
$ sudo usermod -aG docker $USER # Тепер можна юзати docker без sudo. Зручно. Також небезпечно.

Бути у групі docker еквівалентно root. Daemon API може монтувати шляхи з хоста у container і chmod'ити файли, будь-який unprivileged-юзер з доступом до socket має шлях до root. Для дев-машини нормально. Для multi-user сервера ні.

Експозиція TCP без TLS

Знайдено на безлічі інтернет-доступних серверів. dockerd -H tcp://0.0.0.0:2375 (зверни увагу на нешифрований порт) дає будь-якому інтернет-юзеру можливість запустити privileged container і отримати root хоста. Завжди TLS, завжди client cert auth.

Плутати, що CLI зламано і daemon зламано

bash
$ docker ps Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

Помилка чесна: це може бути зупинений daemon (systemctl status docker), відсутній socket-файл або проблема з permissions (ти не у групі docker). Сам CLI рідко зламано; повідомлення показує на daemon.

Рестартити dockerd, щоб щось пофіксити, і випадково повбивати всі container

З дефолтними налаштуваннями на старіших Docker systemctl restart docker вбивав container. Сучасний Docker має live-restore:

json
// /etc/docker/daemon.json { "live-restore": true }

З цим увімкненим systemctl restart docker тримає container живими крізь рестарт daemon. Containerd-shim на кожен container це те, що робить це можливим.

Реальне застосування

  • Docker Desktop на Mac/Windows: daemon живе всередині маленької Linux VM. Твій docker CLI на хості спілкується з ним через forwarded socket. Це бачиш у Docker Desktop > Settings > Resources, де виділяєш RAM і CPU для VM.
  • CI/CD runners: у кожного runner свій dockerd. Джоби використовують локальний daemon для білду і push image. «Docker-in-Docker» це патерн, де джоба сама крутить dockerd всередині privileged-container, але має security-tradeoff.
  • Remote-host керування: ops-команди використовують docker context через SSH, щоб адмінити віддалений staging чи prod, не даючи кожному адміну TCP-експозований daemon.
  • Rootless Docker: dockerd крутиться під non-root юзером. Кожен юзер отримує свій daemon і socket. Використовується у HPC-кластерах і безпеково чутливих середовищах.

Питання для поглиблення

Q: Що відбувається, якщо daemon помирає, а container запущені?


A: З увімкненим live-restore (дефолт у сучасному Docker) container продовжують крутитися. Containerd-shim на кожен container їх тримає. CLI не може взаємодіяти з daemon у це вікно, але самі container і їхня мережа продовжують працювати. Коли dockerd рестартує, він re-attach'ується.

Q: Чи можуть два клієнти спілкуватися з тим самим daemon одночасно?


A: Так. Daemon серіалізує операції над shared state, але охоче обробляє багато конкурентних клієнтів. CI-ран і твій локальний docker ps проти того самого daemon працюють паралельно нормально.

Q: Чому моя команда висить на docker ps?


A: Майже завжди проблема з daemon. Або він перевантажений (багато конкурентних операцій), або dead-lock'нувся на storage driver, або застряг на завислому containerd. journalctl -u docker і journalctl -u containerd твої перші зупинки.

Q: Чи можна написати свій Docker-клієнт?


A: Так, Engine API задокументований і стабільний. Офіційний Go SDK (github.com/docker/docker/client) це те, що використовує сам docker CLI. Купа сторонніх інструментів (Lazydocker, Portainer, Dockge) це по суті кастомні клієнти поверх того самого API.

Q: (Senior) Який security-ризик монтування /var/run/docker.sock у container?


A: Монтування socket дає тому container повний контроль над Docker daemon хоста. Зсередини він може запустити будь-який container з --privileged, bind-mount'нути / і фактично стати root на хості. Поширене у CI-інструментах, що мають піднімати білд-container (альтернатива DinD), але відомий supply-chain ризик: зловмисний image з доступом до socket ескалює до root хоста. Використовуй rootless Docker, або sysbox, або per-runner dockerds, щоб обмежити радіус ураження.

Приклади

Виклик Engine API напряму

bash
# Список усіх image $ curl --unix-socket /var/run/docker.sock \ http://docker/v1.46/images/json | jq '.[].RepoTags' ["nginx:1.27-alpine"] ["postgres:16", "postgres:latest"] # Створення і старт container у двох API-викликах $ CID=$(curl --unix-socket /var/run/docker.sock \ -H 'Content-Type: application/json' \ -X POST "http://docker/v1.46/containers/create?name=test" \ -d '{"Image":"alpine","Cmd":["echo","hi"]}' | jq -r .Id) $ curl --unix-socket /var/run/docker.sock \ -X POST http://docker/v1.46/containers/$CID/start

Жодного docker CLI. CLI це зручність; API авторитетний.

Перемикання між локальним і віддаленим

bash
$ docker context create prod-eu --docker "host=ssh://ops@prod-eu.example.com" $ docker context create prod-us --docker "host=ssh://ops@prod-us.example.com" $ docker context use prod-eu $ docker ps # container в EU $ docker --context prod-us ps # одноразова команда на US

Один CLI, три daemon (local, EU, US). Файл контекстів у ~/.docker/contexts/ тримає роутинг.

Трасування CLI-виклику для підтвердження протоколу

bash
$ DOCKER_DEBUG=true docker ps 2>&1 | head -10 GET /v1.46/containers/json HTTP/1.1 Host: docker User-Agent: Docker-Client/26.1.0 (linux) Content-Type: application/json HTTP/1.1 200 OK Api-Version: 1.46 Content-Type: application/json Docker-Experimental: false Server: Docker/26.1.0 (linux)

GET-запит, JSON-відповідь, узгодження версії у заголовках. Це все, що є у протоколі.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів