简体   繁体   English

Python 守护进程和 systemd 服务

[英]Python daemon and systemd service

I have a simple Python script working as a daemon.我有一个简单的 Python 脚本作为守护进程工作。 I am trying to create systemd script to be able to start this script during startup.我正在尝试创建 systemd 脚本以便能够在启动期间启动此脚本。

Current systemd script:当前系统脚本:

[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:节点.py:

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

run contains while True loop. run包含while True循环。

I try to run this service with systemctl start zebra-node.service .我尝试使用systemctl start zebra-node.service运行此服务。 Unfortunately service never finished stating sequence - I have to press Ctrl+C.不幸的是,服务从未完成说明顺序 - 我必须按 Ctrl+C。 Script is running, but status is activating and after a while it change to deactivating.脚本正在运行,但状态为激活,一段时间后变为停用。 Now I am using python-daemon (but before I tried without it and the symptoms were similar).现在我正在使用 python-daemon(但在我尝试没有它并且症状相似之前)。

Should I implement some additional features to my script or is systemd file incorrect?我应该为我的脚本实现一些附加功能还是 systemd 文件不正确?

The reason, it does not complete the startup sequence is, that for Type forking your startup process is expected to fork and exit (see $ man systemd.service - search for forking).它没有完成启动顺序的原因是,对于类型forking ,您的启动过程预计会分叉并退出(请参阅 $ man systemd.service - 搜索分叉)。

Simply use only the main process, do not daemonize只使用主进程,不要守护进程

One option is to do less.一种选择是少做。 With systemd, there is often no need to create daemons and you may directly run the code without daemonizing.使用 systemd,通常不需要创建守护进程,您可以直接运行代码而无需守护进程。

#!/usr/bin/python -u
from somewhere import Node
check = Node()
check.run()

This allows using simpler Type of service called simple , so your unit file would look like.这允许使用称为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

Note, that the -u in python shebang is not necessary, but in case you print something out to the stdout or stderr, the -u makes sure, there is no output buffering in place and printed lines will be immediately caught by systemd and recorded in journal.请注意,python shebang 中的-u不是必需的,但是如果您将某些内容打印到 stdout 或 stderr,则-u确保没有输出缓冲到位,并且打印的行将立即被 systemd 捕获并记录在日记中。 Without it, it would appear with some delay.没有它,它会出现一些延迟。

For this purpose I added into unit file the lines StandardOutput=syslog and StandardError=syslog .为此,我在单元文件中添加了StandardOutput=syslogStandardError=syslog行。 If you do not care about printed output in your journal, do not care about these lines (they do not have to be present).如果您不关心日记中的打印输出,请不要关心这些行(它们不必存在)。

systemd makes daemonization obsolete systemd使守护进程过时

While the title of your question explicitly asks about daemonizing, I guess, the core of the question is "how to make my service running" and while using main process seems much simpler (you do not have to care about daemons at all), it could be considered answer to your question.虽然您的问题的标题明确询问了守护进程,但我想,问题的核心是“如何使我的服务运行”,而使用主进程似乎要简单得多(您根本不必关心守护进程),它可以考虑回答你的问题。

I think, that many people use daemonizing just because "everybody does it".我认为,很多人使用守护进程只是因为“每个人都这样做”。 With systemd the reasons for daemonizing are often obsolete.使用 systemd 守护进程的原因通常是过时的。 There might be some reasons to use daemonization, but it will be rare case now.使用守护进程可能有一些原因,但现在这种情况很少见。

EDIT: fixed python -p to proper python -u .编辑:将python -p修复为正确的python -u thanks kmftzg谢谢kmftzg

It is possible to daemonize like Schnouki and Amit describe.可以像 Schnouki 和 Amit 描述的那样进行守护进程。 But with systemd this is not necessary.但是对于 systemd,这不是必需的。 There are two nicer ways to initialize the daemon: socket-activation and explicit notification with sd_notify().有两种更好的方式来初始化守护进程:socket-activation 和使用 sd_notify() 的显式通知。

Socket activation works for daemons which want to listen on a network port or UNIX socket or similar.套接字激活适用于希望在网络端口或 UNIX 套接字或类似端口上侦听的守护进程。 Systemd would open the socket, listen on it, and then spawn the daemon when a connection comes in. This is the preferred approch because it gives the most flexibility to the administrator. Systemd 将打开套接字,侦听它,然后在连接进入时生成守护进程。这是首选方法,因为它为管理员提供了最大的灵活性。 [1] and [2] give a nice introduction, [3] describes the C API, while [4] describes the Python API. [1] 和 [2] 给出了很好的介绍,[3] 描述了 C API,而 [4] 描述了 Python API。

[1] http://0pointer.de/blog/projects/socket-activation.html [1] http://0pointer.de/blog/projects/socket-activation.html
[2] http://0pointer.de/blog/projects/socket-activation2.html [2] http://0pointer.de/blog/projects/socket-activation2.html
[3] http://www.freedesktop.org/software/systemd/man/sd_listen_fds.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 [4] http://www.freedesktop.org/software/systemd/python-systemd/daemon.html#systemd.daemon.listen_fds

Explicit notification means that the daemon opens the sockets itself and/or does any other initialization, and then notifies init that it is ready and can serve requests.显式通知意味着守护程序自己打开套接字和/或进行任何其他初始化,然后通知 init 它已准备好并可以为请求提供服务。 This can be implemented with the "forking protocol", but actually it is nicer to just send a notification to systemd with sd_notify().这可以通过“分叉协议”来实现,但实际上最好使用 sd_notify() 向 systemd 发送通知。 Python wrapper is called systemd.daemon.notify and will be one line to use [5]. Python 包装器称为 systemd.daemon.notify 并且将是使用 [5] 的一行。

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

In this case the unit file would have Type=notify, and call systemd.daemon.notify("READY=1") after it has established the sockets.在这种情况下,单元文件将具有 Type=notify,并在建立套接字后调用 systemd.daemon.notify("READY=1")。 No forking or daemonization is necessary.不需要分叉或守护进程。

You're not creating the PID file.您没有创建 PID 文件。

systemd expects your program to write its PID in /var/run/zebra.pid . systemd 期望您的程序将其 PID 写入/var/run/zebra.pid As you don't do it, systemd probably thinks that your program is failing, hence deactivating it.如果您不这样做,systemd 可能会认为您的程序失败,因此将其停用。

To add the PID file, install lockfile and change your code to this:要添加 PID 文件,请安装lockfile并将您的代码更改为:

import daemon
import daemon.pidlockfile 

pidfile = daemon.pidlockfile.PIDLockFile("/var/run/zebra.pid")
with daemon.DaemonContext(pidfile=pidfile):
    check = Node()
    check.run()

(Quick note: some recent update of lockfile changed its API and made it incompatible with python-daemon. To fix it, edit daemon/pidlockfile.py , remove LinkFileLock from the imports, and add from lockfile.linklockfile import LinkLockFile as LinkFileLock .) (快速说明:最近对lockfile的一些更新更改了它的 API,使其与 python-daemon 不兼容。要修复它,请编辑daemon/pidlockfile.py ,从导入中删除LinkFileLock ,然后添加from lockfile.linklockfile import LinkLockFile as LinkFileLock 。)

Be careful of one other thing: DaemonContext changes the working dir of your program to / , making the WorkingDirectory of your service file useless.注意另一件事: DaemonContext将程序的工作目录更改为/ ,使服务文件的WorkingDirectory无用。 If you want DaemonContext to chdir into another directory, use DaemonContext(pidfile=pidfile, working_directory="/path/to/dir") .如果您希望DaemonContext chdir 进入另一个目录,请使用DaemonContext(pidfile=pidfile, working_directory="/path/to/dir")

Also, you most likely need to set daemon_context=True when creating the DaemonContext() .此外,您很可能需要在创建DaemonContext()时设置daemon_context=True

This is because, if python-daemon detects that if it is running under a init system, it doesn't detach from the parent process.这是因为,如果python-daemon检测到它在 init 系统下运行,它不会与父进程分离。 systemd expects that the daemon process running with Type=forking will do so. systemd期望以Type=forking运行的守护进程会这样做。 Hence, you need that, else systemd will keep waiting, and finally kill the process.因此,您需要它,否则systemd将继续等待,并最终终止该进程。

If you are curious, in python-daemon 's daemon module, you will see this code:如果你好奇,在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

Hopefully this explains better.希望这能更好地解释。

I came across this question when trying to convert some python init.d services to systemd under CentOS 7. This seems to work great for me, by placing this file in /etc/systemd/system/ :我在 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

I then dropped my old init.d service file from /etc/init.d and ran sudo systemctl daemon-reload to reload systemd.然后我从/etc/init.d中删除了旧的 init.d 服务文件并运行sudo systemctl daemon-reload以重新加载 systemd。

I wanted my service to auto restart, hence the restart options.我希望我的服务自动重启,因此有重启选项。 I also found using idle for Type made more sense than simple .我还发现对Type使用idlesimple更有意义。

Behavior of idle is very similar to simple; idle 的行为与 simple 非常相似; however, actual execution of the service binary is delayed until all active jobs are dispatched.但是,服务二进制文件的实际执行会延迟,直到所有活动作业都被调度。 This may be used to avoid interleaving of output of shell services with the status output on the console.这可用于避免 shell 服务的输出与控制台上的状态输出交错。

More details on the options I used here .有关我在此处使用的选项的更多详细信息。

I also experimented with keeping the old service and having systemd resart the service but I ran into some issues.我还尝试保留旧服务并让 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

The issues I experienced was that the init.d service script was used instead of the systemd service if both were named the same.我遇到的问题是,如果两者的名称相同,则使用 init.d 服务脚本而不是 systemd 服务。 If you killed the init.d initiated process, the systemd script would then take over.如果你杀死了 init.d 启动的进程,systemd 脚本就会接管。 But if you ran service <service-name> stop it would refer to the old init.d service.但是如果你运行service <service-name> stop它将引用旧的 init.d 服务。 So I found the best way was to drop the old init.d service and the service command referred to the systemd service instead.所以我发现最好的方法是删除旧的 init.d 服务,而 service 命令引用 systemd 服务。

Hope this helps!希望这可以帮助!

You MAY demonise a service.你可以妖魔化一个服务。 This is ok.还行吧。

What's more important is that you do not demonise other people.更重要的是你不要妖魔化别人。

A service won't care about being demonised.服务不会在乎被妖魔化。 A human being will.一个人会。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM