Systemd: пишем собственные .service и .target
У меня появился Linux на домашнем компьютере, и я поспешил обжиться в новой ОС. Она была установлена с systemd init process. Это было мое первое знакомство с этим новым инструментом. Cвой ноутбук я использую для каждодневной жизни и для программирования. Мне хотелось включать рабочие программы (Apache2 и MySQL) только на время, пока я их использую, чтобы не тратить впустую ресурсы своего компьютера. Дополнительно, для тестирования я написал bash скрипт, который выгружает содержимое одной из MySQL БД c жесткого диска в ОЗУ (в tmpfs) – так тесты выполняются значительно быстрее. По идее, я мог бы начинать свой рабочий день вот так:
И заканчивать его:
Но мне хотелось сделать вещи “как надо”.
Чего я хотел?
Я хотел достичь 2 целей:
- Мне было лень писать 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. В какой-то момент в консоли можно набрать:
Все сервисы из my-another.target будут включены, и все включенные сервисы не из my-another.target будут выключены. Это очень похоже на переключение runlevel в openRC. Однако, systemd поддерживает включение более чем 1 target. Вот пример:
После выполнения этих команд в системе будет работать объединение сервисов из my-favorite.target и my-another.target.
Как я это сделал?
В итоге у меня получился вот такой mysqld-tmpfs.service файл:
И вот такой programming.target файл:
Какие были проблемы?
При остановке programming.target почему-то нижележащие apache2.service и mysqld.service не останавливались. Почитав как следует man page, я нашел проблему: systemd останавливает сервисы “лениво” — если никто не требует запущенный сервис, и он не был запущен явным образом, а как зависимость для какого-то другого сервиса, то systemd остановит его только при одном из 3 обстоятельств:
- Запустится какой-то другой сервис, который в своей декларации указывает, что он конфликтует с нашим сервисом.
- Выполнится 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
Результат
Я достиг своей цели. Начиная рабочий день:
Когда нужно выполнить тесты на своем проекте:
Когда я хочу демонтировать БД из tmpfs в жесткий диск (хотя на практике я так почти не делаю, а просто оставляю БД в tmpfs на целый день, и при выключении systemd за меня запускает демонтировку из tmpfs в жесткий диск):
Когда я закончил работать и хочу остановить рабочие программы:
Cheat sheet
Некоторые полезности при работе с systemd:
- Вызывайте 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. Я попытался сделать ее компактной, и если упустил из внимания какие-то дополнительные вопросы, спрашивайте в комментариях!
Источник
Setup a python script as a service through systemctl/systemd
There are several ways you can run your program as a background service in Linux such as crontab, .bashrc, etc but today I’ll write about systemd. I was initially looking for a way to run my python script as a background service so even if the server restarts for some reason, my script will run in the background regardless and I found that systemd allows me to do that. Let’s get started
I’ll be setting this up on an Ubuntu 18.10 machine.
Almost all versions of Linux come with systemd out of the box, but if your’s didn’t come with it then you can simply run the following command:
Note: The -y flag means to install the packages and dependencies quickly.
To check which version of systemd you have simply run the command:
Create a python file whatever you like. I’m going to call mine test.py.
sudo nano test.py
The above script will write the current timestamp in the file after every 10 seconds. Let’s write the service now.
sudo nano /etc/systemd/system/test.service (name of the service which is test in this case)
Insert the username in your OS where is written. The ExecStart flag takes in the command that you want to run. So basically the first argument is the python path (in my case it’s python3) and the second argument is the path to the script that needs to be executed. Restart flag is set to always because I want to restart my service if the server gets restarted. For more information on this, you can go to this link. Now we need to reload the daemon.
Let’s enable our service so that it doesn’t get disabled if the server restarts.
And now let’ start our service.
Now our service is up and running.
Note: The file will be written in the root directory (/) because the program will write in the path from the perspective of systemd. To change that simply edit out the file path. For example:
There are several commands you can do to start, stop, restart, and check status.
To stop the service.
To check status.
This was a very basic introduction to systemd aimed at beginners who want to get started with writing their own systemd services for python. If you want a deep dive into systemd and systemctl, here is a detailed guide by the digital ocean.
NOTE: This doesn’t only apply to python scripts. You can basically run any program with this regardless of the programming language your program is written in.
Источник
ilyasst / python_systemd.md
Run a python script forever
In this section, we are going to show how to run a python script as a systemd service, allowing you to boot it at the start of a Linux machine and to maintain it alive.
Test method: Telegram Bot
We are going to use a very basic Telegram bot in order to test that our script will:
- Automatically start when the machine boot
- Automatically restart when it crashes/exits for whichever reason
If the bot is alive, then it means that our method works. Of course, we will also be able to check the status of the service through systemd, but just to be sure . This bot is going to send us
/Temp folder on your Raspberry Pi using SSh
Create a virtual environment in
/Temp, you should already have venv we installed it at the beginning.
- Modify the python script, add first line: #!/home/dietpi/Temp/.env/bin/python3 which is the path to the python in the virtual env
- chmod +x the python file to make it executable, it will execute with the python you specified (no need to load the venv yourself, just execute without specifying the python bin)
- Type the following command in your terminal to add a systemd service sudo systemctl edit —force —full dummy.service :
- We also add a Restart flag in order to get systemd to always restart the script if it were to ever fail
- Enable the service sudo systemctl enable dummy.service
- Reboot, wait for 30 seconds
- Try to contact your bot with /help , All good !
- SSH into your RPi
- Check your service status: sudo systemctl status dummy.service
- Manipulate your service:
- Let’s validate that it will really restart on crash. Let’s add a function to our bot that simply kills the script. By killing, I mean that we are going to create an error in order to get the script to crash. When we are working on a telegram bot script, each function is kind of loaded separately, we are going to create an error in a new function and use it to check if the bot truely restarts, add this:
- Deploy (copy/paste) this new script on the Raberry pi, then reboot the RPi, so it properly loads as the new systemd service
- Wait for 30 seconds, then contact your bot using /help to check that it is online
- Use the kill command, the bot should die, wait for a bit more than 10 seconds, as we have a 10 second timer set prior to starting the script
- Try /help again, IT WORKS!
Load the virtual environment: source .env/bin/activate
Let’s also update pip, and install a pip we need.
Let’s give our bot:
Let’s now make this script into a systemd service.
Important
All the paths in your scripts have to be absolute paths, there can be no relative path in your scripts. If there are relative paths that you must keep, you will have to change your current working directory by retrieving
Note that ExecStart is the path to the Python file directly if you made it an executable using the right virtual environment. If you did not, then you have to specify a python binary to execute it.
We have to add an ExecStartPre delay otherwise the service keeps tries to start before internet is even available and we get this error:
Use Ctrl X + Y to save and exit when you finished editing.
If you try ths script (in your Virtual Environment not as a service), you will see that the script will return the /help command, but it will simply crash if you try to run the /kill command which tries to print a variable a that was never defined. Because python sees each telegram bot function as a separate function, it does not check that all variables exist before, as a variable can be defined with an incoming Telegram message.
In order to delete the service:
sudo systemctl disable dummy.service
Freqtrade on Dietpi
UPDATE (March 2020)
The freqtrade code was updated in February 2020 in such a way that freqtrade should now be run from the freqtrade/freqtrade/main.py file instead of freqtrade/bin/freqtrade . This means that there is a difference between the file being edited in the video and the one edited in this document. The version presented in this document should be used.
In addition, the DefaultStrategy was removed from freqtrade. You will thus get an error if you try to run it, use BBRSI instead.
/freqtrade/freqtrade , and edit the file called main.py using nano:
- Change the first line so that your new file looks like this:
- Add the freqtrade systemd service using:
- Paste the following:
Use Ctrl+X then Y followed by Enter to save the new serviced.
Enable the service sudo systemctl enable freqtrade.service
Reboot the Raspberry pi: sudo reboot now
Freqtrade on VM
UPDATE (March 2020)
The freqtrade code was updated in February 2020 in such a way that freqtrade should now be run from the freqtrade/freqtrade/main.py file instead of freqtrade/bin/freqtrade . This means that there is a difference between the file being edited in the video and the one edited in this document. The version presented in this document should be used.
In addition, the DefaultStrategy was removed from freqtrade. You will thus get an error if you try to run it, use BBRSI instead.
/freqtrade/freqtrade , and edit the file called main.py using nano:
- Change the first line so that your new file looks like this:
- Add the freqtrade systemd service using:
- Paste the following:
Use Ctrl+X then Y followed by Enter to save the new serviced.
Источник