跳至内容

systemd 用于 Python 脚本的服务

如果您像许多系统管理员一样,是使用 * * * * * /I/launch/my/script.sh 启动的 cron 脚本的爱好者,那么这篇文章应该让您考虑另一种使用 systemd 提供的所有功能和便捷性的方法。

我们将编写一个 Python 脚本,该脚本将提供一个连续循环来执行您定义的操作。

我们将看到如何将此脚本作为 systemd 服务运行,查看 journalctl 中的日志,并查看脚本崩溃时会发生什么。

先决条件

首先,让我们安装脚本使用 journalctl 所需的一些 Python 依赖项

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 执行的最小值,因此我们可以低于此限制)。

注意

我个人在更高级的形式中使用此脚本,该脚本持续查询数据库并根据通过 rundeck API 检索的信息执行作业

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/ 启动的。请记住将路径调整为您的脚本和 syslog 标识符。

启动并启用新服务

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 的重启功能非常有用。

结论

systemdjournald 为我们提供了足够强大的工具来制作健壮而强大的脚本,足以取代我们可靠的旧 crontab 脚本。

希望此解决方案对您有所帮助。

作者:Antoine Le Morvan

贡献者:Steven Spencer