Запуск Python скрипта в виде службы через systemctl/systemd
Есть несколько способов запуска вашей программы в качестве фоновой службы в Linux, таких как crontab, .bashrc и т. д., но сегодня будет разговор о systemd. Изначально я искал способ запустить свой скрипт на Python в качестве фоновой службы, поэтому даже если сервер по какой-то причине перезагрузится, мой скрипт все равно должен работать в фоновом режиме, после небольшого ресерча и я обнаружил, что systemd позволяет мне это сделать. Давайте начнем.
Настройки далее будут производиться на машине с Ubuntu 20.04.
Почти все версии Linux поставляются с systemd из коробки, но если у вас его нет, вы можете просто запустить следующую команду:
sudo apt install -y systemd
Примечание. Флаг -y означает быструю установку пакетов и зависимостей.
Чтобы проверить, какая версия systemd у вас установлена, просто выполните команду:
Создайте файл python с любым именем. Я назову свой скрипт именем test.py.
import time from datetime import datetime while True: with open("timestamp.txt", "a") as f: f.write("Текущая временная метка: " + str(datetime.now())) f.close() time.sleep(10)
Приведенный выше скрипт будет записывать текущую метку времени в файл каждые 10 секунд. Теперь напишем сервис.
sudo nano /etc/systemd/system/test.service
(имя службы, которая тестируется в этом случае)
[Unit] Description=My test service After=multi-user.target [Service] User=deepak Group=admin Type=simple Restart=always ExecStart=/usr/bin/python3 /home//test.py [Install] WantedBy=multi-user.target
Замените имя пользователя в вашей ОС, где написано . Флаг ExecStart принимает команду, которую вы хотите запустить. Таким образом, в основном первый аргумент — это путь к python (в моем случае это python3), а второй аргумент — это путь к скрипту, который необходимо выполнить. Флаг перезапуска всегда установлен, потому что я хочу перезапустить свою службу, если сервер будет перезапущен.
Здесь мы определили User=deepak и Group=admin, чтобы убедиться, что скрипт будет выполняться только от имени пользователя deepak, входящего в группу admin.
Теперь нам нужно перезагрузить демон.
sudo systemctl daemon-reload
Давайте включим наш сервис, чтобы он не отключался при перезагрузке сервера.
sudo systemctl enable test.service
А теперь давайте запустим наш сервис.
sudo systemctl start test.service
Теперь наш сервис работает.
Примечание. Файл будет записан в корневой каталог (/), потому что программа запишет путь с точки зрения systemd. Чтобы изменить это, просто отредактируйте путь к файлу. Например:
import time from datetime import datetime path_to_file = "введите желаемый путь к файлу" while True: with open(path_to_file, "a") as f: f.write("Текущая временная метка: " + str(datetime.now())) f.close() time.sleep(10)
Есть несколько команд, которые вы можете выполнить для запуска, остановки, перезапуска и проверки состояния.
sudo systemctl stop name_of_your_service
sudo systemctl restart name_of_your_service
sudo systemctl status name_of_your_service
Это было очень поверхностное знакомство с systemd, предназначенное для новичков, которые хотят начать писать свои собственные systemd службы для python.
ПРИМЕЧАНИЕ. Это относится не только к сценариям Python. Вы можете запустить любую программу с ним, независимо от языка программирования, на котором написана ваша программа.
Using dbus API in python for managing services in Linux
Sometimes programmer encounters a case where they need to manage Linux services using systemctl of systemd in python where they use subprocess call to execute systemctl call to manage services in Linux. Using subprocess call is generally take times to manage services. Better
approaches to use dbus API directly because systemd uses the D-Bus wire protocol for communication between systemctl and systemd. Managing
these Linux services will be fast by using dbus API in python compare to using subprocess calls.
A dbus service is a program that offers some IPC API on a bus. A service is identified by a name in reverse domain name notation. Every connection to a bus is identified in the context of D-Bus by what is called a bus name.
Thus, the org.freedesktop.NetworkManager service on the system bus is where NetworkManager’s APIs are available and org.freedesktop.login1 on the system bus is where systemd-logind’s APIs are exposed and org.freedesktop.systemd1 on the system bus is where systemctl API is available.
When a process sets up a connection to a bus, the bus assigns to the connection a special bus name called a unique connection name. Bus names of this type are immutable—it’s guaranteed they won’t change as long as the connection exists—and, more importantly, they can’t be reused during the bus lifetime. This means that no other connection to that bus will ever have assigned such a unique connection name, even if the same process closes down the connection to the bus and creates a new one.
In this blog, I will talk about systemctl systemd call using dbus API in python .
dbus API in python:
import sys import dbus bus = dbus.SystemBus() systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd, 'org.freedesktop.systemd1.Manager') def disable_and_stop(service): """ disable_and_stop and stop method will check if the service is already running or not in system. If its already running then it will first stop the service. After that it will check if service is already enabled or not. If its enabled then it will disabled the service. It raise exception if there is error :param str service: name of the service """ if is_service_active(service): try: manager.StopUnit(service, 'replace') except: sys.exit("Error: Failed to stop <>.".format(service)) if is_service_enabled(service): try: manager.DisableUnitFiles([service], False) except: sys.exit("Error: Failed to disable <>.".format(service)) def enable(service): """ enable method will enable service that is passed in this method. It raise exception if there is error :param str service: name of the service """ try: manager.EnableUnitFiles([service], False, True) except: sys.exit("Error: Failed to enable <>.".format(service)) def start(service): """ start method will start service that is passed in this method. If service is already started then it will ignore it. It raise exception if there is error :param str service: name of the service """ try: manager.StartUnit(service, 'replace') except: sys.exit("Error: Failed to start <>.".format(service)) def restart(service): """ restart method will restart service that is passed in this method. It raise exception if there is error :param str service: name of the service """ try: manager.RestartUnit(service, 'replace') except: sys.exit("Error: Failed to restart <>.".format(service)) def is_service_enabled(service): """ is_service_enabled method will check if service is already enabled that is passed in this method. It raise exception if there is error. Return value, True if service is already enabled otherwise False. :param str service: name of the service """ try: return manager.GetUnitFileState(service) == 'enabled' except: return False def is_service_active(service): """ is_service_active method will check if service is running or not. It raise exception if there is service is not loaded Return value, True if service is running otherwise False. :param str service: name of the service """ try: manager.GetUnit(service) return True except: return False def enable_and_start(service): """ enable_and_start method will enable and start service. Return value, True if service is running otherwise False. :param str service: name of the service """ try: if not is_service_enabled(service): enable(service) if not is_service_active(service): start(service) return True except: return False
Sample programme in python to restart sshd service:
import sys import dbus bus = dbus.SystemBus() systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd, 'org.freedesktop.systemd1.Manager') def restart(service): """ restart method will restart service that is passed in this method. It raise exception if there is error :param str service: name of the service """ try: manager.RestartUnit(service, 'replace') except: sys.exit("Error: Failed to restart <>.".format(service)) if __name__ == "__main__": restart("sshd.service")
How to stop and start a systemd service via python script w/o requiring sudo password
The following script allows me to check whether a systemd service is active, and to stop or start the service. When executing .stop() or .start() , how can I proceed to stopping and starting the service directly w/o having to supply the sudo password? An example application of where this is useful is stopping and restarting the NetworkManager service.
#!/bin/python3 import subprocess import sys class SystemdService(object): '''A systemd service object with methods to check it's activity, and to stop() and start() it.''' def __init__(self, service): self.service = service def is_active(self): """Return True if systemd service is running""" try: cmd = '/bin/systemctl status <>.service'.format(self.service) completed = subprocess.run( cmd, shell=True, check=True, stdout=subprocess.PIPE ) except subprocess.CalledProcessError as err: print( 'ERROR:', err ) else: for line in completed.stdout.decode('utf-8').splitlines(): if 'Active:' in line: if '(running)' in line: print('True') return True return False def stop(self): ''' Stop systemd service.''' try: cmd = '/bin/systemctl stop <>.service'.format(self.service) completed = subprocess.run( cmd, shell=True, check=True, stdout=subprocess.PIPE ) except subprocess.CalledProcessError as err: print( 'ERROR:', err ) def start(self): ''' Start systemd service.''' try: cmd = '/bin/systemctl start <>.service'.format(self.service) completed = subprocess.run( cmd, shell=True, check=True, stdout=subprocess.PIPE ) except subprocess.CalledProcessError as err: print( 'ERROR:', err ) if __name__ == '__main__': monitor = SystemdService(sys.argv[1]) monitor.is_active()
How to use subprocess.call to restart a service in linux?
However, when using shell=True the command didn’t do anything while, I’m using shell=False it said no directory or files «command I implemented the subprocess.call in a button.
We have no idea why this fails, though. Does subprocess.check_call([«sudo», «-l»]) succeed? If you have a GUI in front, there is no good way for sudo to ask you for a password.
@tripleee i tried the command you give me and the server responded by no such file or directory «sudo». everytime i drop the shell=False they always return no such file or directory. i tried to call («./ file.sh»,shell=True) a shellscript i made consisting the restart command and it return non-zero exit status 127. and when using shell=False it return no such file.
Again, with shell=False , you need to split it into a list yourself. But the token ./ is not valid syntax with or without a shell. Do you mean «./file.sh» ? The way to run that with shell=False is subprocess.call([«./file.sh»]) ; notice how I have manually converted the command to a list of tokens (where of course because there is only one token, the list is also just one item).
@tripleee sorry, now i get it. i try it and it doesn’t return an error but the service itself didn’t restart.
1 Answer 1
The difference between shell=True and shell=False on Unix-like systems is that the shell takes care of breaking up the command line for exec , whereas if you don’t have a shell, you have to do that yourself.
The shell offers no benefits here, so you can safely drop it.
subprocess.call(["sudo", "systemctl", "restart", "nginx"])
So in the general case anything which looks like
subprocess.something("blah 'blah' \"blah\"", shell=True)
needs to be converted into a list without shell quoting to run it without shell=True :
subprocess.something(['blah', 'blah', "blah"])
and of course anything which is specific to the shell (redirection, pipelines, globbing, etc) will need to be replaced with native Python code.
If your original code didn’t work, this also probably won’t work, though.