跳至内容

为 Python 脚本创建 systemd 服务

如果您像许多系统管理员一样,是使用 * * * * * /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