Python守护程序和systemd服务

时间:2012-10-25 13:25:10

标签: 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

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文件不正确?

5 个答案:

答案 0 :(得分:101)

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

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

一种选择是做得更少。使用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确保没有输出缓冲到位并打印系统会立即捕获行并记录在日志中。没有它,它会出现一些延迟。

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

systemd使守护进程过时

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

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

编辑:将python -p修正为正确python -u。谢谢kmftzg

答案 1 :(得分:21)

像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它已准备好并可以提供请求。这可以使用" forking protocol"来实现,但实际上只需使用sd_notify()向systemd发送通知就更好了。 Python包装器名为systemd.daemon.notify,将使用一行[5]。

[5] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.notify

在这种情况下,单元文件将具有Type = notify和call systemd.daemon.notify(" READY = 1")在建立套接字之后。不需要分叉或守护。

答案 2 :(得分:14)

您没有创建PID文件。

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 chdir进入另一个目录,请使用DaemonContext(pidfile=pidfile, working_directory="/path/to/dir")

答案 3 :(得分:3)

此外,您最有可能在创建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

希望这可以更好地解释。

答案 4 :(得分:1)

在尝试将一些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

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

我希望我的服务能够自动重启,因此重启选项。我还发现idle使用Typesimple更有意义。

  

闲置的行为非常类似于简单;但实际执行   延迟服务二进制文件,直到调度所有活动作业。   这可以用于避免交换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服务和引用systemd服务的服务命令。

希望这有帮助!