Systemd: пишем собственные .service и .target
У меня появился Linux на домашнем компьютере, и я поспешил обжиться в новой ОС. Она была установлена с systemd init process. Это было мое первое знакомство с этим новым инструментом. Cвой ноутбук я использую для каждодневной жизни и для программирования. Мне хотелось включать рабочие программы (Apache2 и MySQL) только на время, пока я их использую, чтобы не тратить впустую ресурсы своего компьютера. Дополнительно, для тестирования я написал bash скрипт, который выгружает содержимое одной из MySQL БД c жесткого диска в ОЗУ (в tmpfs) – так тесты выполняются значительно быстрее. По идее, я мог бы начинать свой рабочий день вот так:
systemctl start apache2.service systemctl start mysqld.service /root/scripts/mysqld-tmpfs start
systemctl stop apache2.service systemctl stop mysqld.service /root/scripts/mysqld-tmpfs stop
Чего я хотел?
- Мне было лень писать 2 команды (запуск apache и запуск mysql), т.к. я знал, что обе программы всегда будут выключаться и включаться синхронно. Хотелось выполнять эту операцию одной командой.
- Дело попахивало неприятностями, если компьютер перезагрузится пока моя база данных будет сидеть в tmpfs – все файлы будут потеряны. Конечно, я делал бекапы, но мне опять же было лень восстанавливать их вручную после каждой непредвиденной перезагрузки.
Что я сделал?
В итоге я объединил Apache2 и MySQL в один target. Это позволило запускать оба сервиса одной командой. А свой mysqld-tmpfs скрипт я декларировал в виде сервиса в глазах systemd. Будучи сервисом, я уверен, что systemd выполнит его корректную остановку, если система пойдет на перезагрузку или еще в какую-то нештатную ситуацию, и моя БД без потерь сохранится на жесткий диск.
Что такое service?
Это некоторая программа, которая выполняется в фоне и предоставляет полезную функциональность. К примеру, Apache веб сервер. Сервисы можно запускать и останавливать. Некоторые сервисы могут запускаться и останавливаться автоматически по определенным событиям (загрузка ОС, выгрузка ОС и тп). Так же их можно запускать/останавливать вручную. Сервис декларируется в /etc/systemd/system/my-name.service файлах (с суффиксом “.service”).
Что такое target?
Target в systemd очень похож на runlevel в openRC, но это все-таки разные вещи. Во-первых, target позволяет группировать 1 и более сервисов в единый блок. Группируя сервисы в targets, ими проще управлять. Во-вторых, systemd автоматически включает/выключает targets по событиям. “Включение” target означает включение всех сервисов, которые он объединяет в себе. К примеру, если в systemd настроен target по умолчанию my-favorite.target, то при загрузке системы systemd включит все сервисы, которые задекларированы внутри my-favorite.target. В какой-то момент в консоли можно набрать:
systemctl isolate my-another.target
Все сервисы из my-another.target будут включены, и все включенные сервисы не из my-another.target будут выключены. Это очень похоже на переключение runlevel в openRC. Однако, systemd поддерживает включение более чем 1 target. Вот пример:
# Эксклюзивно включаем my-favorite.target и выключаем все остальные сервисы systemctl isolate my-favorite.target # К уже запущенным сервисам и targets добавляем еще 1 target systemctl start my-another.target
После выполнения этих команд в системе будет работать объединение сервисов из my-favorite.target и my-another.target.
Как я это сделал?
Description=Mount a MySQL database into tmpfs. # Мой /root/scripts/tmpfs скрипт может работать как при включенном, так и при выключенном mysql сервисе. Но если бы mysql сервис нужен был включенным, к примеру, то я бы добавил эти строки: #Requires=mysqld.service #After=mysqld.service [Service] # Даем знать systemd, что этот сервис представляет из себя лишь 1 процесс. Man page хорошо описывает доступные опции. Type=oneshot # Выполнить эту команду при запуске сервиса. ExecStart=/root/scripts/mysqld-tmpfs start # Выполнить эту команду при остановке сервиса. ExecStop=/root/scripts/mysqld-tmpfs stop # Даем знать systemd, что сервис нужно считать запущенным, даже если основной процесс прекратил свою работу. Как раз то, что мне нужно: мой процесс выполнит монтировку и после этого прекратит свою работу, но должен считаться активным, т.к. монтировка осталась в системе. RemainAfterExit=yes
[Unit] Description=Working/Programming target Requires=mysqld.service Requires=apache2.service # Сюда я могу дописывать новые сервисы “Requires=another.service”, если они мне понадобятся в повседневной работе.
Какие были проблемы?
- Запустится какой-то другой сервис, который в своей декларации указывает, что он конфликтует с нашим сервисом.
- Выполнится systemctl isolate some-another.target или systemctl stop this.service.
- Наш сервис может запросить в своей декларации останавливать себя не ленивым образом, а активным, добавив вот такую строку в [Unit] секцию: StopWhenUnneeded=true
Декларации “чужих” сервисов можно менять создавая файлы /etc/systemd/system/name-i-alter.service.d/*.conf. Я просто создал /etc/systemd/system/apache2.service/auto-stop.conf и /etc/systemd/system/mysqld.service.d/auto-stop.conf и поместил туда ту строку.
Другая проблема, на которую я, наткнулся была в том, что systemd не очень любит symlinks. Я не большой любитель “загаживать” системные директории типа /etc, /bin, /usr своими локальными продуктами жизнедеятельности, поэтому изначально я попытался свой /etc/systemd/system/mysqld-tmpfs.service сделать symlink на /root/scripts/mysqld-tmpfs.service файл, т.е. хранить сам файл в домашнем каталоге root пользователя. Но systemctl команда отказывалась работать с таким сервисом выдавая малопонятные ошибки. Оказалось, что определенную часть своей внутренней кухни systemd делает именно на symlinks, и ему тогда “трудно” отличать внутреннюю кухню (свои symlinks) от сторонних *.service файлов (если они тоже являются symlinks). Удалив symlink из /etc/systemd/system/mysqld-tmpfs.service и скопировав туда содержимое настоящего файла, я решил эту проблему. Более подробное описание этой проблемы можно прочитать тут: bugzilla.redhat.com/show_bug.cgi?id=955379
Результат
systemctl start programming.target
systemctl start mysqld-tmpfs.service
Когда я хочу демонтировать БД из tmpfs в жесткий диск (хотя на практике я так почти не делаю, а просто оставляю БД в tmpfs на целый день, и при выключении systemd за меня запускает демонтировку из tmpfs в жесткий диск):
systemctl stop mysqld-tmpfs.service
systemctl stop programming.target
Cheat sheet
- Вызывайте systemctl daemon-reload, если вы изменили декларацию чего-либо (systemd считает файлы декларации заново)
- systemctl start my-name.(service|target) – запуск сервиса или target
- systemctl stop my-name.(service|target) – остановка сервиса или target
- systemctl enable my-name.service – сервисы могут декларировать при каких включенных targets они должны включаться. Для этого используется [Install] секция в файле декларации сервиса. Вы, как сисадмин, имеете власть на установку этого “пожелания” сервиса. Часто сервисы “устанавливаются” в target по умолчанию multi-user.target или в похожее.
- systemctl disable my-name.service – обратная операция по отношению к enable: деассоциировать связь между my-name.service и targets, которые он запросил в [Install] секции своей декларации.
- systemctl isolate my.target — включить все сервисы из my.target и выключить все остальные включенные сервисы.
- systemctl status my-name.(service|target) — узнать статус (запущен/остановлен) у сервиса или target.
Надеюсь, эта статья кому-то поможет при осваивании systemd. Я попытался сделать ее компактной, и если упустил из внимания какие-то дополнительные вопросы, спрашивайте в комментариях!
How to Create a Systemd Service in Linux
Systemd is a modern software suite that provides many components on a Linux system including a system and service manager. It is compatible with SysV and LSB init scripts and works as a replacement for sysvinit.
A systemd service is defined in a unit file (a unit is a representation of a service and system resources such as devices, sockets, mount points, etc.). Custom service unit files should be stored in the /etc/systemd/system/ directory and must have an .service extension. For example, a custom test-app service uses /etc/systemd/system/test-app.service unit file.
A unit file is a plain text ini-style file that usually includes three common sections. The first section is usually the Unit section which carries generic information about the unit that is not dependent on the type of unit.
The next section is the unit type section, for a service, it is a Service section. And the final section is the Install section which carries installation information for the unit.
In this guide, we will show how to create a new systemd service and manage the service using the systemctl command, in Linux.
Creating Custom Systemd Service File in Linux
To run an application or program or script as a service under systemd, you can create a new systemd service as follows. Start by creating the service unit file named test-app.service (remember to replace test-app with the actual name of your service or application), under /etc/systemd/system/:
# vi /etc/systemd/system/test-app.service
The following configuration is used to define a service for running a Flask application using Gunicorn, a Python WSGI HTTP Server for UNIX.
[Unit] Description=Gunicorn daemon for serving test-app After=network.target [Service] User=root Group=root WorkingDirectory=/apps/test-app/ Environment="PATH=/apps/test-app/bin" ExecStart=/apps/test-app/bin/gunicorn --workers 9 -t 0 --bind 127.0.0.1:5001 -m 007 wsgi:app --log-level debug --access-logfile /var/log/gunicorn/test_app_access.log --error-logfile /var/log/gunicorn/test_app_error.log ExecReload=/bin/kill -s HUP $MAINPID RestartSec=5 [Install] WantedBy=multi-user.target
Let’s briefly describe each configuration directive in the configuration above:
- Description – is used to specify a description for the service.
- After – defines a relationship with a second unit, the network.target. In this case, the test-app.service is activated after the network.target unit.
- User – is used to specifying the user with whose permissions the service will run.
- Group – is used to specify the group with whose permissions the service will run.
- WorkingDirectory – is used to set the working directory for executed processes.
- Environment – is used to set environment variables for executed processes.
- ExecStart – is used to define the commands with their arguments that are executed when this service is started.
- ExecReload – is used to define the commands to execute to trigger a configuration reload in the service.
- WantedBy – enables a symbolic link to be created in the .wants/ or .requires/ directory of each of the listed unit(s), multi-user.target in this case, when the test-app.service unit is enabled using the systemctl enable command.
You can find all service unit configuration parameters, well described in the Service unit configuration documentation.
Save the unit file and close it. Then reload systemd with this new service unit file by running:
# systemctl daemon-reload command
Remember to always run this command after editing a unit file.
Manage Systemd Service in Linux
To start/activate the service, run the systemctl command as follows:
# systemctl start test-app.service
To check if the service is running or not, issue the systemctl command as shown.
# systemctl status test-app.service
To enable the service to start at system boot, use the systemctl enable command. You can check if the service has been enabled using the systemctl is-enable command as follows:
# systemctl enable test-app.service # systemctl is-enabled test-app.service
Alternatively, you can also enable and start the service at the same time as shown.
# systemctl enable --now test-app.service
To stop/deactivate the service, run the systemctl stop command as follows:
# systemctl stop test-app.service
To restart the service, run the systemctl restart command as follows:
# systemctl restart test-app.service
You can also disable a service to prevent it from starting at system boot, using the systemctl disable command. You can check if the service has been enabled using the systemctl is-enable command as follows:
# systemctl disable test-app.service # systemctl is-disabled test-app.service
Alternatively, you can disable and stop it at the same time as shown.
# systemctl disable --now test-app.service
For more details about managing systemd services and other resources, run: