Сервис остаётся в состоянии deactivating, когда systemd уже запустил остановку юнита, но ещё ждёт завершения команды ExecStop=, основного процесса или других процессов в control group. Решение: определить, кого именно ждёт systemd, корректно завершить этот процесс, а затем настроить ограничение времени остановки, чтобы зависание не повторялось.
Предпосылки и ограничения
Команды ниже применимы к системам с systemd и требуют прав root либо доступа через sudo. Вместо my.service укажите фактическое имя юнита. Не отправляйте SIGKILL, пока не проверите, что процесс не выполняет критическую запись в базу данных, файловую систему или другое хранилище.
Фактическое поведение остановки зависит от конфигурации конкретного юнита, значений Type=, KillMode=, TimeoutStopSec=, SendSIGKILL= и команд ExecStop=. Проверять нужно эффективные параметры установленного юнита, а не только файл из /etc/systemd/system.
Шаг 1. Подтвердить состояние и найти текущую операцию
Сначала получите состояние сервиса без постраничного вывода:
sudo systemctl status my.service --no-pager -l
Затем запросите свойства, которые показывают фазу остановки, процессы и фактический тайм-аут:
sudo systemctl show my.service \
-p ActiveState \
-p SubState \
-p Result \
-p MainPID \
-p ControlPID \
-p ExecMainCode \
-p ExecMainStatus \
-p TimeoutStopUSec \
-p KillMode \
-p SendSIGKILL
Интерпретация основных полей:
ActiveState=deactivatingподтверждает, что выполняется остановка.ControlPIDобычно указывает на процесс текущей управляющей команды, напримерExecStop=.MainPIDсодержит PID основного процесса, если systemd его ещё отслеживает.TimeoutStopUSecпоказывает эффективное ограничение времени остановки.KillModeопределяет, какие процессы systemd завершает при остановке.
Проверьте также очередь заданий systemd:
sudo systemctl list-jobs
Если для my.service отображается задание stop, остановка ещё считается выполняющейся. Наличие других ожидающих заданий может объяснить, почему зависят связанные юниты.
Шаг 2. Проверить журнал остановки
Просмотрите сообщения сервиса и systemd за текущую загрузку:
sudo journalctl -u my.service -b --no-pager -n 200
Для наблюдения в реальном времени используйте:
sudo journalctl -u my.service -f
Ищите следующие признаки:
- запуск команды остановки и отсутствие сообщения о её завершении;
- сообщения о превышении времени остановки;
- повторяющиеся попытки закрыть соединения или дочерние процессы;
- ошибки доступа к сокетам, PID-файлам, файловым системам и сетевым ресурсам;
- сообщения вида «process remains running after unit stopped» или аналогичные предупреждения о процессах, оставшихся в cgroup.
Если журнал самого сервиса недостаточен, отфильтруйте сообщения systemd по имени юнита:
sudo journalctl -b --no-pager | grep -F 'my.service'
Шаг 3. Определить зависший процесс
Получите PID основного и управляющего процессов:
sudo systemctl show my.service -p MainPID -p ControlPID
Для ненулевого PID проверьте состояние процесса:
ps -o pid,ppid,stat,etime,wchan:32,cmd -p PID
Замените PID фактическим числом. Значение столбца STAT помогает определить характер зависания:
S— прерываемое ожидание; процесс обычно реагирует на сигналы.R— выполнение на процессоре.D— непрерываемое ожидание ядра, часто связанное с диском, сетевой файловой системой, драйвером или другим вводом-выводом.Z— завершившийся процесс-зомби, который должен быть обработан родительским процессом.
Чтобы увидеть все процессы, относящиеся к сервису, используйте:
sudo systemd-cgls --unit my.service
На некоторых версиях systemd набор параметров systemd-cgls может отличаться. Доступный синтаксис следует проверить локально:
systemd-cgls --help
Если процесс находится в состоянии
D, сигналSIGKILLне гарантирует немедленного завершения: процесс сможет обработать завершение только после возврата из непрерываемого ожидания ядра. В этом случае нужно устранять зависший ввод-вывод, недоступное хранилище, сетевую файловую систему или проблему драйвера. Принудительная перезагрузка должна быть последним вариантом после оценки риска повреждения данных.
Шаг 4. Завершить сервис от мягкого способа к жёсткому
Повторная штатная остановка
Сначала повторите обычную остановку и наблюдайте журнал:
sudo systemctl stop my.service
Если команда ожидает бесконечно в интерактивной сессии, откройте вторую сессию и продолжайте диагностику там. Не запускайте множество параллельных команд stop и restart: они добавляют задания в очередь, но не устраняют причину зависания.
Отправка SIGTERM всем процессам сервиса
Если процессы не завершились штатно, отправьте им сигнал завершения через systemd:
sudo systemctl kill --kill-whom=all --signal=SIGTERM my.service
После этого снова проверьте состояние:
sudo systemctl status my.service --no-pager -l
sudo systemctl show my.service -p ActiveState -p SubState -p MainPID -p ControlPID
Принудительное завершение
Если процесс не реагирует на SIGTERM, данные сохранены или допустима аварийная остановка, отправьте SIGKILL всем процессам юнита:
sudo systemctl kill --kill-whom=all --signal=SIGKILL my.service
Проверьте, исчезли ли процессы:
sudo systemd-cgls --unit my.service
sudo systemctl status my.service --no-pager -l
Если PID остался и имеет состояние D, повторная отправка сигналов не решит проблему. Проверьте сообщения ядра:
sudo journalctl -k -b --no-pager -n 200
Особое внимание уделите сообщениям об ошибках блочных устройств, тайм-аутах I/O, зависших задачах, NFS, FUSE, iSCSI и сбоях файловой системы. Конкретное исправление зависит от источника ожидания: восстановление хранилища, возврат сетевого ресурса, размонтирование проблемной файловой системы или перезагрузка узла в контролируемом порядке.
Шаг 5. Вернуть юнит в рабочее состояние
Когда зависшие процессы завершены, проверьте итоговое состояние:
sudo systemctl is-active my.service
sudo systemctl is-failed my.service
sudo systemctl status my.service --no-pager -l
Если сервис перешёл в failed, сбросьте запомненное состояние ошибки:
sudo systemctl reset-failed my.service
После устранения причины запустите сервис:
sudo systemctl start my.service
Проверьте результат:
sudo systemctl is-active my.service
sudo systemctl status my.service --no-pager -l
sudo journalctl -u my.service -b --no-pager -n 100
Ожидаемый проверяемый результат — команда systemctl is-active выводит active, а в журнале отсутствует новый цикл зависшей остановки. Для одноразового сервиса нормальным итогом может быть inactive; это зависит от Type= и RemainAfterExit=.
Шаг 6. Исправить unit-файл, чтобы зависание не повторялось
Посмотрите полный эффективный unit-файл вместе с фрагментами переопределения:
sudo systemctl cat my.service
Не редактируйте unit-файл из /usr/lib/systemd/system или /lib/systemd/system напрямую: пакет может заменить его при обновлении. Создайте drop-in:
sudo systemctl edit my.service
Пример ограниченной конфигурации остановки:
[Service]
TimeoutStopSec=30s
KillMode=control-group
SendSIGKILL=yes
TimeoutStopSec=30s — пример, а не универсальное значение. Для базы данных, очереди сообщений или приложения с длительным сохранением состояния может потребоваться больше времени. Значение должно превышать нормальную длительность корректного завершения, измеренную по журналам и метрикам.
KillMode=control-group предписывает systemd завершать процессы в control group сервиса, а не только основной процесс. Это обычно предотвращает ситуацию, когда дочерний процесс остаётся жить после завершения родителя. Перед изменением проверьте, не запускает ли сервис намеренно процессы, которые должны переживать его остановку.
SendSIGKILL=yes разрешает systemd применить финальное принудительное завершение после истечения соответствующего тайм-аута. Не используйте SendSIGKILL=no без чёткой эксплуатационной причины: неотзывчивый процесс может оставить юнит в состоянии остановки.
После сохранения проверьте синтаксис unit-файла:
sudo systemd-analyze verify my.service
Затем перечитайте конфигурацию менеджера:
sudo systemctl daemon-reload
Убедитесь, что применились ожидаемые параметры:
sudo systemctl show my.service \
-p TimeoutStopUSec \
-p KillMode \
-p SendSIGKILL
Проверка ExecStop
Если при зависании был задан ненулевой ControlPID, вероятная причина — команда ExecStop=. Получите её эффективное значение:
sudo systemctl show my.service -p ExecStop
Проверьте скрипт остановки по следующим пунктам:
- он не ждёт интерактивного ввода;
- сетевые обращения и обращения к внешним API имеют собственные тайм-ауты;
- цикл ожидания PID ограничен по времени;
- PID-файл проверяется на актуальность, а PID не используется без проверки процесса;
- ошибка остановки возвращает ненулевой код, а не скрывается бесконечным повтором;
- скрипт не запускает фоновый процесс и не завершается раньше фактической остановки сервиса.
Не добавляйте ExecStop=/bin/kill -9 ... как первое средство. Systemd уже умеет отправлять сигналы процессам юнита и учитывать их cgroup. Команда ExecStop= должна выполнять прикладное корректное завершение, а принудительное уничтожение следует оставлять механизму тайм-аута и политике завершения systemd.
Итоговый чек-лист
- Проверить
ActiveState,SubState,MainPIDиControlPID. - Просмотреть журнал сервиса за текущую загрузку.
- Определить состояние зависшего процесса через
ps. - Проверить все процессы cgroup сервиса.
- Сначала отправить
SIGTERM, затем при допустимом риске —SIGKILL. - При состоянии
Dдиагностировать ввод-вывод и сообщения ядра. - После завершения процессов выполнить
reset-failedи запустить сервис. - Настроить обоснованный
TimeoutStopSecи проверитьKillMode. - Проверить unit через
systemd-analyze verifyи подтвердить эффективные параметры черезsystemctl show.
Источники
Ниже приведены официальные страницы проекта systemd. Для точного соответствия установленной версии дополнительно используйте локальные команды man systemctl, man systemd.service и man systemd.kill. Проверить актуальное содержимое онлайн-страниц в текущей среде невозможно, поэтому версионные различия следует сверять с локальной документацией пакета.