Создание службы systemd на примерах
На эту статью меня натолкнула установка клиента qbittorrent, с доступом к нему через webgui. После интсталяции обнаружил что он почему-то не хочет стартовать в виде демона и поэтому встал вопрос о запуске.
Можно было конечно использовать screen или просто запустить с nohup, но я попробовал сделать с помощью systemd, т.к. решил рассмотреть поближе систему которую так и так приходится использовать каждый день и от которой все плюются :). Расмотренно будет только создание трех видов служб (services): simple, forking, oneshot (в этом обзоре только oneshot) и некоторых ключевых опций, короче только самое простое или необходимое. Cтатья не претендует на ховту или что-то в этом роде, т.к. сам простой любитель экспериментировать и линуха. Кто сочтет все написанное бредом не обижусь :). Тестировал на своей fedora 23, на этот момент версия systemd у меня:
$ systemctl --version
systemd 222
Сначала основы, некоторые замечания или то, что многие у кого система c systemd и так знают.
В systemd модули (units) являются по сути просто конфигурационными файлами, аля ini-файл. Их месторасположение с точки зрения увеличения приоритета:
- $ pkg-config systemd --variable=systemdsystemunitdir # директория системных юнитов
- /run/systemd/ для временных, автоматически генерируетмых юнитов. Полезно иногда подсмотреть различные опции. Например в /run/systemd/generator/ лежат сгенерированные из fstab mount-юниты.
- $ pkg-config systemd --variable=systemdsystemconfdir # кастомные юниты и ссылки на системные, где мы расположим и наши
После создания юнита необходимо чтобы systemd перечитала его конфигурацию - если мы хотим немедленно (до перезагрузки) запустить службу, например для тестирования и активировать для последующего автостарта.
$ sudo systemctl daemon-reload
$ sudo systemctl start [служба]
$ sudo systemctl enable [служба]
Для контроля за статусом службы можно использовать например:
$ systemctl -l status [служба] # говорит само за себя, -l чтобы не обрезались сообщения
$ journalctl -b -u [служба] # посмотреть журнал systemd c последней загрузки и сообщения только для данной службы
Конечно завистит от того, куда будет направлен стандартный вывод. По умолчанию в осях с systemd это journal
----------------- cut---------------
Юнит службы состоит из 3 секций которые имеют огромное колличество параметров (по англицки properties - свойства ): [Unit] - описание, порядок запуска, условия и зависимости службы, [Service] - тип службы, команда для запуска бинарника или скрипта, управление поведением службы и выводом информации, [Install] - свойства установки службы для автостарта
Тип службы oneshot.
При этом типе systemd ожидает что процесс будет завершен перед тем как systemd запустит следующие юниты. Таким образом можно выполнить определенные конечные действия, например: создать директорию, изменить права на файл, добавить правила в iptables, сделать backup и т.д. Напишем нашу первую службу с минимальным набором свойств: описанием (параметр Description), типом (параметр Type) oneshot, запуском комманды echo (параметр ExecStart). Этого достаточно, но так как все процессы у службы типа oneshot будут завершены, systemd будет думать что служба загружена, но неактивна. Так что если не посмотреть специально статус, можно подумать что она завершилась с ошибками. Поэтому напоминаем ей что мы живы (параметр RemainAfterExit).
$ sudo nano /etc/systemd/system/test.service
[Unit]
Description=test service
[Service]
Type=oneshot
ExecStart=/usr/bin/echo "Привет мир"
RemainAfterExit=yes
Для команды "echo" прописываем обязательно полный путь, почему - объясню ниже. Сохраняем, перечитывает конфигурацию, запускаем. Небольшое замечание на будущее: так как служба будет всегда считаться активной, делаем всегда рестарт, на всякий случай.
$ su -c "systemctl daemon-reload && systemctl restart test.service"
Смотрим статус, видим что наша служба и запущена и активна.
$ systemctl -l status test.service
test.service - test service
Loaded: loaded (/etc/systemd/system/test.service; static; vendor preset: disabled)
Active: active (exited) since Mo 2016-01-11 12:48:54 CET; 3s ago
Process: 6020 ExecStart=/usr/bin/echo Привет мир (code=exited, status=0/SUCCESS)
Main PID: 6020 (code=exited, status=0/SUCCESS)
Что же произойдет если мы запустим команду, которая длится очень долго или как в случае qbittorent будет писать что-нибудь постоянно. Посмотрим это на примере. Кстати одна особенность oneshot службы - в отличии от других типов, опций ExecStart тут может быть несколько.
$ sudo nano /etc/systemd/system/longtime.service
[Unit]
Description=longtime service
[Service]
Type=oneshot
ExecStart=/usr/bin/echo "Привет"
ExecStart=/usr/bin/sleep 600
ExecStart=/usr/bin/echo "Пока"
RemainAfterExit=yes
Сохраняем, перечитывает конфигурацию, запускаем longtime.service. Консоль висит, ожидая завершения процесса. Стартуем другой терминал, смотрим статус: activating - ожидает активации, другими словами - когда процесс завершиться. В ранних версиях systemd существовал по умолчанию таймаут при старте юнита, потом его убрали. Я видел в багтреке федоры поток негативных отзывов - действительно кто знает сколько будет длиться например полный бэкап? Откроем другой терминал или просто грубо прервем процесс Ctrl+C (это не остановит активацию службы, заключается в использовании cgroup, но об этом потом). И остановим службу.
$ sudo systemctl stop longtime.service
Я упомянул о значениях по умолчанию - действительно даже такой примитивный юнит как наш, имеет оказывается много других свойств, потому как собран из дефолтных конфигов которые являются шаблонами. Посмотреть все текущие (т.е. с уже измененными нами) значения можно командой
$ systemctl show longtime.service
#Если интересует определенное свойсво, то так
$ systemctl show --property=TimeoutStartUSec longtime.service
для меня непонятно почему свойство таймаута старта называется тут TimeoutStartUSec, а в юните должно называться TimeoutStartSec. Конечно мелочь, но из такого и складывается негативное впечатление :(. У вас может быть уже значение больше нуля, у меня 0, что означает ждать до бесконечности. Добавим таймаут в наш скрипт со значение 5 сек.:
[Unit]
Description=longtime service
[Service]
Type=oneshot
ExecStart=/usr/bin/echo "Привет"
ExecStart=/usr/bin/sleep 60
ExecStart=/usr/bin/echo "Пока"
RemainAfterExit=yes
TimeoutStartSec=5
Сохраняем, перечитывает конфигурацию, запускаем, ждем. Через 5 сек. служба безвременно скончается, о чем systemd нам в панике и сообщит.
Теперь важное замечание - вы заметили что мы прописывали полные пути к командам. Действительно systemd не шелл и как, например, у crona при запуске службы отсутствуют переменные окружения. Точнее очень ограничены, поэтому в запускаемых скриптах пишем полные пути и экспортируем нужные переменные. Однако нам доступны некоторые специальные переменные - спецификаторы (man SYSTEMD.UNIT(5) раздел SPECIFIERS), о которых поговорим позже, а также мы можем добавить нужные переменные с помощью опций Environment и EnvironmentFile - путь к файлу в котором с новой строки определяется очередная переменная. Давайте напишем еще короткий юнит: добавим переменную TEXT и напишем полноценный шелл скрипт, где будем использовать ее и спецификатор %N, содержащий короткое имя юнита, который передадим ему в качестве аргумента. Для наглядности я использовал zenity, если у кого нет и не хочет устанавливать можно просто выдать echo. Скрипт в тестовых целях я просто поместил в директорию пользователя:
$ nano /home/testuser/output.sh
#!/bin/sh
export DISPLAY=:0.0
/usr/bin/zenity --info --title "$1" --text "$TEXT"
exit 0
сохраняем, выходим, даем права на выполнение
$ sudo chmod +x /home/testuser/output.sh
создадим службу имени Дарта Вейдера:
$ sudo nano /etc/systemd/system/DarthVader.service
[Unit]
Description=Дарт Вейдер
[Service]
Type=oneshot
ExecStart=/home/testuser/output.sh %N
Environment="TEXT=Люк, я твой отец."
RemainAfterExit=yes
Сохраняем, делаем reload, запускаем DarthVader.service. Кто делал c zenity увидят диалог, в качестве титула имя службы, переменная TEXT тоже благополучно нашлась.
Пару слов о зависимостях: Они указываются в разделе [Unit]. Requires - указанный здесь юнит будет запущен до старта нашей службы. Вслучае неудачи и наша служба не запустится. Wants - наша служба желает чтобы эти юниты были запущены и ей не важен результат. Давайте создадим юнит Skywalker и поставим его папашу в зависимость Requires:
$ sudo nano /etc/systemd/system/Skywalker.service
[Unit]
Description=Скайуокер
Requires=DarthVader.service
[Service]
Type=oneshot
ExecStart=/home/testuser/output.sh %N
Environment="TEXT=Охренеть!."
RemainAfterExit=yes
Перед запуском Скайуокера остановим дарта вейдера (если он был запущен), потому как если зависимость успешно запущена, она больше не будет стартовать заново, это логично.
$ sudo systemctl stop DarthVader.service
$ su -c "systemctl daemon-reload && systemctl restart Skywalker.service"
Мы увидим два диалоговых окна (скорее всего наложенных друг на друга), зависимость стартовала успешно.
На этом моменте я остановлюсь, Мы рассмотрели несколько опций, достаточных для написания полноценной службы типа oneshot. Если кому-то станет интересно, буду писать продолжение
Комментарии
Чингачгук
11 января, 2016 - 16:12
Огромное спасибо! Считал systemd чем-то очень сложным и недоступным. Сейчас вижу, что запилить свою службу смогу самостоятельно. Это даже удобно!
Чингачгук
11 января, 2016 - 19:30
СПС за статью!
Ждём продолжения!
pomodor
11 января, 2016 - 21:05
Кто сочтет бредом, пускай лучше сначала покажет свои статьи. ;)
Информация актуальная, преподнесена доступно, оценка читателей высокая. Можно констатировать, что первый блин не оказался комом. :) Когда у меня появится представление о том, как лучше организовать хранение полезных сисадминских статей, эту статью сразу же внесу в список. Спасибо автору!
jtad
11 января, 2016 - 23:50
спасибо за лестные отзывы. Это конечно капля в море и без рассмотрния хотя бы остальных 2 типов служб знания будут не то что неполные, а может даже вредные. Выкрою время, попробую наваять еще.
Чингачгук
12 января, 2016 - 01:47
комментарий удален
jtad
12 января, 2016 - 02:06
ну так я не навязываюсь, не надо, значит не надо. Просто до некоторых вещей дошел не просто, думал комуто пригодится.
Чингачгук
12 января, 2016 - 11:26
Службы? Не демона? Простите, если придираюсь, просто слух резануло.
jtad
12 января, 2016 - 11:53
service переводится как служба. Я не стал писать огромную статью, потому как и расчитывал на небольшой интерес, думал напишу еще больше, вообще читать никто не станет. Если употреблять слово демон, возникнет путаница — смотрите сами: тип simple — systemd сама демонизирует (fork) ваш скажем например скрипт в котором бесконечный цикл. Тип forking - там главный процесс сам порождает демона, до своей смерти (звучит устрашающе :)), это класический unix daemon.
Texnoline
12 января, 2016 - 13:43
Хм, это скорее всего из той же области, что: командная строка, консоль и терминал в ОС!:)
Для новичков, должно быть понятно — отличие демона от службы!?
jtad
12 января, 2016 - 23:52
В терминологию даже никогда не вдумывался. Коммент от pomodorman в принципе отражает туже суть. Спасибо обоим за дополнения
pomodor
12 января, 2016 - 15:49
В systemd именно службы (services). А вот службы могут запускать сторонние фоновые процессы, которые некоторые люди называют демонами. Как по мне, так лучше избегать использования этого термина.
Чингачгук
8 мая, 2018 - 13:49
Предположительно этот термин ("DAEMON") происходит от слов "Disk And Execution MONitor" program.
Чингачгук
12 января, 2016 - 21:57
Как насчет переименовать статью? Ну там "Демонология для чайников".
Чингачгук
21 февраля, 2016 - 02:11
не хватает секса?
Чингачгук
19 июля, 2017 - 17:44
Спасибо, pomodor, за толковый мануал.
pomodor
19 июля, 2017 - 21:20
Не за что. Но статья не моя, а jtad. :)
Комментировать