Несмотря на большое количество проблем и ненависть некоторых пользователей, systemd уже стал стандартом де-факто во многих дистрибутивах Linux. С его помощью из десяток строк настроек можно за несколько минут создать несложный процесс . В то же время многие более интересные возможности документированы не очень понятным языком или требуют углубленных познаний в тонкостях работы systemd.
Основы создания сервиса Linux
Если вы еще никогда не делали свои сервисы, начнем с основ. Systemd оперирует абстрактными единицами (unit), которые бывают разных типов, могут предоставлять различные ресурсы (процессы, сокеты, абстрактные «цели») и требовать других ресурсов для запуска.
Самый распространенный вид ресурса — сервис (service). Файлы с описаниями сервисов и всего прочего лежат в каталоге /lib/systemd/system/. Чтобы systemd нашел новый сервис, достаточно положить в этот каталог свой файл. Если этот сервис ранее не существовал, systemd прочитает файл и загрузит его в память. Однако, если вы редактируете файл ранее запущенного сервиса, не забудьте заставить systemd перечитать файлы командой sudo systemctl daemon-reload!
РЕКОМЕНДУЕМ:
Как защититься от руткитов в Linux
Сервисы типа oneshot — долой rc.local
Когда-то основным способом добавить выполнение команд в загрузку системы было дописать их в /etc/rc.local. Очевидный недостаток — нет способов следить, насколько успешно они выполнились. В systemd легко создать для такой цели свой сервис типа oneshot, и им можно будет управлять через systemctl, как любым другим. В этом случае systemd выполнит команду и посчитает запуск сервиса успешным, если она завершилась с кодом ноль.
Сохраним следующий файл в /lib/systemd/system/dumb-test.service:
1 2 3 4 5 6 7 8 9 |
[Unit] Description=Dumb test [Service] ExecStart=/bin/true Type=oneshot [Install] WantedBy=multiuser.target |
Дополнительных действий не требуется, и теперь вы можеыр делать с ним все то же, что с системными сервисами: запустить с помощью sudo systemctl start dumb-test.service, поставить на загрузку с помощью sudo systemctl enable dumb-test.service и так далее.
Создание сервиса из любой программы
Любой долгоживущий процесс можно легко превратить в сервис с помощью опции Type=idle. В этом случае systemd перехватит стандартные потоки ввода-вывода и будет следить за жизнью процесса.
Для демонстрации напишем программу на Python, которая просто выводит сообщение в бесконечном цикле. Сохраним в /usr/local/bin/test.py следующее:
1 2 3 4 5 |
import time while True: print("Test service is alive") time.sleep(5) |
Затем создадим для нее файл сервиса в /lib/systemd/system/smart-test.service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[Unit] Description=Smart test [Service] ExecStart=/usr/bin/python3 -u /usr/local/bin/test.py Type=idle KillMode=process SyslogIdentifier=smart-test SyslogFacility=daemon Restart=on-failure [Install] WantedBy=multiuser.target |
Теперь можно запустить наш сервис и убедиться, что он работает:
1 2 3 4 5 6 7 8 9 |
$ sudo systemctl status smart-test.service smart-test.service - Smart test Loaded: loaded (/usr/lib/systemd/system/smart-test.service; static; vendor preset: disabled) Active: active (running) since Fri 2019-10-25 16:25:18 +07; 1s ago Main PID: 19893 (python3) Tasks: 1 (limit: 4915) Memory: 3.5M CGroup: /system.slice/smart-test.service └─19893 /usr/bin/python3 -u /usr/local/bin/test.py |
Стандартный вывод программы пишется в journald, и его можно увидеть в journalctl -u smart-test. Ради интереса посмотрим на работу опции Restart=on-failure. Остановим наш процесс с помощью kill -9 ${Main PID} и заглянем в логи:
1 2 3 4 5 6 7 8 9 10 |
systemd[1]: Started Smart test. Test service is alive Test service is alive smart-test.service: Main process exited, code=killed, status=9/KILL smart-test.service: Failed with result 'signal'. smart-test.service: Service RestartSec=100ms expired, scheduling restart. smart-test.service: Scheduled restart job, restart counter is at 1. Stopped Smart test. Started Smart test. Test service is alive |
Для настоящих демонов нужно использовать тип forking, но мы не будем вдаваться в детали — авторы таких пакетов наверняка все уже знают сами.
Зависимости и порядок запуска
Опций для настройки зависимости в systemd очень много. Прежде всего нужно отметить, что в нем есть два независимых механизма для указания порядка запуска сервисов и зависимостей между ними.
Порядок запуска сервисов
Порядок запуска сервисов определяется опциями Before и After. Если в настройках сервиса foo написано After=bar.service и оба сервиса должны запуститься, то systemd сначала выполнит попытку запустить bar, а затем foo.
Однако опция After=bar.service сама по себе не поставит сервис на загрузку. Более того, она никак не повлияет на решение запускать foo, даже если запуск bar завершится неудачей.
Причина существования этих опций — способность systemd запускать сервисы параллельно.
РЕКОМЕНДУЕМ:
Советы и трюки для настройки сети в Linux
Для примера возьмем типичный веб-сервер с набором из веб-приложения FCGI, СУБД и обратного прокси. В каком порядке запускать процесс FCGI и обратный прокси, не так важно. Запросы будут работать, только когда они оба запущены, но «неверный порядок» никак не помешает им запуститься.
Если веб-приложение требует данных из базы для инициализации, то мало убедиться, что и процесс FCGI, и СУБД запущены, — приложение нужно запускать только после полного запуска СУБД. Именно для этих случаев и предназначены опции Before/After.
Зависимости
Зависимости бывают двух видов: мягкие и жесткие. Если оба сервиса запустились успешно, то никакой разницы между ними нет. Различие вступает в действие, если один из сервисов не смог запуститься: если зависимость мягкая, то зависимые сервисы все равно будут запущены, а если жесткая, то systemd не станет даже пробовать их запустить.
Мягкие зависимости указываются с помощью опции Wants= в секции [Unit]. Пример из sshd.service:
1 2 3 4 5 |
[Unit] Description=OpenSSH server daemon Documentation=man:sshd(8) man:sshd_config(5) After=network.target sshd-keygen.target Wants=sshd-keygen.target |
Цель sshd-keygen.target, очевидно, генерирует ключ хоста, если он отсутствует. Технически sshd не сможет запуститься без ключа хоста, поэтому, почему авторы решили сделать зависимость мягкой, можно только догадываться. Возможно, они посчитали, что в большинстве случаев ключ уже существует и устаревший ключ лучше неработающего SSH.
При копировании настроек из пакетов дистрибутива нужно быть осторожным. Разработчики дистрибутивов тоже люди и вполне могут создавать неоптимальные или ошибочные конфигурации. Кроме того, если что-то работает для одного сервиса, совсем не факт, что оно же подойдет для другого, так что сверяйтесь с документацией и тестируйте перед выпуском.
Жесткие зависимости можно указать с помощью опции Requires. К примеру, в systemd-journal-flush.service есть опция Requires=systemd-journald.service — очевидно, отправить команду journald невозможно, пока он не запущен.
У этой опции существуют вариации, например RequiresMountsFor. Посмотрим в файл logrotate.service:
1 2 3 4 |
[Unit] Description=Rotate log files Documentation=man:logrotate(8) man:logrotate.conf(5) RequiresMountsFor=/var/log |
Для работы logrotate нужен доступ к каталогу с логами и больше ничего. Опция RequiresMountsFor=/var/log позволяет выразить именно это: сервис запустится, как только будет примонтирован каталог, содержащий путь /var/log, даже если он находится не в корневом разделе.
Внедрение в зависимости к чужим сервисам
В системах с System V init добавить что-то в зависимости к чужому сервису можно было, лишь отредактировав его скрипт. Такие изменения, очевидно, не переживут обновления системы, поэтому сделать их постоянными можно было бы только пересборкой пакета.
В systemd есть несколько способов решить эту проблему. Если нужно именно внедриться к кому-то в зависимости, можно попробовать опции обратных зависимостей: WantedBy и RequiredBy. Они должны находиться в секции [Install], а не [Unit]. Подводных камня здесь два: они обрабатываются только при установке сервиса с помощью systemctl enable и, как все в systemd, не всегда нормально работают во всех версиях.
Второй вариант, который позволяет менять любые настройки: скопировать файл сервиса в /etc/systemd/system/. Если один файл присутствует в обоих каталогах, то файл из /etc/systemd/system имеет приоритет.
Третий, менее радикальный вариант: создать файл вида /etc/systemd/system/${unit}.d/local.conf и прописать туда только нужные настройки.
Для примера притворимся, что наш сервис smart-test вовсе и не наш, и добавим ему в зависимости sshd.service третьим способом. Создадим файл /etc/systemd/system/smart-test.service.d/local.conf со следующим содержанием:
1 2 |
[Unit] Requires=sshd.service |
Теперь sshd.service будет запущен вместе со smart-test.service, даже если раньше был выключен.
Если важен не только сам факт запуска обоих сервисов, но и порядок их запуска, не забудьте указать его с помощью опций Before или After.
Зависимости по умолчанию
Нужно отметить, что systemd создает для каждого сервиса зависимости по умолчанию. В большинстве случаев это скорее благо, потому что пользовательские сервисы обычно требуют полностью инициализированной системы для своей работы. Но если вы хотите добавить новый шаг в процесс загрузки системы, вам нужно избавиться от этих зависимостей. Это можно сделать опцией DefaultDependencies=no.
Вот такой шаблон неплохо работает для сервисов, которые должны запускаться как можно раньше:
1 2 3 4 5 6 |
[Unit] Description=My early boot step DefaultDependencies=no After=systemd-remount-fs.service |
Просмотреть зависимости сервиса и убедиться, что там нет лишнего, можно командой systemctl list-dependencies ${unit}.
РЕКОМЕНДУЕМ:
Мониторинг в Linux с помощью командой строки
Итого
Systemd можно любить или ненавидеть, но игнорировать его невозможно — нужно уметь с ним работать. Надеюсь, эти знания помогут вам обратить systemd себе на пользу.
Почему это файлы сервисов предлагается хранить в директории, когда пакеты кладут свои файлы? для этого есть /etc/systemd/system/
Вячеслав, может, именно потому, что в «/etc/systemd/system/» лежат символьные ссылки на файлы из «/lib/systemd/system»?
Даниил Батурин, Вы молодец. Благодаря Вашей статье я настроил запуск своего сервиса управления реле USB.