Docker в systemd: почему демон не поднимается
Демон Docker в systemd устроен нетипично: рядом с docker.service есть docker.socket, а параметры запуска частично берутся из /etc/docker/daemon.json. Большинство отказов — это ошибка в этом файле или конфликт между способом указания адреса в файле и в unit-файле.
О службе
Первое, что стоит знать: Docker отказывается стартовать при синтаксической ошибке в daemon.json. Проверить файл можно любым разборщиком JSON — сам Docker в журнале печатает строку с ошибкой, но не всегда очевидно.
Второе: адрес прослушивания задаётся либо ключом -H в unit-файле, либо параметром hosts в daemon.json, но не двумя способами одновременно. При конфликте демон завершается с сообщением о том, что адрес указан дважды. Это одна из самых частых поломок после ручной правки.
Третье: Docker сильно зависит от контрольных групп и от версии их иерархии. На системах со смешанной иерархией и в необычных окружениях демон может не запуститься вовсе или запуститься без части возможностей ограничения ресурсов.
Как устроена
| Связанные unit | docker.service и docker.socket, а также containerd.service |
|---|---|
| Файл настроек | /etc/docker/daemon.json — ошибка в нём не даёт демону стартовать |
| Адрес прослушивания | задаётся либо через -H в unit, либо через hosts в daemon.json; одновременно нельзя |
| Каталог данных | /var/lib/docker — растёт быстро, переполнение раздела останавливает демон |
| Зависимость | демон требует работающий containerd; его состояние стоит проверять вместе с docker |
Частые ошибки
32 записи базы отмечены за этой службой.
Коды выхода
- status=1/FAILURE в systemdКод 1/FAILURE означает, что программа запустилась и сама завершилась с ошибкой. Как найти настоящую причину в журнале службы.
- status=219/CGROUP в systemdКод 219/CGROUP: не удалось создать или настроить контрольную группу службы. Причины на уровне системы и контейнеров.
- status=226/NAMESPACE в systemdКод 226/NAMESPACE: не удалось настроить пространства имён монтирования, UTS или IPC. Частая причина — путь в ReadOnlyPaths= или ProtectHome=.
- status=200/CHDIR в systemdКод 200/CHDIR означает, что systemd не смог перейти в каталог из WorkingDirectory= до запуска программы. Причины и решение.
- status=203/EXEC в systemdКод 203/EXEC означает, что systemd не смог выполнить программу из ExecStart=. Разбор причин: путь, права, интерпретатор, синтаксис оболочки.
- status=216/GROUP в systemdКод 216/GROUP: systemd не смог определить или сменить группу из Group= или SupplementaryGroups=. Причины и как проверить.
- status=225/NETWORK в systemdКод 225/NETWORK: не удалось создать сетевое пространство имён для службы при PrivateNetwork=yes или NetworkNamespacePath=.
- status=228/SECCOMP в systemdКод 228/SECCOMP: не удалось применить фильтр системных вызовов из SystemCallFilter=. Причины: неизвестное имя вызова, отсутствие поддержки в ядре.
Ресурсы и ограничения
- No space left on device в журнале службыНет места на устройстве: разбор по свободным блокам, по inode, по журналу systemd и по удалённым, но открытым файлам.
- Too many tasks: упор в предел TasksMaxСлужба не может создать новый процесс: достигнут предел TasksMax. Где он задан и как поднять правильно.
Сообщения журнала
- Address already in use при запуске службыПорт или адрес уже занят: bind() failed (98: Address already in use). Как найти владельца порта и что делать с остатками прежнего процесса.
- Permission denied в журнале службыОтказ в доступе у службы systemd: права на файл, каталоги по пути, пользователь службы, параметры изоляции, SELinux и AppArmor.
- Function not implemented в контейнереОшибка 38 при системном вызове: вызов запрещён фильтром или отсутствует в окружении. Разбор для контейнеров.
- No such file or directory в журнале службыСлужба не находит файл или каталог. Разбор: путь, момент запуска, изоляция unit-файла, символические ссылки, приватный /tmp.
- Read-only file system в журнале службыСлужба не может писать: файловая система только для чтения. Разбор: параметры изоляции unit, монтирование ro, ошибки файловой системы.
- Порт занят, но процесса не видноАдрес занят, а инструменты не показывают владельца: другое сетевое пространство имён, контейнер, сокет systemd.
Зависимости и порядок
- Dependency failed for … и результат 'dependency'Сообщение Dependency failed for: служба не запускалась, потому что упала её зависимость. Как найти настоящего виновника.
Состояния результата
- Failed with result 'oom-kill'Состояние oom-kill: процесс службы убит из-за нехватки памяти. Как понять, упёрлись в предел службы или в память машины.
- Failed with result 'timeout'Состояние timeout: служба не уложилась в отведённое время при запуске, остановке или перезагрузке настроек.
- Job for … failed because a timeout was exceededЗадание на запуск прервано по таймауту. Как отличить медленный старт от заблокированного и правильно настроить TimeoutStartSec.
Сигналы
- signal=KILL (status=9/KILL) в systemdПроцесс службы убит сигналом KILL. Кто мог его послать: OOM-killer, таймаут остановки systemd, администратор.
- signal=SYS (status=31/SYS) в systemdПроцесс службы убит сигналом SYS: фильтр системных вызовов запретил вызов. Как найти запрещённый вызов и исправить SystemCallFilter.
- signal=TERM (status=15/TERM) в systemdПроцесс службы завершён сигналом TERM. Когда это нормальная остановка, а когда признак проблемы.
Ошибки служб
- Docker и kubelet: несовпадение драйвера cgroupДрайвер контрольных групп Docker не совпадает с systemd: предупреждения, нестабильная работа контейнеров, проблемы с kubelet.
- Docker: no space left on device при запуске контейнеровРаздел с /var/lib/docker заполнен образами и слоями. Как посчитать занятое и что можно удалить безопасно.
- Docker: unable to configure the Docker daemon with file daemon.jsonДемон Docker не запускается из-за ошибки в /etc/docker/daemon.json: неверный JSON или неизвестный ключ.
- Docker: unable to configure the Docker daemon — конфликт -H и hostsАдрес прослушивания задан дважды: ключом -H в unit-файле и параметром hosts в daemon.json. Демон отказывается стартовать.
- Docker: демон не запускается без containerddocker.service падает, потому что не работает containerd.service. Разбор зависимости и сокета containerd.
- Docker: имена не разрешаются внутри контейнераКонтейнеры не разрешают имена: конфликт с локальным разрешателем, неверные серверы в daemon.json, отключённый проброс.
- Docker: правила брандмауэра конфликтуют с вашимиПосле перезапуска брандмауэра контейнеры теряют сеть: Docker управляет своими правилами сам.
- Исполнитель задач не берёт работу, хотя служба активнаСлужба исполнителя сборок работает, но задачи не выполняются: нет связи с сервером, не тот токен, исчерпан предел задач.
- Контейнеры не поднимаются при загрузке сервераКонтейнеры с политикой перезапуска не стартуют после перезагрузки: docker не включён, тома на неподмонтированном разделе, своя служба без зависимостей.
Коды выхода этой службы
Числа в status=N ниже 200 назначает сама программа, 200 и выше — systemd, когда не смог подготовить запуск.
Диагностика
Docker без containerd не работает: проверять нужно оба unit.
systemctl status docker --no-pager -l && systemctl status containerd --no-pager | head -8Проверка синтаксиса файла настроек: самая частая причина отказа.
sudo python3 -c "import json;json.load(open('/etc/docker/daemon.json'))" && echo JSON в порядкеСообщения демона с точной причиной завершения.
journalctl -u docker -n 60 --no-pagerРазбор настроек демоном без запуска (в новых версиях).
sudo dockerd --validate 2>&1 | headМесто под образы и контейнеры: переполнение останавливает демон.
df -h /var/lib/dockerПараметры unit, которые тут важны
- Type=notify: демон сообщает systemd о готовности
- ExecReload=перезагрузка настроек сигналом HUP без остановки контейнеров
- LimitNOFILE=демону нужно много дескрипторов при большом числе контейнеров
- TasksMax=контейнеры создают много процессов; предел задач у службы должен это учитывать
- KillMode=в unit Docker обычно process: остановка демона не убивает контейнеры
Частые вопросы
В журнале «unable to configure the Docker daemon with file /etc/docker/daemon.json». Что это?
Ошибка разбора файла настроек: либо неверный JSON, либо неизвестный ключ. Сообщение обычно содержит имя ключа или позицию в файле.
Демон не стартует после добавления -H в unit-файл. Почему?
Скорее всего адрес уже задан в daemon.json ключом hosts. Уберите один из способов: одновременно они конфликтуют.
Источники
- Документация Docker: настройка демона через systemd
- systemd.resource-control(5)
- Воспроизведено на тестовой машине, systemd 255 (Ubuntu 24.04)