Назад | Перейти на главную страницу

systemd игнорирует код возврата при запуске службы

Я столкнулся с этой проблемой при написании unit-файла для одного простого демона. Когда демон возвращает «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

Для 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. (см. документацию, которую я указал выше)

Итак, чтобы исправить ваш конкретный пример, вы должны изменить свой служебный файл, чтобы сказать 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