Python守护程序和systemd服务

paw*_*ial 70 python python-daemon systemd

我有简单的Python脚本,可以作为守护进程使用.我正在尝试创建systemd脚本,以便能够在启动期间启动此脚本.

当前的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
Run Code Online (Sandbox Code Playgroud)

node.py:

if __name__ == '__main__':
    with daemon.DaemonContext():
        check = Node()
        check.run()
Run Code Online (Sandbox Code Playgroud)

run包含while True循环.

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

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

Jan*_*sky 106

原因是,它没有完成启动顺序,对于Type forking你的启动过程有望分叉和退出(参见$ man systemd.service - 搜索forking).

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

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

#!/usr/bin/python -u
from somewhere import Node
check = Node()
check.run()
Run Code Online (Sandbox Code Playgroud)

这允许使用更简单的服务类型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
Run Code Online (Sandbox Code Playgroud)

注意,-u在python shebang中没有必要,但是如果你向stdout或stderr输出一些内容,-u确保没有输出缓冲,并且打印的行将立即被systemd捕获并记录在日志中.如果没有它,它会出现一些延迟.

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

systemd 使守护进程变得过时

虽然你的问题的标题明确地询问守护进程,但我猜,问题的核心是"如何让我的服务运行",而使用主进程似乎更简单(你根本不必关心守护进程),它可以考虑回答你的问题.

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

编辑:固定python -p到正确python -u.谢谢kmftzg

  • 守护进程的原因是支持不使用systemd的其他平台.必须为systemd创建单独的代码路径是systemd禁止可移植性的另一种方式. (3认同)
  • @NickBastin OP谈论"简单的python脚本"和使用"systemd".没有非系统平台可移植性的请求,这种请求只存在于你的反应系统中. (3认同)
  • @NickBastin,这是可移植性抑制进步和简化的另一种方式。 (2认同)
  • @NickBastin systemd 确实支持分叉应用程序,因此如果您不想,则无需为 systemd 设置单独的代码路径。不阅读文档会抑制实践技能,并使评论毫无根据。 (2认同)

zby*_*zek 22

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

套接字激活适用于想要侦听网络端口或UNIX套接字等的守护进程.Systemd会打开套接字,监听它,然后在连接进入时生成守护进程.这是首选的approch,因为它为管理员提供了最大的灵活性.[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").不需要分叉或守护.


Sch*_*uki 15

您没有创建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()
Run Code Online (Sandbox Code Playgroud)

(快速注意:最近更新了一些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`如何更改程序工作目录的最后一段刚刚解决了我的守护问题 (2认同)
  • 由于代码越少越好,我更喜欢答案"只使用主进程,不要守护进程". (2认同)

rad*_*tek 5

当我尝试在 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
Run Code Online (Sandbox Code Playgroud)

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

我希望我的服务自动重新启动,因此需要重新启动选项。我还发现使用idleforType比 更有意义simple

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
Run Code Online (Sandbox Code Playgroud)

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

希望这可以帮助!