Архитектура Docker
Что происходит между командой docker run и работающим процессом.
Общая схема
ваш терминал
│
│ docker run -d --name api образ
▼
┌───────────────┐
│ docker (CLI) │ преобразует аргументы в HTTP-запросы
└───────┬───────┘
│ HTTP через /var/run/docker.sock
▼
┌───────────────────────────────────────────────┐
│ dockerd (демон, работает от root) │
│ ───────────────────────────────────────── │
│ образы, тома, сети, сборка, API │
└───────┬───────────────────────────────────────┘
│ gRPC
▼
┌───────────────────────────────────────────────┐
│ containerd │
│ жизненный цикл container'ов, снимки, задачи │
└───────┬───────────────────────────────────────┘
│ создаёт по одному на container
▼
┌───────────────┐
│ containerd- │ остаётся жить: держит stdio,
│ shim │ код возврата, переживает
└───────┬───────┘ перезапуск демона
│ вызывает один раз и ждёт
▼
┌───────────────┐
│ runc │ настраивает namespaces, cgroups,
└───────┬───────┘ seccomp — и ЗАВЕРШАЕТСЯ
│ exec
▼
┌───────────────┐
│ ваш процесс │ PID 1 в своём namespace
└───────────────┘
Как читать
Стрелки — вызовы, а не «содержится внутри». Ни один уровень не выполняется внутри другого: это отдельные процессы, общающиеся через сокеты.
Что здесь неочевидно
runc завершается. После настройки окружения он делает exec — то есть заменяет себя вашим процессом. В списке процессов работающего container'а runc отсутствует:
pgrep -c runc # 0 при десятке работающих container'ов
Родитель вашего процесса — shim, а не демон. Отсюда следует свойство, которое иначе выглядит магией: перезапуск dockerd не убивает container'ы. Некому убивать — они ему не принадлежат.
PID=$(docker inspect имя --format '{{.State.Pid}}')
ps -o ppid= -p "$PID" # PID shim'а, не dockerd
CLI не делает ничего. Всё, что он умеет, — составить HTTP-запрос:
curl -s --unix-socket /var/run/docker.sock \
http://localhost/v1.44/containers/json | python3 -m json.tool | head
Отсюда следствие про безопасность: доступ к сокету равен правам root на хосте — можно попросить демон создать container с любыми параметрами.
Один docker run — это два запроса
POST /containers/create ──► container существует, не запущен
(Pid = 0)
POST /containers/ID/start ──► container работает
(Pid = настоящий)
Между ними container можно осмотреть:
ID=$(docker create --name проба образ)
docker inspect проба --format '{{.State.Status}} {{.State.Pid}}' # created 0
docker start проба
docker inspect проба --format '{{.State.Status}} {{.State.Pid}}' # running 12345
Где что искать при отказе
| Симптом | Уровень | Как смотреть |
|---|---|---|
| CLI не отвечает | Сокет или демон | systemctl status docker |
| Ошибка при создании | Демон | journalctl -u docker --since '5 min ago' |
| Container не стартует | runc | docker logs, код возврата 125–127 |
| Процесс упал | Приложение | docker logs, код возврата приложения |
| Процесс убит | Ядро | dmesg, docker events --filter event=oom |
Последняя строка — та, где docker logs бесполезен: запись делает ядро, а не процесс.
Подробнее
Урок 17.1. Engine API и сокет
Урок 17.2. containerd, shim, runc
Навигация
Все диаграммы
Раздел 17. Docker internals
Главное оглавление