Что означает ошибка циклической зависимости systemd
При загрузке systemd строит граф зависимостей между юнитами (службами, таргетами, сокетами и т.д.). Циклическая зависимость возникает, когда два или более юнита указывают друг на друга как на требуемые (через Requires, Wants, PartOf или After в сочетании с другими директивами), образуя замкнутый цикл. Systemd не может определить порядок запуска таких юнитов и прерывает процесс, выводя ошибку.
Типичные симптомы:
- В логах (
journalctl -xeилиsystemctl status <юнит>) появляется сообщение:Dependency cycle detected: foo.service → bar.service → foo.service. - Службы, вовлечённые в цикл, переходят в состояние
failedилиdead. - Система может зависнуть на этапе загрузки (например, на
Starting...). - Команда
systemctl list-dependenciesдля проблемного юнита завершается с ошибкой или покажет зацикленную цепочку.
Эта ошибка критична: она предотвращает запуск как минимум одной службы, а в худшем случае — всю загрузку системы.
Причины возникновения
- Неправильно настроенные директивы зависимостей в юнит-файлах. Например,
serviceA.serviceимеетRequires=serviceB.service, аserviceB.service—Requires=serviceA.service. - Цепочка зависимостей через целевые юниты (targets). Например,
multi-user.targetзависит отnetwork.target, который зависит от службы, требующейmulti-user.target. - Конфликтующие директивы. Сочетание
Requires/WantsсConflictsилиBefore/Afterможет создать неявный цикл. - Ошибки в пакетах. При установке стороннего ПО его юнит-файлы могут содержать циклы, особенно если пакет некорректно объявляет зависимости.
- Ручное редактирование без проверки. Администратор мог добавить зависимость, не учтя уже существующие связи.
Способы решения
Способ 1: Ручной анализ и исправление юнит-файлов
Этот метод подходит, когда вы знаете примерные имена проблемных юнитов.
- Определите юниты, участвующие в цикле. Используйте:
systemd-analyze critical-chain
Команда покажет цепочку загрузки и укажет, где возникает задержка или цикл. Ищите повторяющиеся имена.
Или для конкретного юнита:systemctl list-dependencies <имя_юнита> --all
Если есть цикл, вывод будет прерываться или содержать одни и те же юниты несколько раз. - Визуализируйте граф зависимостей (опционально, но полезно). Установите
graphviz:sudo apt install graphviz # Debian/Ubuntu sudo yum install graphviz # CentOS/RHEL
Затем сгенерируйте SVG-файл:systemd-analyze plot > dependencies.svg
Откройтеdependencies.svgв браузере и найдите цикл (замкнутый контур стрелок). - Найдите файлы юнитов. Для каждого юнита в цикле определите, откуда он загружен:
systemctl status <имя_юнита> | grep Loaded
Вывод покажет путь:/etc/systemd/system/(пользовательские переопределения) или/lib/systemd/system/(из пакета). - Отредактируйте юнит-файлы. Откройте каждый файл в редакторе (например,
sudo nano /path/to/unit.service). Найдите директивыRequires=,Wants=,After=,Before=. Уберите или измените ссылку, которая замыкает цикл. Например, еслиserviceAтребуетserviceB, аserviceBтребуетserviceA, удалите одну из этих зависимостей (часто можно оставитьWants=вместоRequires=или убратьAfter=). - Перезагрузите конфигурацию systemd:
sudo systemctl daemon-reload - Проверьте результат:
systemctl status <имя_юнита> systemd-analyze critical-chain
Ошибка должна исчезнуть.
Способ 2: Использование systemd-analyze verify для автоматической проверки
systemd-analyze verify проверяет синтаксис и логику юнит-файлов, включая циклы.
- Запустите проверку для всех юнитов:
sudo systemd-analyze verify --fail --no-pager
Флаг--failзаставит команду выходить с ненулевым кодом при ошибках, а--no-pagerотключит постраничный вывод. В выводе будут указаны файлы и строки с проблемами, включая циклические зависимости. - Если вывод слишком длинный, проверяйте по одному юниту:
sudo systemd-analyze verify /etc/systemd/system/foo.service - Исправьте указанные файлы как в Способе 1.
- Перезагрузите демон и проверьте.
Способ 3: Временное отключение проблемных юнитов (mask)
Если цикл предотвращает загрузку системы (например, вы не можете получить доступ к shell), загрузитесь в recovery mode или используйте live-USB.
- Определите цикл через
systemd-analyzeиз recovery-режима или, если система частично загружена, черезjournalctl -b -1(логи предыдущей загрузки). - Временно заблокируйте (mask) один из юнитов в цикле:
sudo systemctl mask <имя_юнита>
Это создаст символическую ссылку на/dev/null, предотвратив загрузку юнита. - Перезагрузите систему. Она должна загрузиться без ошибки (хотя функциональность, связанная с заблокированным юнитом, будет недоступна).
- После загрузки исправьте юнит-файлы (Способ 1), затем разблокируйте:
sudo systemctl unmask <имя_юнита> sudo systemctl daemon-reload sudo systemctl restart <имя_юнита>
Способ 4: Обновление или переустановка пакета
Если цикл вызван пакетом из репозитория (например, после обновления):
- Найдите пакет, которому принадлежит юнит:
dpkg -S /lib/systemd/system/foo.service # Debian/Ubuntu rpm -qf /usr/lib/systemd/system/foo.service # CentOS/RHEL - Обновите пакет до последней версии — ошибка могла быть исправлена в новом релизе:
sudo apt update && sudo apt upgrade <имя_пакета> # Debian/Ubuntu sudo yum update <имя_пакета> # CentOS/RHEL - Если обновление не помогло, переустановите пакет:
sudo apt install --reinstall <имя_пакета> # Debian/Ubuntu sudo yum reinstall <имя_пакета> # CentOS/RHEL
Переустановка заменит юнит-файлы на стандартные, возможно, без цикла. - Перезагрузите systemd и проверьте.
Способ 5: Использование systemd-analyze dump для глубокого анализа
Если предыдущие методы не дали результата, получите полный дамп состояния systemd:
- Создайте дамп:
sudo systemd-analyze dump > systemd-dump.txt - Ищите циклы вручную. Откройте файл и найдите все вхождения
Requires=,Wants=,After=для подозреваемых юнитов. Постройте цепочку на бумаге или в графическом редакторе. - Особое внимание на "want" и "require" цепочки. Часто цикл образуется через
WantedBy=в секциях[Install]. Например,serviceAимеетWantedBy=multi-user.target, аserviceB(который зависит отserviceA) также имеетWantedBy=multi-user.targetи при этомRequires=serviceA. Это не всегда цикл, но еслиserviceAтакже требуетserviceB— цикл возникнет. - Исправьте файлы и перезагрузите демон.
Профилактика
- Тестируйте юнит-файлы перед установкой. Используйте
systemd-analyze verify <файл>. - Избегайте взаимных
Requires=. Если службе A нужна служба B, достаточноRequires=Bв A. Не добавляйте обратную зависимость в B, если это не абсолютно необходимо. - Используйте
Wants=вместоRequires=, если зависимость не критична. Это снизит риск цикла. - Группируйте службы через таргеты. Создайте целевой юнит (например,
myapp.target) и добавляйте в него службы черезWantedBy=. Затем включайте только таргет. - Регулярно проверяйте зависимости после изменений:
systemctl list-dependencies <ключевой_юнит>. - При обновлении пакетов внимательно читайте changelog — там могут быть изменения в зависимостях.
- Держите systemd обновлённым. В новых версиях улучшена обработка зависимостей и добавлены предупреждения.
Эта статья должна помочь вам быстро диагностировать и устранить циклические зависимости в systemd, восстановив работу служб и системы.