繁体   English   中英

Python 守护进程和 systemd 服务

[英]Python daemon and systemd service

我有一个简单的 Python 脚本作为守护进程工作。 我正在尝试创建 systemd 脚本以便能够在启动期间启动此脚本。

当前系统脚本:

[Unit]
Description=Text
After=syslog.target

[Service]
Type=forking
User=node
Group=node
WorkingDirectory=/home/node/Node/
PIDFile=/var/run/zebra.pid
ExecStart=/home/node/Node/node.py

[Install]
WantedBy=multi-user.target

节点.py:

if __name__ == '__main__':
    with daemon.DaemonContext():
        check = Node()
        check.run()

run包含while True循环。

我尝试使用systemctl start zebra-node.service运行此服务。 不幸的是,服务从未完成说明顺序 - 我必须按 Ctrl+C。 脚本正在运行,但状态为激活,一段时间后变为停用。 现在我正在使用 python-daemon(但在我尝试没有它并且症状相似之前)。

我应该为我的脚本实现一些附加功能还是 systemd 文件不正确?

它没有完成启动顺序的原因是,对于类型forking ,您的启动过程预计会分叉并退出(请参阅 $ man systemd.service - 搜索分叉)。

只使用主进程,不要守护进程

一种选择是少做。 使用 systemd,通常不需要创建守护进程,您可以直接运行代码而无需守护进程。

#!/usr/bin/python -u
from somewhere import Node
check = Node()
check.run()

这允许使用称为simple的更简单的服务类型,因此您的单元文件看起来像。

[Unit]
Description=Simplified simple zebra service
After=syslog.target

[Service]
Type=simple
User=node
Group=node
WorkingDirectory=/home/node/Node/
ExecStart=/home/node/Node/node.py
StandardOutput=syslog
StandardError=syslog

[Install]
WantedBy=multi-user.target

请注意,python shebang 中的-u不是必需的,但是如果您将某些内容打印到 stdout 或 stderr,则-u确保没有输出缓冲到位,并且打印的行将立即被 systemd 捕获并记录在日记中。 没有它,它会出现一些延迟。

为此,我在单元文件中添加了StandardOutput=syslogStandardError=syslog行。 如果您不关心日记中的打印输出,请不要关心这些行(它们不必存在)。

systemd使守护进程过时

虽然您的问题的标题明确询问了守护进程,但我想,问题的核心是“如何使我的服务运行”,而使用主进程似乎要简单得多(您根本不必关心守护进程),它可以考虑回答你的问题。

我认为,很多人使用守护进程只是因为“每个人都这样做”。 使用 systemd 守护进程的原因通常是过时的。 使用守护进程可能有一些原因,但现在这种情况很少见。

编辑:将python -p修复为正确的python -u 谢谢kmftzg

可以像 Schnouki 和 Amit 描述的那样进行守护进程。 但是对于 systemd,这不是必需的。 有两种更好的方式来初始化守护进程:socket-activation 和使用 sd_notify() 的显式通知。

套接字激活适用于希望在网络端口或 UNIX 套接字或类似端口上侦听的守护进程。 Systemd 将打开套接字,侦听它,然后在连接进入时生成守护进程。这是首选方法,因为它为管理员提供了最大的灵活性。 [1] 和 [2] 给出了很好的介绍,[3] 描述了 C API,而 [4] 描述了 Python API。

[1] http://0pointer.de/blog/projects/socket-activation.html
[2] http://0pointer.de/blog/projects/socket-activation2.html
[3] http://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
[4] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.listen_fds

显式通知意味着守护程序自己打开套接字和/或进行任何其他初始化,然后通知 init 它已准备好并可以为请求提供服务。 这可以通过“分叉协议”来实现,但实际上最好使用 sd_notify() 向 systemd 发送通知。 Python 包装器称为 systemd.daemon.notify 并且将是使用 [5] 的一行。

[5] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.notify

在这种情况下,单元文件将具有 Type=notify,并在建立套接字后调用 systemd.daemon.notify("READY=1")。 不需要分叉或守护进程。

您没有创建 PID 文件。

systemd 期望您的程序将其 PID 写入/var/run/zebra.pid 如果您不这样做,systemd 可能会认为您的程序失败,因此将其停用。

要添加 PID 文件,请安装lockfile并将您的代码更改为:

import daemon
import daemon.pidlockfile 

pidfile = daemon.pidlockfile.PIDLockFile("/var/run/zebra.pid")
with daemon.DaemonContext(pidfile=pidfile):
    check = Node()
    check.run()

(快速说明:最近对lockfile的一些更新更改了它的 API,使其与 python-daemon 不兼容。要修复它,请编辑daemon/pidlockfile.py ,从导入中删除LinkFileLock ,然后添加from lockfile.linklockfile import LinkLockFile as LinkFileLock 。)

注意另一件事: DaemonContext将程序的工作目录更改为/ ,使服务文件的WorkingDirectory无用。 如果您希望DaemonContext chdir 进入另一个目录,请使用DaemonContext(pidfile=pidfile, working_directory="/path/to/dir")

此外,您很可能需要在创建DaemonContext()时设置daemon_context=True

这是因为,如果python-daemon检测到它在 init 系统下运行,它不会与父进程分离。 systemd期望以Type=forking运行的守护进程会这样做。 因此,您需要它,否则systemd将继续等待,并最终终止该进程。

如果你好奇,在python-daemon的 daemon 模块中,你会看到这段代码:

def is_detach_process_context_required():
    """ Determine whether detaching process context is required.

        Return ``True`` if the process environment indicates the
        process is already detached:

        * Process was started by `init`; or

        * Process was started by `inetd`.

        """
    result = True
    if is_process_started_by_init() or is_process_started_by_superserver():
        result = False

希望这能更好地解释。

我在 CentOS 7 下尝试将一些 python init.d 服务转换为 systemd 时遇到了这个问题。通过将此文件放在/etc/systemd/system/中,这对我来说似乎很有效:

[Unit]
Description=manages worker instances as a service
After=multi-user.target

[Service]
Type=idle
User=node
ExecStart=/usr/bin/python /path/to/your/module.py
Restart=always
TimeoutStartSec=10
RestartSec=10

[Install]
WantedBy=multi-user.target

然后我从/etc/init.d中删除了旧的 init.d 服务文件并运行sudo systemctl daemon-reload以重新加载 systemd。

我希望我的服务自动重启,因此有重启选项。 我还发现对Type使用idlesimple更有意义。

idle 的行为与 simple 非常相似; 但是,服务二进制文件的实际执行会延迟,直到所有活动作业都被调度。 这可用于避免 shell 服务的输出与控制台上的状态输出交错。

有关我在此处使用的选项的更多详细信息。

我还尝试保留旧服务并让 systemd 重新启动服务,但我遇到了一些问题。

[Unit]
# Added this to the above
#SourcePath=/etc/init.d/old-service 

[Service]
# Replace the ExecStart from above with these
#ExecStart=/etc/init.d/old-service start
#ExecStop=/etc/init.d/old-service stop

我遇到的问题是,如果两者的名称相同,则使用 init.d 服务脚本而不是 systemd 服务。 如果你杀死了 init.d 启动的进程,systemd 脚本就会接管。 但是如果你运行service <service-name> stop它将引用旧的 init.d 服务。 所以我发现最好的方法是删除旧的 init.d 服务,而 service 命令引用 systemd 服务。

希望这可以帮助!

你可以妖魔化一个服务。 还行吧。

更重要的是你不要妖魔化别人。

服务不会在乎被妖魔化。 一个人会。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM