Сервис Systemd работает без выхода

30

Я создал свой собственный сервис для jekyll, и когда я запускаю сервис, кажется, что он не работает как фоновый процесс, потому что я вынужден ctrl+ cвыходить из него. Он просто остается на переднем плане из-за --watch. Я не уверен, как обойти это и сделать так, чтобы он работал в фоновом режиме. Есть предположения?

# /etc/systemd/system/jekyll-blog.service

[Unit]
Description=Start blog jekyll

[Service]
Type=forking
WorkingDirectory=/home/blog
ExecStart=/usr/local/bin/jekyll build --watch --incremental -s /home/blog -d /var/www/html/blog &
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
User=root
Group=root

[Install]
WantedBy=multi-user.target
madmanali93
источник
systemd запустит ваш процесс и ожидает, что он запустит другой процесс, если вы используете Type=forking. Более того, он не будет работать execStartкак расширение оболочки, так что &в конце никогда не будет восприниматься как фоновый флаг.
Грохмал
мой плохой и я его тестировал. Так должен ли тип быть простым?
madmanali93
2
Если я не ошибаюсь, jekyll - это штука типа рельсов, то есть маленький веб-сервер в ruby. Так что да, Type=simpleбудет уместно. Кроме того, это не то приложение, которое я запускаю от имени пользователя root, по крайней мере, на компьютере, выходящем в Интернет (что может быть не в вашем случае).
горький
Спасибо, да просто работал. Также команда this генерирует статический html для Apache, чтобы jekyll не работал на сервере. Я думаю, что все должно быть в порядке, если он работает от имени пользователя root Не уверен, хотя обсуждал это.
madmanali93
О, хорошо, вот что --incrementalделает :). Да, я не вижу проблем с безопасностью при восстановлении файлов с правами root. Конечно, учитывая, что эти файлы не предоставлены пользователем.
горький

Ответы:

54

Systemd может обрабатывать различные типы сервисов, в частности один из следующих

  • simple - Длительный процесс, который не справляется с самим собой и остается прикрепленным к оболочке.
  • forking - Типичный демон, который разветвляется, отсоединяя его от процесса, который его запускал, эффективно отгоняя себя.
  • oneshot - недолговечный процесс, который, как ожидается, завершится.
  • dbus - Вроде бы просто, но уведомление о завершении запуска процессов отправляется через dbus.
  • notify - Вроде бы просто, но уведомление об окончании запуска процессов отправляется поверх inotify.
  • idle - Вроде бы просто, но двоичный файл запускается после отправки задания.

В вашем случае вы выбрали, Type=forkingчто означает, что systemd ожидает завершения процесса и завершения родительского процесса, что указывает на успешное начало процесса. Однако ваш процесс этого не делает - он остается на переднем плане и поэтому systemctl startбудет зависать бесконечно или до тех пор, пока процессы не завершатся.

Вместо этого вы хотите Type=simple, что по умолчанию, так что вы можете полностью удалить строку, чтобы получить тот же эффект. В этом режиме systemd не ожидает завершения процессов (так как не знает, когда это произошло), и поэтому сразу же продолжает выполнять зависимые службы. В вашем случае их нет, так что это не имеет значения.

Небольшая заметка о безопасности:

Вы запускаете службу от имени пользователя root, это не рекомендуется, поскольку она менее безопасна, чем ее использование в качестве непривилегированного пользователя. Причина этого заключается в том, что если в jekyll есть уязвимость, которая каким-то образом позволяет выполнять команды (возможно, с помощью кода, который он анализирует), то злоумышленнику не нужно ничего делать, чтобы полностью владеть вашей системой. Если, с другой стороны, он запускается как непривилегированный пользователь, злоумышленник может нанести такой же ущерб, как этот пользователь, и теперь должен попытаться получить привилегии root, чтобы полностью владеть вашей системой. Это просто добавляет дополнительный слой, который должен пройти атакующий.

Вы можете просто запустить его под тем же пользователем, на котором работает ваш веб-сервер, но это оставляет вас открытым для другой потенциальной атаки. Если на вашем веб-сервере есть уязвимость, позволяющая пользователю манипулировать файлами в вашей системе, он может изменить сгенерированные html-файлы или, что хуже, исходные файлы и заставить ваш сервер обслуживать все, что ему нужно. Однако, если сгенерированные файлы и исходные файлы доступны для чтения только веб-серверу и доступны для записи другим непривилегированным пользователям, они не смогут так же легко изменить их, атаковав веб-сервер.

Однако, если вы просто обслуживаете статические файлы с этого сервера и обновляете сервер, эти атаки очень маловероятны, но все же возможны. Вы несете ответственность за взвешивание рисков по сравнению с накладными расходами на настройку в зависимости от того, насколько критична ваша система, но оба эти совета очень просты в настройке и практически не требуют затрат на обслуживание.

Майкл Даффин
источник
0

В дополнение к решению @ Michael Daffin , вы также можете использовать инструмент daemonize для достижения использования, forkingкак показано в следующем примере.

Учитывая небольшой сценарий оболочки, который я хочу демонизировать и которым я хочу управлять через systemd, я сохранил его как /home/pi/testscript.sh:

#!/bin/bash

while true;
do
    sleep 1
    echo -n "."
done

Если у вас его еще нет, установите daemonize, например так:

sudo apt install daemonize

Теперь создайте файл определения файловой службы:

sudo vi /etc/systemd/system/testomat.service
# It is not recommended to modify this file in-place, because it will
# be overwritten during package upgrades. If you want to add further
# options or overwrite existing ones then use
# $ systemctl edit testomat.service
# See "man systemd.service" for details.

# copied from https://github.com/bitcoin/bitcoin/blob/master/contrib/init/bitcoind.service and modified by Michael 

[Unit]
Description=Test service
After=network.target

[Service]
ExecStart=daemonize -p /run/testomat/testomat.pid -o /home/pi/testscript.log /home/pi/testscript.sh
TimeoutSec=1200

# Make sure the config directory is readable by the service user
PermissionsStartOnly=true

# Process management
####################
Type=forking
PIDFile=/run/testomat/testomat.pid
Restart=on-failure
GuessMainPID = true

# Directory creation and permissions
####################################

# Run as pi:pi
User=pi
Group=pi

# /run/testomat
RuntimeDirectory=testomat
RuntimeDirectoryMode=0710

# /var/lib/testomat
StateDirectory=testomat
StateDirectoryMode=0710

# Hardening measures
####################

# Provide a private /tmp and /var/tmp.
PrivateTmp=true

# Mount /usr, /boot/ and /etc read-only for the process.
ProtectSystem=full

# Allow access to /home, /root and /run/user
# Chosing "false" is actually no hardening, this is just to demonstrate the usage of a service. Well, I could have omitted it. True. :)
ProtectHome=false

# Disallow the process and all of its children to gain
# new privileges through execve().
NoNewPrivileges=true

# Use a new /dev namespace only populated with API pseudo devices
# such as /dev/null, /dev/zero and /dev/random.
PrivateDevices=true

# Deny the creation of writable and executable memory mappings.
MemoryDenyWriteExecute=true

[Install]
WantedBy=multi-user.target

Недавно созданный сервис должен быть объявлен для systemd:

systemctl daemon-reload

Теперь вы можете запустить сервис и скрипт вилки. Как и ожидалось, запуск службы сразу возвращается в оболочку. Результат очевиден:

$ tail -f testscript.log 
.....................

Майкл
источник
В чем преимущество использования daemonize+ Type=forkingвместо того, Type=simpleчтобы позволить systemd позаботиться о запуске службы? Type=forkingэто своего рода параметр совместимости в systemd для поддержки устаревших программ, написанных на fork.
Йохан Мирен
Я думаю, что это эквивалентное решение; Я просто хотел предоставить оператору альтернативное решение и рассказать ему об этом инструменте, который я уже использовал в /etc/init.d, поскольку вопрос также состоит в том, как демонизировать процесс.
Майкл