Перейти до змісту

Служба systemd для сценарію Python

Якщо ви, як і багато системних адміністраторів, шанувальник сценаріїв cron, які запускаються за допомогою * * * * * /I/launch/my/script.sh, ця стаття повинна змусити вас подумати про інший спосіб зробити це використовуючи всю потужність і легкість, які пропонує systemd.

Ми напишемо сценарій python, який забезпечить безперервний цикл для виконання дій, які ви визначаєте.

Ми побачимо, як запустити цей сценарій як службу systemd, переглянемо журнали в journalctl і побачимо, що станеться, якщо сценарій аварійно завершує роботу.

Передумови

Давайте почнемо зі встановлення деяких залежностей python, необхідних для сценарію для використання journalctl:

shell > sudo dnf install python36-devel systemd-devel
shell > sudo pip3 install systemd

Написання сценарію

Давайте розглянемо такий сценарій my_service.py:

"""
Sample script to run as script
"""
import time
import logging
import sys
from systemd.journal import JournaldLogHandler

# Get an instance of the logger
LOGGER = logging.getLogger(__name__)

# Instantiate the JournaldLogHandler to hook into systemd
JOURNALD_HANDLER = JournaldLogHandler()
JOURNALD_HANDLER.setFormatter(logging.Formatter(
    '[%(levelname)s] %(message)s'
))

# Add the journald handler to the current logger
LOGGER.addHandler(JOURNALD_HANDLER)
LOGGER.setLevel(logging.INFO)

class Service(): # pylint: disable=too-few-public-methods
    """
    Launch an infinite loop
    """
    def __init__(self):

        duration = 0

        while True:
            time.sleep(60)
            duration += 60
            LOGGER.info("Total duration: %s", str(duration))
            # will failed after 4 minutes
            if duration > 240:
                sys.exit(1)

if __name__ == '__main__':

    LOGGER.info("Starting the service")
    Service()

Ми починаємо з створення екземплярів необхідних змінних для надсилання логів у journald. Потім скрипт запускає нескінченний цикл і робить паузу на 60 секунд (що є мінімумом для виконання cron, тому ми можемо опуститися нижче цього обмеження).

Примітка

Особисто я використовую цей сценарій у більш просунутій формі, який постійно надсилає запити до бази даних і виконує завдання на основі інформації, отриманої через API rundeck

Інтеграція Systemd

Тепер, коли у нас є сценарій, який може служити основою для вашої уяви, ми можемо встановити його як службу systemd.

Давайте створимо цей файл my_service.service і скопіюємо його до /etc/systemd/system/.

[Unit]
Description=My Service
After=multi-user.target

[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/python3 my_service.py
WorkingDirectory=/opt/my_service/

StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=my_service

[Install]
WantedBy=multi-user.target

Як бачите, скрипт запускається з /opt/my_service/. Не забудьте адаптувати шлях до вашого сценарію та ідентифікатора системного журналу.

Запустіть і ввімкніть новий сервіс:

shell > sudo systemctl daemon-reload
shell > sudo systemctl enable my_service.service
shell > sudo systemctl start my_service.service

Тести

Тепер ми можемо переглядати журнали через journalctl:

shell > journalctl -f -u my_service
oct. 14 11:07:48 rocky8 systemd[1]: Started My Service.
oct. 14 11:07:49 rocky8 __main__[270267]: [INFO] Starting the service
oct. 14 11:08:49 rocky8 __main__[270267]: [INFO] Total duration: 60
oct. 14 11:09:49 rocky8 __main__[270267]: [INFO] Total duration: 120

Тепер давайте подивимося, що станеться, якщо сценарій виходить з ладу:

shell > ps -elf | grep my_service
4 S root      270267       1  0  80   0 - 82385 -      11:07 ?        00:00:00 /usr/bin/python3 my_service.py
shell > sudo kill -9 270267
shell > journalctl -f -u my_service
oct. 14 11:10:49 rocky8 __main__[270267]: [INFO] Total duration: 180
oct. 14 11:11:49 rocky8 __main__[270267]: [INFO] Total duration: 240
oct. 14 11:12:19 rocky8 systemd[1]: my_service.service: Main process exited, code=killed, status=9/KILL
oct. 14 11:12:19 rocky8 systemd[1]: my_service.service: Failed with result 'signal'.
oct. 14 11:12:19 rocky8 systemd[1]: my_service.service: Service RestartSec=100ms expired, scheduling restart.
oct. 14 11:12:19 rocky8 systemd[1]: my_service.service: Scheduled restart job, restart counter is at 1.
oct. 14 11:12:19 rocky8 systemd[1]: Stopped My Service.
oct. 14 11:12:19 rocky8 systemd[1]: Started My Service.
oct. 14 11:12:19 rocky8 __main__[270863]: [INFO] Starting the service

Ми також можемо почекати 5 хвилин, доки сценарій завершить роботу самостійно: (видаліть це для свого виробництва)

oct. 14 11:16:02 rocky8 systemd[1]: Started My Service.
oct. 14 11:16:03 rocky8 __main__[271507]: [INFO] Starting the service
oct. 14 11:17:03 rocky8 __main__[271507]: [INFO] Total duration: 60
oct. 14 11:18:03 rocky8 __main__[271507]: [INFO] Total duration: 120
oct. 14 11:19:03 rocky8 __main__[271507]: [INFO] Total duration: 180
oct. 14 11:20:03 rocky8 __main__[271507]: [INFO] Total duration: 240
oct. 14 11:21:03 rocky8 __main__[271507]: [INFO] Total duration: 300
oct. 14 11:21:03 rocky8 systemd[1]: my_service.service: Main process exited, code=exited, status=1/FAILURE
oct. 14 11:21:03 rocky8 systemd[1]: my_service.service: Failed with result 'exit-code'.
oct. 14 11:21:03 rocky8 systemd[1]: my_service.service: Service RestartSec=100ms expired, scheduling restart.
oct. 14 11:21:03 rocky8 systemd[1]: my_service.service: Scheduled restart job, restart counter is at 1.
oct. 14 11:21:03 rocky8 systemd[1]: Stopped My Service.
oct. 14 11:21:03 rocky8 systemd[1]: Started My Service.
oct. 14 11:21:03 rocky8 __main__[271993]: [INFO] Starting the service

Як бачите, функція перезапуску systemd дуже корисна.

Висновок

systemd і journald надають нам інструменти для створення надійних і потужних сценаріїв, які достатньо легко замінять наші старі надійні сценарії crontab.

Сподіваюся, це рішення буде для вас корисним.

Author: Antoine Le Morvan

Contributors: Steven Spencer