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

Почему systemd-сервис зависает в состоянии activating и как исправить

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

Состояние activating означает, что systemd запустил процедуру активации юнита, но ещё не получил признак успешного завершения запуска. Исправление зависит от типа сервиса: процесс должен либо продолжить работу, либо завершить стартовый скрипт, либо отправить уведомление готовности, либо создать корректный PID-файл.

Команды ниже применимы к системным сервисам systemd. Выполняйте их с правами, достаточными для чтения журнала и управления юнитом. Вместо example.service укажите имя проблемного сервиса.

1. Зафиксировать фактическое состояние сервиса

Сначала получите краткий статус без попыток немедленно перезапускать сервис:

systemctl status example.service --no-pager -l

Затем запросите свойства, которые определяют текущую фазу запуска:

systemctl show example.service \
  -p LoadState \
  -p ActiveState \
  -p SubState \
  -p Result \
  -p Type \
  -p MainPID \
  -p ControlPID \
  -p ExecMainCode \
  -p ExecMainStatus \
  -p TimeoutStartUSec

Для зависшего запуска обычно характерны значения ActiveState=activating и одно из промежуточных состояний в SubState, например start, start-pre, start-post или auto-restart.

  • ControlPID указывает на процесс управляющей команды, например ExecStartPre= или стартового скрипта.
  • MainPID указывает на основной процесс, если systemd уже смог его определить.
  • Result и ExecMainStatus помогают отличить незавершённый запуск от циклических падений и перезапусков.

Не увеличивайте TimeoutStartSec= до выяснения причины. Большой или бесконечный тайм-аут скрывает зависший скрипт, ошибочный тип сервиса или отсутствие уведомления готовности, но не исправляет их.

2. Найти команду, на которой остановился запуск

Просмотрите журнал текущей загрузки системы:

journalctl -u example.service --boot --no-pager

Для просмотра последних сообщений с точными временными метками используйте:

journalctl -u example.service --boot \
  --since "-10 minutes" \
  -o short-precise \
  --no-pager

Если в журнале нет полезного сообщения, проверьте все команды юнита:

systemctl cat example.service

Особое внимание уделите директивам:

  • ExecStartPre= — подготовительные команды;
  • ExecStart= — основная команда запуска;
  • ExecStartPost= — действия после старта;
  • Type= — условие, по которому systemd считает сервис запущенным;
  • PIDFile= — путь к PID-файлу для некоторых сервисов типа forking;
  • TimeoutStartSec= — максимальная длительность запуска;
  • After=, Requires= и Wants= — зависимости и порядок запуска.

Если SubState=start-pre, зависла одна из команд ExecStartPre=. Если SubState=start-post, проверяйте ExecStartPost=. Эти команды должны завершиться, прежде чем активация продолжится.

Узнать PID выполняемой управляющей команды можно так:

systemctl show example.service -p ControlPID -p MainPID

После этого исследуйте процесс стандартными средствами Linux:

ps -fp PID

Замените PID фактическим числом. Проверьте, не ожидает ли команда пользовательского ввода, недоступного файла, сокета, сетевого ресурса, блокировки или завершения дочернего процесса.

3. Проверить соответствие Type поведению программы

Type=simple

При Type=simple процесс из ExecStart= должен оставаться в foreground. Не следует запускать его через оболочку с добавлением & и не следует включать внутреннюю демонизацию, если программа умеет работать на переднем плане.

Проблемный вариант:

[Service]
Type=simple
ExecStart=/usr/local/bin/example --daemon

Если параметр --daemon заставляет программу породить фоновый процесс и завершить исходный, systemd может отслеживать не тот процесс. Предпочтительный вариант для программы с foreground-режимом:

[Service]
Type=simple
ExecStart=/usr/local/bin/example --foreground

Точное имя параметра зависит от программы. Не добавляйте --foreground, если приложение его не поддерживает; проверьте его встроенную справку или официальную документацию.

Type=notify

При Type=notify сервис остаётся в activating, пока systemd не получит уведомление READY=1 через механизм sd_notify. Если приложение не реализует этот протокол, тип выбран неверно.

Проверьте настройки:

systemctl show example.service -p Type -p NotifyAccess

Возможны два корректных решения:

  1. Настроить приложение так, чтобы оно отправляло READY=1 после фактической готовности.
  2. Изменить Type=notify на тип, соответствующий реальному поведению программы, например simple или exec.

Не подменяйте уведомление готовности произвольной задержкой через sleep: время запуска может измениться, а systemd всё равно не получит достоверного сигнала готовности.

Type=oneshot

Для Type=oneshot команда ExecStart= должна завершиться. Пока она работает, юнит остаётся в состоянии активации. Такой тип подходит для конечной операции: создания каталога, применения конфигурации, выполнения миграции или однократной настройки.

Типичная ошибка — запуск постоянного серверного процесса как oneshot:

[Service]
Type=oneshot
ExecStart=/usr/local/bin/example-server

Если example-server должен работать постоянно, используйте подходящий тип, обычно simple, exec или поддерживаемый приложением notify.

RemainAfterExit=yes не заставляет зависшую команду завершиться. Эта директива лишь позволяет считать юнит активным после успешного завершения команды.

Type=forking

При Type=forking стартовый процесс должен породить фоновый процесс и завершиться. Если стартовая команда не выходит или PID-файл не появляется по ожидаемому пути, сервис может долго оставаться в activating.

Проверьте конфигурацию:

systemctl show example.service -p Type -p PIDFile -p MainPID

Затем убедитесь, что путь из PIDFile= совпадает с настройкой самой программы. PID-файл должен содержать PID реально работающего основного процесса. Не создавайте PID-файл вручную и не записывайте в него PID оболочки или стартового скрипта.

Если приложение поддерживает foreground-режим, обычно надёжнее отказаться от внутренней демонизации и использовать Type=simple, exec или notify.

4. Проверить зависимости и незавершённые задания

Сервис может ожидать другой юнит. Просмотрите активные задания systemd:

systemctl list-jobs

Покажите зависимости проблемного сервиса:

systemctl list-dependencies example.service

Для анализа порядка запуска используйте:

systemd-analyze critical-chain example.service

Эта команда помогает увидеть цепочку юнитов, повлиявших на момент запуска. Она не доказывает, что последний показанный юнит завис: вывод нужно сопоставлять с журналом и текущими заданиями.

Проверьте зависимые mount-, socket-, network- и device-юниты отдельно:

systemctl status dependency.unit --no-pager -l
journalctl -u dependency.unit --boot --no-pager

Замените dependency.unit именем конкретной зависимости. Не добавляйте After=network-online.target автоматически: это оправдано только тогда, когда сервис действительно требует настроенной сети до запуска, а в системе корректно реализовано ожидание network-online.

5. Проверить синтаксис и итоговую конфигурацию юнита

Файл из /usr/lib/systemd/system или /lib/systemd/system может быть переопределён drop-in-файлом из /etc/systemd/system. Поэтому проверяйте не только исходный файл пакета, а объединённую конфигурацию:

systemctl cat example.service

Для статической проверки используйте:

systemd-analyze verify /etc/systemd/system/example.service

Если юнит поставляется пакетом и изменяется через drop-in, создайте или отредактируйте переопределение штатной командой:

systemctl edit example.service

Например, исправление типа и тайм-аута может выглядеть так:

[Service]
Type=simple
TimeoutStartSec=30s

Это только пример структуры. Значение Type должно соответствовать программе, а тайм-аут — допустимой продолжительности её запуска.

После изменения файлов перечитайте конфигурацию менеджера:

systemctl daemon-reload

6. Остановить зависший запуск и проверить исправление

Сначала попробуйте штатную остановку:

systemctl stop example.service

После исправления конфигурации сбросьте сохранённое состояние ошибки и запустите сервис:

systemctl reset-failed example.service
systemctl start example.service

Проверяйте результат сразу тремя способами:

systemctl is-active example.service
systemctl status example.service --no-pager -l
journalctl -u example.service --boot -n 50 --no-pager

Для постоянно работающего сервиса ожидается состояние active. Для успешно завершившегося oneshot итог зависит от RemainAfterExit=: без него юнит может перейти в inactive, что само по себе не означает ошибку.

Практическая последовательность диагностики

  1. Получить ActiveState, SubState, Type, MainPID и ControlPID.
  2. Просмотреть журнал юнита за текущую загрузку.
  3. Определить, зависла ли команда ExecStartPre, ExecStart или ExecStartPost.
  4. Сопоставить Type= с реальным поведением приложения.
  5. Для notify проверить отправку READY=1.
  6. Для oneshot убедиться, что команда должна завершаться.
  7. Для forking проверить завершение родительского процесса и корректность PIDFile=.
  8. Проверить незавершённые задания и зависимости.
  9. Проверить итоговую конфигурацию через systemctl cat и синтаксис через systemd-analyze verify.
  10. Выполнить daemon-reload, запустить сервис и подтвердить результат по статусу и журналу.

Ограничения по версиям

Набор свойств, точные промежуточные состояния и отдельные возможности systemd различаются между выпусками дистрибутивов. Для установленной системы приоритет имеют локальные справочные страницы:

man systemd.service
man systemd.unit
man systemctl
man journalctl
man systemd-analyze
man sd_notify

Ссылки ниже ведут на официальную документацию проекта systemd для актуальной опубликованной версии. Она может отличаться от версии systemd в конкретном дистрибутиве.

Источники