Гайд: Траблшутинг циклов зависимостей (Ordering Cycles) в unit-файлах systemd
Природа циклических зависимостей (Deadlock Ordering Cycles) в systemd
При построении дерева запуска системный менеджер systemd создает ориентированный ациклический граф (Directed Acyclic Graph, DAG), узлами которого являются юниты (.service, .mount, .socket, .target), а ребрами — директивы порядка исполнения (After=, Before=) и требования зависимостей (Requires=, Wants=, BindsTo=). Если в конфигурациях пользовательских или системных юнитов возникает взаимно противоположный порядок ожидания (например, Сервис A должен стартовать После Сервиса B, а Сервис B настроен стартовать После Сервиса A), образуется неразрешимый замкнутый цикл:
«systemd[1]: Found ordering cycle on custom-app.service/start
systemd[1]: Found dependency on network-online.target/start
systemd[1]: Found dependency on custom-app.service/start
systemd[1]: Breaking ordering cycle by deleting job network-online.target/start»
Бизнес-риски
Непредсказуемое поведение сервера при загрузке: systemd принудительно удаляет случайную задачу из графа («Breaking ordering cycle»), что приводит к внезапному незапуску критических сервисов (баз данных, сети или стораджа).
Сравнение директив зависимостей и очередности в systemd
| Директива | Категория | Назначение |
|---|---|---|
Wants= / Requires= | Зависимость требования | Определяет, ЧТО должно быть запущено (Wants — мягкая связь, Requires — жесткая). НЕ ЗАДАЕТ ПОРЯДОК! |
After= / Before= | Очередность исполнения | Определяет строго КОГДА юнит должен запуститься относительно другого (After — после, Before — до). |
DefaultDependencies=yes | Неявные зависимости | По умолчанию автоматически добавляет Requires=basic.target и After=basic.target. |
Регламент локализации и исправления циклов зависимостей
Сценарий 1: Локализация цикла в системном журнале journalctl
Найдите точный список юнитов, вовлеченных в замкнутое кольцо:
# 1. Поиск сообщений об обнаружении циклов в журнале загрузки:
journalctl -b -u init.scope | grep -i "ordering cycle"
# 2. Просмотр полной трассировки разорванного цикла:
journalctl -b | grep -B 2 -A 8 "Found ordering cycle"Сценарий 2: Построение и валидация графа зависимостей через systemd-analyze
# 1. Синтаксическая валидация всех unit-файлов на сервере:
sudo systemd-analyze verify /etc/systemd/system/*.service
# 2. Построение графа зависимостей конкретного проблемного сервиса в формате DOT:
systemd-analyze dot custom-app.service | dot -Tsvg -o /tmp/dependencies_graph.svgСценарий 3: Разрешение конфликта между early-boot и late-boot юнитами
Самая распространенная причина цикла: сервис должен стартовать на раннем этапе (например, до монтирования локальных дисков или инициализации сокетов), но имеет включенный флаг DefaultDependencies=yes:
# Пример ошибочного unit-файла (/etc/systemd/system/early-logger.service):
[Unit]
Description=Early Boot Logger
Before=sysinit.target # Хотим запуститься до инициализации системы
# ОШИБКА: По умолчанию включен DefaultDependencies=yes, который неявно добавляет After=sysinit.target!
# Возникает моментальный цикл: Before=sysinit.target + After=sysinit.target
# ПРАВИЛЬНОЕ ИСПРАВЛЕНИЕ:
[Unit]
Description=Early Boot Logger
DefaultDependencies=no # Явно отключаем стандартные скрытые зависимости
After=local-fs.target
Before=sysinit.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/early-logger.sh
[Install]
WantedBy=sysinit.targetСценарий 4: Применение изменений в systemd
# 1. Перезагрузка конфигурации systemd менеджера:
sudo systemctl daemon-reload
# 2. Проверка статуса сервиса:
sudo systemctl restart custom-app.service
sudo systemctl status custom-app.serviceТиповые ошибки администраторов
- Путаница между Requires= и After=: Объявление
Requires=service-b.serviceбез указанияAfter=service-b.serviceприведет к одновременному параллельному старту обоих сервисов без гарантии очередности. - Использование циклических After= в связках сокетов: Добавление
After=app.serviceвнутриapp.socket, когдаapp.serviceуже зависит отapp.socket.
Специалисты ITSTM проведут аудит системных юнитов, развяжут скрытые циклы зависимостей и обеспечат детерминированный порядок старта сервисов.
Частые вопросы (FAQ)
Почему systemd удаляет именно нужный мне сервис при разрыве цикла?
Алгоритм эвристики systemd выбирает для удаления задание с наименьшим системным приоритетом или то, которое проще всего исключить из транзакции запуска, не зная о бизнес-важности вашего приложения.
Что входит в DefaultDependencies=yes для сервисов?
Неявно добавляются зависимости: Requires=sysinit.target, After=sysinit.target, After=basic.target, Before=shutdown.target, Conflicts=shutdown.target.
Как сделать, чтобы сервис запускался строго после полной инициализации сети и базы данных?
Укажите в секции [Unit]: After=network-online.target mysql.service и Wants=network-online.target.
Можно ли увидеть граф зависимостей в текстовом виде в консоли?
Да, выполните: systemctl list-dependencies my-service.service --all.