Python 守护进程和 systemd 服务
Posted
技术标签:
【中文标题】Python 守护进程和 systemd 服务【英文标题】:Python daemon and systemd service 【发布时间】:2012-10-15 16:45:41 【问题描述】:我有一个作为守护进程工作的简单 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。它可能会起作用。 @pawelbial 很遗憾,您的 Python 代码示例不完整(缺少daemon
的导入并且不清楚,Node
的来源)因此不容易/不可能重现您的情况。
@pawelbial 这与问题间接相关,但可能对您有所帮助:unix.stackexchange.com/a/226853/33386
【参考方案1】:
它没有完成启动顺序的原因是,对于 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
请注意,python shebang 中的 -u
不是必需的,但如果您将某些内容打印到 stdout 或 stderr,-u
会确保没有输出缓冲到位,并且打印的行将立即被 systemd 捕获并记录在日志中。没有它,它会出现一些延迟。
为此,我在单元文件中添加了StandardOutput=syslog
和StandardError=syslog
行。如果您不关心日记中的打印输出,请不要关心这些行(它们不必存在)。
systemd
使守护进程过时
虽然您的问题的标题明确询问了守护进程,但我想,问题的核心是“如何让我的服务运行”,而 使用主进程似乎要简单得多(您不需要必须关心守护进程),它可以被视为您问题的答案。
我认为,很多人使用守护进程只是因为“每个人都这样做”。使用 systemd 守护进程的原因通常是过时的。使用守护进程可能有一些原因,但现在很少见。
编辑:将python -p
固定为正确的python -u
。谢谢kmftzg
【讨论】:
守护进程的原因是为了支持其他不使用systemd的平台。必须为 systemd 创建单独的代码路径是 systemd 抑制可移植性的另一种方式。 @NickBastin 这是可移植性阻碍进步和简化的另一种方式。 @NickBastin systemd 确实支持分叉应用程序,因此如果您不想,则不需要为 systemd 单独的代码路径。不阅读文档会抑制实践技能,并且会使 cmets 毫无根据。 @NickBastin OP 谈论“简单的 python 脚本”和“systemd”的使用。没有要求非 systemd 平台的可移植性,这种要求只存在于你指责 systemd 的反应中。 显然还有其他的进程管理工具,比如supervisord或者pm2。我认为“不守护”显然是 unix 的做事方式。【参考方案2】:我在 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。
我希望我的服务自动重启,因此有重启选项。我还发现将idle
用于Type
比simple
更有意义。
idle 的行为与 simple 非常相似;然而,实际执行 服务二进制文件被延迟,直到所有活动作业都被调度。 这可用于避免 shell 服务的输出交错 在控制台上输出状态。
有关我使用的选项的更多详细信息here。
我还尝试保留旧服务并让 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 服务。
希望这会有所帮助!
【讨论】:
【参考方案3】:可以像 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") 建立套接字后。不需要分叉或守护进程。
【讨论】:
看起来不错。如何安装这个通过pip提供systemd.daemon
的python库?
github.com/systemd/python-systemd#installation 的官方安装说明展示了如何使用 pip 进行安装。如果它们不适合您,请在github.com/systemd/python-systemd/issues 提出问题。【参考方案4】:
此外,您很可能需要在创建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
希望这能解释得更好。
【讨论】:
我可能弄错了,但我认为这个标志被称为“detach_process”而不是“daemon_context”【参考方案5】:您没有创建 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
如何改变程序的工作目录刚刚解决了我的守护问题
由于代码越少,我更喜欢“只使用主进程,不要守护进程”的答案。
idk 如果 API 在 Python3 中发生了变化。但它必须是import daemon.pidfile
而不是import daemon.pidlockfile
解除锁定后,即使守护进程完成,它也始终保持锁定状态以上是关于Python 守护进程和 systemd 服务的主要内容,如果未能解决你的问题,请参考以下文章