Python守护进程和systemd服务
我有简单的Python脚本作为守护进程。 我正在尝试创buildsystemd脚本,以便能够在启动过程中启动此脚本。
当前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守护进程(但在我尝试之前没有它,症状是相似的)。
我应该实施一些额外的function,我的脚本或systemd文件不正确?
原因是,它没有完成启动顺序,对于typesforking
你的启动过程预计叉和退出(见$ man systemd.service – search分叉)。
只需使用主进程,不要守护进程
一种select是less做。 使用systemd,通常不需要创build守护进程,而且可以直接运行代码而不需要守护进程。
#!/usr/bin/python -p from somewhere import Node check = Node() check.run()
这允许使用简单的简单服务types,所以你的单元文件看起来像。
[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中的-p
是不必要的,但是如果您将某些内容打印到stdout或stderr, -p
确保没有输出缓冲,并且打印出的行会立即被systemd捕获并logging在杂志上。 没有它,它会出现一些延迟。
为此,我在单元文件中添加了StandardOutput=syslog
和StandardError=syslog
。 如果你不关心你的日记中的打印输出,不要关心这些行(他们不必在场)。
systemd
使许多守护进程过时
虽然问题的标题明确提出了守护进程的问题,但我猜,问题的核心是“如何让我的服务运行”,而使用主进程似乎更简单 (根本不用关心守护进程),它可以考虑回答你的问题。
我想,很多人只是因为“每个人都这样做”而使用守护进程。 通过systemd,守护进程的原因往往已经过时。 使用守护进程可能有一些原因,但现在将是罕见的情况。
像Schnouki和Amit所描述的那样可以守护进程。 但是使用systemd这不是必须的。 有两种更好的方法来初始化守护进程:套接字激活和sd_notify()显式通知。
对于希望在networking端口或UNIX套接字或类似服务器上侦听的守护进程,套接字激活起作用。 Systemd会打开套接字,监听它,然后在连接时产生守护进程。这是首选,因为它给pipe理员提供了最大的灵活性。 [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,并在build立套接字后调用systemd.daemon.notify(“READY = 1”)。 没有分叉或守护进程是必要的。
你没有创buildPID文件。
systemd期望你的程序在/var/run/zebra.pid
写入它的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
进入另一个目录,使用DaemonContext(pidfile=pidfile, working_directory="/path/to/dir")
。
另外,创buildDaemonContext()
时,您很可能需要设置daemon_context=True
。
这是因为,如果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
希望这个解释更好。
当我尝试在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
更有意义。
闲置行为非常类似于简单; 然而,服务二进制文件的实际执行被延迟,直到所有活动的作业被分派。 这可以用来避免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脚本就会接pipe。 但是如果你运行service <service-name> stop
它会引用旧的init.d服务。 所以我发现最好的办法就是放弃旧的init.d服务,而把服务命令引用到systemd服务。
希望这可以帮助!