Python守护程序和systemd服务


78

我有一个简单的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

node.py:

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

run包含while True循环。

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

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


答案是否解决了您的问题?如果不是,请在创建DaemonContext()时尝试设置daemon_context = True。它可能会起作用。

1
@pawelbial遗憾的是,您的Python代码示例不完整(缺少导入的信息,daemon并且不清楚其Node来源),因此重现您的情况并非易事。
2015年

@pawelbial这与问题间接相关,但可能会帮助您:unix.stackexchange.com/a/226853/33386
Jonathan Komar,

Answers:


115

原因是,它没有完成启动顺序,原因是对于Type,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

请注意,-u不需要使用in python shebang,但是如果您将某些内容输出到stdout或stderr,请-u确保没有适当的输出缓冲,并且系统会立即捕获打印的行并将其记录在日志中。没有它,它会出现一些延迟。

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

systemd 使守护进程过时

尽管您的问题的标题明确询问了有关守护程序的问题,但我想,问题的核心是“如何使我的服务运行”,而使用主流程似乎要简单得多(您根本不必关心守护程序),可以被视为您问题的答案。

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

编辑:固定python -p为正确python -u。谢谢kmftzg


4
进行守护进程的原因是要支持其他不使用systemd的平台。必须为systemd创建单独的代码路径是systemd抑制可移植性的另一种方法。
尼克·巴斯汀

2
@NickBastin,这是可移植性阻碍进度和简化的另一种方式。
intelfx

2
@NickBastin systemd确实支持派生应用程序,因此,如果您不想这样做,则不需要systemd的单独代码路径。不阅读文档会抑制实际技能,并且会使评论毫无根据。
Jan Vlcinsky '18

4
@NickBastin OP谈论“简单的python脚本”和“ systemd”的使用。没有非系统平台可移植性的要求,这种要求仅存在于您将系统归咎于您的反应中。
Jan Vlcinsky '18

1
显然还有其他流程管理工具,例如supervisor或pm2。我认为“不可妖魔化”显然是统一的处理方式。
ospider

23

可以像Schnouki和Amit所描述的那样进行守护进程。但是使用systemd则没有必要。有两种更好的初始化守护程序的方法:套接字激活和使用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

显式通知意味着守护程序自己打开套接字和/或执行任何其他初始化,然后通知初始化它已准备就绪并且可以处理请求。可以使用“派生协议”来实现,但是实际上最好只是使用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”)。无需分叉或守护进程。


看起来挺好的。如何安装systemd.daemon通过pip提供的python库?
guettli '16

github.com/systemd/python-systemd#installation上的官方安装说明显示了如何使用pip安装。如果它们对您不起作用,请在github.com/systemd/python-systemd/issues上提交问题。
zbyszek '16

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()

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

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


2
关于如何DaemonContext更改程序的工作目录的最后一段刚刚解决了我的守护程序问题
DJG 2013年

2
由于更少的代码就更多了,因此我希望回答“仅使用主过程,不要守护”。
guettli

如果API在Python3中已更改,则为idk。但必须存在,import daemon.pidfile而不是import daemon.pidlockfile
reox

解除锁定后,即使守护进程完成,它也始终保持锁定状态
alper

4

另外,您最有可能需要daemon_context=True在创建时进行设置DaemonContext()

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

如果您感到好奇,请在python-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

希望这可以更好地解释。


2
我可能会误会,但我认为此标志称为“ detach_process”,而不是“ daemon_context”
Peter Turner

4

尝试将某些python init.d服务转换为CentOS 7下的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

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

我希望我的服务能够自动重启,因此需要重启选项。我也发现idle用于Type比有意义simple

空闲行为与简单行为非常相似。但是,服务二进制文件的实际执行被延迟,直到所有活动作业都被调度为止。这可用于避免将Shell服务的输出与控制台上的状态输出进行交织。

我使用的选项的更多详细信息 在这里

我还尝试过保留旧服务,并重新调整了服务的系统,但是遇到了一些问题。

[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服务。

希望这可以帮助!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.