Python守护进程和systemd服务
我有一个简单的Python脚本作为守护进程工作。我正在尝试创建systemd脚本,以便能够在启动期间启动此脚本 当前systemd脚本:Python守护进程和systemd服务,python,python-daemon,systemd,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.
[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
包含而True
循环
我尝试使用systemctl start zebra node.service
运行此服务。不幸的是,服务从未完成顺序声明-我必须按Ctrl+C。
脚本正在运行,但状态为“正在激活”,一段时间后,它将更改为“正在停用”。
现在我正在使用python守护进程(但在我尝试不使用它之前,症状与之类似)
我应该在脚本中实现一些附加功能,还是systemd文件不正确?您没有创建PID文件 systemd希望您的程序将其PID写入
/var/run/zebra.PID
。如果您不这样做,systemd可能会认为您的程序失败,因此将其停用
要添加PID文件,请安装并将代码更改为:
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/pidlockfile.py
,从导入中删除LinkFileLock
,并将从lockfile.linklockfile导入linklockfile作为LinkFileLock
)
请注意另一件事:
DaemonContext
将程序的工作目录更改为/
,使服务文件的工作目录
无效。如果希望DaemonContext
将chdir导入另一个目录,请使用DaemonContext(pidfile=pidfile,working_directory=“/path/to/dir”)
此外,在创建DaemonContext()
时,很可能需要设置daemon_context=True
这是因为,如果python守护进程
检测到它在init系统下运行,它就不会与父进程分离systemd
期望运行Type=forking
的守护进程会这样做。因此,您需要这样做,否则systemd
将继续等待,并最终终止进程
如果您感到好奇,在python守护进程的守护进程模块中,您将看到以下代码:
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
希望这能更好地解释。可以像Schnouki和Amit描述的那样进行daemonize。但对于systemd来说,这是不必要的。初始化守护进程有两种更好的方法:套接字激活和使用sd_notify()显式通知
套接字激活适用于希望在网络端口或UNIX套接字或类似端口上侦听的守护进程。Systemd将打开套接字,监听它,然后在连接进入时生成守护进程。这是首选方法,因为它为管理员提供了最大的灵活性。[1] [2]给出了一个很好的介绍,[3]描述了C API,而[4]描述了Python API
[1]
[2]
[3]
[4]
显式通知意味着守护进程打开套接字本身和/或执行任何其他初始化,然后通知init它已经准备好并可以为请求提供服务。这可以通过“forking协议”实现,但实际上,使用sd_notify()向systemd发送通知更好。
Python包装器名为systemd.daemon.notify,将作为一行使用[5]
[5]
在这种情况下,单位文件的Type=notify,并调用
systemd.daemon.notify(“READY=1”)在建立套接字之后。无需分叉或后台处理。其原因是,对于类型分叉
而言,您的启动过程将分叉并退出(请参阅$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
是不必要的,但如果您将某些内容打印到标准输出或标准输出,则-u
确保没有适当的输出缓冲,并且打印的行将立即被systemd捕获并记录在日志中。如果没有它,它会出现一些延迟
为此,我在单元文件中添加了行StandardOutput=syslog
和StandardError=syslog
。如果您不关心日记中的打印输出,请不要关心这些行(它们不必存在)
systemd
使daemonization过时
虽然您问题的标题明确询问了守护进程,但我想,问题的核心是“如何使我的服务运行”,虽然使用主进程似乎简单得多(您根本不必关心守护进程),但它可以被视为您问题的答案
我认为,很多人使用Daemoning只是因为“每个人都这么做”。使用systemd时,后台监控的原因通常是过时的。使用守护进程可能有一些原因,但现在这种情况很少见
编辑:将python-p
修改为正确的python-u
。感谢kmftzg我在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
更有意义
空闲行为与简单行为非常相似;然而,实际执行
服务二进制文件的调度将延迟,直到调度所有活动作业。
这可用于避免交错
[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