Soft2Soft Ops Практическая база знаний
systemd

Как исправить зависание systemd-сервиса в состоянии deactivating

2 просмотров
Linux systemd диагностика

Сервис остаётся в состоянии 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.

Итоговый чек-лист

  1. Проверить ActiveState, SubState, MainPID и ControlPID.
  2. Просмотреть журнал сервиса за текущую загрузку.
  3. Определить состояние зависшего процесса через ps.
  4. Проверить все процессы cgroup сервиса.
  5. Сначала отправить SIGTERM, затем при допустимом риске — SIGKILL.
  6. При состоянии D диагностировать ввод-вывод и сообщения ядра.
  7. После завершения процессов выполнить reset-failed и запустить сервис.
  8. Настроить обоснованный TimeoutStopSec и проверить KillMode.
  9. Проверить unit через systemd-analyze verify и подтвердить эффективные параметры через systemctl show.

Источники

Ниже приведены официальные страницы проекта systemd. Для точного соответствия установленной версии дополнительно используйте локальные команды man systemctl, man systemd.service и man systemd.kill. Проверить актуальное содержимое онлайн-страниц в текущей среде невозможно, поэтому версионные различия следует сверять с локальной документацией пакета.