systemd игнорирует код возврата при запуске сервиса
Я столкнулся с этой проблемой, когда писал модуль для одного простого демона. Когда демон запускает '1' при запуске, systemd просто игнорирует его, и похоже, что демон был успешно запущен, пока он фактически мертв.
Например, у меня есть очень простой сценарий оболочки:
#!/bin/bash
exit 1
Итак, unit-файл выглядит так:
[Unit]
Description=test service
After=syslog.target
[Service]
User=testuser
Group=testuser
ExecStart=/usr/local/bin/return1
[Install]
WantedBy=multi-user.target
Пытаясь начать, кажется, хорошо:
# service testservice start
# echo $?
0
Но на самом деле он мертв
# service testservice status
● testservice.service - test service
Loaded: loaded (/etc/systemd/system/testservice.service; enabled)
Active: failed (Result: exit-code) since Fri 2016-01-22 14:51:45 MSK; 1min 13s ago
Process: 16416 ExecStart=/usr/local/bin/return1 (code=exited, status=1/FAILURE)
Main PID: 16416 (code=exited, status=1/FAILURE)
Jan 22 14:51:45 servername systemd[1]: Started test service.
Jan 22 14:51:45 servername systemd[1]: testservice.service: main process exited, code=exited, status=1/FAILURE
Jan 22 14:51:45 servername systemd[1]: Unit testservice.service entered failed state.
Похоже, systemd считает, что демон был успешно запущен, но позже потерпел крах.
Я попытался решить эту проблему, изменив тип службы на "разветвление" и другие - это отлично работает в случае ненулевого кода, но служба на самом деле "проста", поэтому в случае успешного запуска она просто остается и сохраняет Терминал занят.
Как мне управлять такими услугами? Или может быть нужно что-то исправить в коде демона?
ОС debian 8 x64, systemd 215
1 ответ
За systemd
чтобы определить, был ли процесс запущен успешно, вы должны использовать Type=forking
затем раскошелите ваш процесс на вспомогательный скрипт и проверьте этот скрипт, если процесс был успешно запущен. С разветвлением systemd
будет ждать ExecStart
команда, чтобы закончить, и он проверит свой статус выхода.
Вы должны изменить свой файл модуля следующим образом:
[Unit]
Description=test service
After=syslog.target
[Service]
Type=forking
User=testuser
Group=testuser
ExecStart=/usr/local/bin/fork_service
[Install]
WantedBy=multi-user.target
И в /usr/local/bin/fork_service
у вас должно быть что-то вроде этого:
#!/bin/bash
# Run your process in background
/path/to/your_service &
# Check if the services started successfully
if ! kill -0 $! 2>/dev/null; then
# Return 1 so that systemd knows the service failed to start
exit 1
fi
Я здесь просто проверяю, активен ли PID фонового процесса, но вы можете сделать любую проверку, какую захотите. Единственно важным является то, что этот скрипт завершается с 0, если процесс запущен успешно, или с положительным ненулевым значением, если он не прошел.
Также вам не нужно использовать Bash для форка процесса, вы можете использовать любой язык, который захотите.
Итак, вы хотите, чтобы systemd дождался инициализации процесса, прежде чем вернуться. Что является разумной чертой. Однако, поскольку инициализация процесса может занять произвольное количество времени, у systemd нет нормального способа узнать, как долго ждать без помощи запускаемого процесса.
Традиционный способ сделать это - использовать демона разветвления. Демон выполняет инициализацию в одном процессе, а затем отключает реальный процесс, как только он установил, что он может успешно инициализироваться. Происходящий форк и успешное завершение исходного процесса - это сигнал для systemd об инициализации демона. Из документации systemd:
Если установлено значение разветвления, ожидается, что процесс, настроенный с помощью ExecStart=, будет вызывать fork() как часть своего запуска. Ожидается, что родительский процесс завершится после завершения запуска и настройки всех каналов связи. Дочерний процесс продолжает выполняться как основной процесс обслуживания, и диспетчер служб будет считать модуль запущенным при выходе из родительского процесса. Это поведение традиционных сервисов UNIX. Если используется этот параметр, рекомендуется также использовать параметр PIDFile=, чтобы systemd мог надежно идентифицировать основной процесс службы. systemd продолжит запуск последующих модулей, как только завершится родительский процесс.
Systemd предлагает другие решения, которые кажутся мне более элегантными. Нет никаких причин для разветвления, просто заставьте демона напрямую сообщать systemd, когда он инициализируется; это тип = уведомить. (см. документацию, которую я связал выше)
Таким образом, чтобы исправить ваш конкретный пример, вы должны изменить свой служебный файл, чтобы сказать Type=notify
а также /usr/local/bin/return1
быть
#!/bin/bash
exit 1
systemd-notify READY=1
Где, очевидно, выход 1 обычно происходит условно, если инициализация не удалась. И куда READY=1 отправляется после завершения инициализации.
Это даст вам ожидаемое сообщение об ошибке в командной строке при попытке его запустить. Чтобы увидеть, что systemd на самом деле ожидает READY=1, вы можете попробовать это с помощью следующего /usr/local/bin/return1
:
#!/bin/bash
sleep 3
systemd-notify READY=1
sleep 1000000