如何在Python中创建守护程序?

问题:如何在Python中创建守护程序?

在Google上搜索会发现x2代码段。第一个结果是该代码配方的内容,其中包含大量文档和说明,并在下面进行了一些有用的讨论。

但是,另一个代码示例虽然没有包含太多文档,但包含用于传递命令(例如启动,停止和重新启动)的示例代码。它还会创建一个PID文件,可以方便地检查守护程序是否已在运行等。

这些示例都说明了如何创建守护程序。还有其他需要考虑的事情吗?一个样本比另一个样本好吗,为什么?

Searching on Google reveals x2 code snippets. The first result is to this code recipe which has a lot of documentation and explanation, along with some useful discussion underneath.

However, another code sample, whilst not containing so much documentation, includes sample code for passing commands such as start, stop and restart. It also creates a PID file which can be handy for checking if the daemon is already running etc.

These samples both explain how to create the daemon. Are there any additional things that need to be considered? Is one sample better than the other, and why?


回答 0

当前解决方案

PEP 3143(标准守护程序进程库)的参考实现现已作为python-daemon提供

历史答案

Sander Marechal的代码示例优于最初于2004年发布的原始代码示例。我曾经为Pyro提供了一个守护程序,但如果不得不这样做,可能会使用Sander的代码。

Current solution

A reference implementation of PEP 3143 (Standard daemon process library) is now available as python-daemon.

Historical answer

Sander Marechal’s code sample is superior to the original, which was originally posted in 2004. I once contributed a daemonizer for Pyro, but would probably use Sander’s code if I had to do it over.


回答 1

成为行为良好的守护进程时,有很多事情要注意:

  • 防止核心转储(许多守护程序以root身份运行,并且核心转储可以包含敏感信息)

  • chroot监狱中表现正确

  • 根据使用情况适当设置UID,GID,工作目录,umask和其他过程参数

  • 放弃升高suidsgid特权

  • 关闭所有打开的文件描述符,并根据用例进行排除

  • 正确的行为,如果启动一个已经脱离上下文中,如initinetd

  • 为明智的守护程序行为设置信号处理程序,还可以根据用例确定特定的处理程序

  • 重定向标准流stdinstdoutstderr因为守护进程不再具有控制终端

  • 将PID文件作为合作咨询锁处理,它本身就是蠕虫完整罐,具有许多相互矛盾但有效的行为方式

  • 在进程终止时允许适当的清理

  • 实际上成为守护进程而不会导致僵尸

其中一些是标准的,如规范的UNIX文献所述(UNIX环境中的Advanced Programming,由已故的W. Richard Stevens,Addison-Wesley,1992年出版)。其他(例如流重定向和PID文件处理)是大多数守护程序用户期望的常规行为,但标准化程度较低。

PEP 3143 “标准守护程序进程库”规范涵盖了所有这些内容。该Python守护参考实现工作在Python 2.7版或更高版本,和Python 3.2或更高版本。

There are many fiddly things to take care of when becoming a well-behaved daemon process:

  • prevent core dumps (many daemons run as root, and core dumps can contain sensitive information)

  • behave correctly inside a chroot gaol

  • set UID, GID, working directory, umask, and other process parameters appropriately for the use case

  • relinquish elevated suid, sgid privileges

  • close all open file descriptors, with exclusions depending on the use case

  • behave correctly if started inside an already-detached context, such as init, inetd, etc.

  • set up signal handlers for sensible daemon behaviour, but also with specific handlers determined by the use case

  • redirect the standard streams stdin, stdout, stderr since a daemon process no longer has a controlling terminal

  • handle a PID file as a cooperative advisory lock, which is a whole can of worms in itself with many contradictory but valid ways to behave

  • allow proper cleanup when the process is terminated

  • actually become a daemon process without leading to zombies

Some of these are standard, as described in canonical Unix literature (Advanced Programming in the UNIX Environment, by the late W. Richard Stevens, Addison-Wesley, 1992). Others, such as stream redirection and PID file handling, are conventional behaviour most daemon users would expect but that are less standardised.

All of these are covered by the PEP 3143 “Standard daemon process library” specification. The python-daemon reference implementation works on Python 2.7 or later, and Python 3.2 or later.


回答 2

在开发新的守护程序应用程序时,这是我的基本“ Howdy World” Python守护程序。

#!/usr/bin/python
import time
from daemon import runner

class App():
    def __init__(self):
        self.stdin_path = '/dev/null'
        self.stdout_path = '/dev/tty'
        self.stderr_path = '/dev/tty'
        self.pidfile_path =  '/tmp/foo.pid'
        self.pidfile_timeout = 5
    def run(self):
        while True:
            print("Howdy!  Gig'em!  Whoop!")
            time.sleep(10)

app = App()
daemon_runner = runner.DaemonRunner(app)
daemon_runner.do_action()

请注意,您将需要该python-daemon库。您可以通过以下方式安装它:

pip install python-daemon

然后以开始./howdy.py start,然后以停止./howdy.py stop

Here’s my basic ‘Howdy World’ Python daemon that I start with, when I’m developing a new daemon application.

#!/usr/bin/python
import time
from daemon import runner

class App():
    def __init__(self):
        self.stdin_path = '/dev/null'
        self.stdout_path = '/dev/tty'
        self.stderr_path = '/dev/tty'
        self.pidfile_path =  '/tmp/foo.pid'
        self.pidfile_timeout = 5
    def run(self):
        while True:
            print("Howdy!  Gig'em!  Whoop!")
            time.sleep(10)

app = App()
daemon_runner = runner.DaemonRunner(app)
daemon_runner.do_action()

Note that you’ll need the python-daemon library. You can install it by:

pip install python-daemon

Then just start it with ./howdy.py start, and stop it with ./howdy.py stop.


回答 3

请注意python-daemon软件包,该软件包可立即解决守护程序背后的许多问题。

它支持的其他功能(来自Debian软件包描述):

  • 将流程分离到其自己的流程组中。
  • 设置适合在chroot内部运行的进程环境。
  • 放弃suid和sgid特权。
  • 关闭所有打开的文件描述符。
  • 更改工作目录,uid,gid和umask。
  • 设置适当的信号处理程序。
  • 打开stdin,stdout和stderr的新文件描述符。
  • 管理指定的PID锁定文件。
  • 注册清理功能以进行退出处理。

Note the python-daemon package which solves a lot of problems behind daemons out of the box.

Among other features it enables to (from Debian package description):

  • Detach the process into its own process group.
  • Set process environment appropriate for running inside a chroot.
  • Renounce suid and sgid privileges.
  • Close all open file descriptors.
  • Change the working directory, uid, gid, and umask.
  • Set appropriate signal handlers.
  • Open new file descriptors for stdin, stdout, and stderr.
  • Manage a specified PID lock file.
  • Register cleanup functions for at-exit processing.

回答 4

一种替代方法-创建一个普通的,非守护进程的Python程序,然后使用supervisor在外部对其进行守护进程。这可以节省很多麻烦,并且* nix和语言可移植。

An alternative — create a normal, non-daemonized Python program then externally daemonize it using supervisord. This can save a lot of headaches, and is *nix- and language-portable.


回答 5

可能不是该问题的直接答案,但是systemd可以用作守护程序来运行您的应用程序。这是一个例子:

[Unit]
Description=Python daemon
After=syslog.target
After=network.target

[Service]
Type=simple
User=<run as user>
Group=<run as group group>
ExecStart=/usr/bin/python <python script home>/script.py

# Give the script some time to startup
TimeoutSec=300

[Install]
WantedBy=multi-user.target

我喜欢这种方法,因为为您完成了很多工作,然后守护程序脚本的行为与系统其余部分的行为类似。

-奥比

Probably not a direct answer to the question, but systemd can be used to run your application as a daemon. Here is an example:

[Unit]
Description=Python daemon
After=syslog.target
After=network.target

[Service]
Type=simple
User=<run as user>
Group=<run as group group>
ExecStart=/usr/bin/python <python script home>/script.py

# Give the script some time to startup
TimeoutSec=300

[Install]
WantedBy=multi-user.target

I prefer this method because a lot of the work is done for you, and then your daemon script behaves similarly to the rest of your system.

-Orby


回答 6

YapDi是一个相对较新的python模块,出现在Hacker News中。看起来非常有用,可用于将python脚本从脚本内部转换为守护程序模式。

YapDi is a relatively new python module that popped up in Hacker News. Looks pretty useful, can be used to convert a python script into daemon mode from inside the script.


回答 7

由于python-daemon尚不支持python 3.x,并且从邮件列表中可以读取的内容来看,它可能永远不会支持,因此我编写了PEP 3143的新实现:pep3143daemon

pep3143daemon至少应支持python 2.6、2.7和3.x

它还包含一个PidFile类。

该库仅取决于标准库和六个模块。

它可以用作python-daemon的替代品。

这是文档

since python-daemon has not yet supported python 3.x, and from what can be read on the mailing list, it may never will, i have written a new implementation of PEP 3143: pep3143daemon

pep3143daemon should support at least python 2.6, 2.7 and 3.x

It also contains a PidFile class.

The library only depends on the standard library and on the six module.

It can be used as a drop in replacement for python-daemon.

Here is the documentation.


回答 8

此函数会将应用程序转换为守护程序:

import sys
import os

def daemonize():
    try:
        pid = os.fork()
        if pid > 0:
            # exit first parent
            sys.exit(0)
    except OSError as err:
        sys.stderr.write('_Fork #1 failed: {0}\n'.format(err))
        sys.exit(1)
    # decouple from parent environment
    os.chdir('/')
    os.setsid()
    os.umask(0)
    # do second fork
    try:
        pid = os.fork()
        if pid > 0:
            # exit from second parent
            sys.exit(0)
    except OSError as err:
        sys.stderr.write('_Fork #2 failed: {0}\n'.format(err))
        sys.exit(1)
    # redirect standard file descriptors
    sys.stdout.flush()
    sys.stderr.flush()
    si = open(os.devnull, 'r')
    so = open(os.devnull, 'w')
    se = open(os.devnull, 'w')
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())

This function will transform an application to a daemon:

import sys
import os

def daemonize():
    try:
        pid = os.fork()
        if pid > 0:
            # exit first parent
            sys.exit(0)
    except OSError as err:
        sys.stderr.write('_Fork #1 failed: {0}\n'.format(err))
        sys.exit(1)
    # decouple from parent environment
    os.chdir('/')
    os.setsid()
    os.umask(0)
    # do second fork
    try:
        pid = os.fork()
        if pid > 0:
            # exit from second parent
            sys.exit(0)
    except OSError as err:
        sys.stderr.write('_Fork #2 failed: {0}\n'.format(err))
        sys.exit(1)
    # redirect standard file descriptors
    sys.stdout.flush()
    sys.stderr.flush()
    si = open(os.devnull, 'r')
    so = open(os.devnull, 'w')
    se = open(os.devnull, 'w')
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())

回答 9

恐怕@Dustin提到的守护程序模块对我不起作用。相反,我安装了python-daemon并使用了以下代码:

# filename myDaemon.py
import sys
import daemon
sys.path.append('/home/ubuntu/samplemodule') # till __init__.py
from samplemodule import moduleclass 

with daemon.DaemonContext():
    moduleclass.do_running() # I have do_running() function and whatever I was doing in __main__() in module.py I copied in it.

跑步很容易

> python myDaemon.py

为了完整起见,这里是samplemodule目录内容

>ls samplemodule
__init__.py __init__.pyc moduleclass.py

moduleclass.py的内容可以是

class moduleclass():
    ...

def do_running():
    m = moduleclass()
    # do whatever daemon is required to do.

I am afraid the daemon module mentioned by @Dustin didn’t work for me. Instead I installed python-daemon and used the following code:

# filename myDaemon.py
import sys
import daemon
sys.path.append('/home/ubuntu/samplemodule') # till __init__.py
from samplemodule import moduleclass 

with daemon.DaemonContext():
    moduleclass.do_running() # I have do_running() function and whatever I was doing in __main__() in module.py I copied in it.

Running is easy

> python myDaemon.py

just for completeness here is samplemodule directory content

>ls samplemodule
__init__.py __init__.pyc moduleclass.py

The content of moduleclass.py can be

class moduleclass():
    ...

def do_running():
    m = moduleclass()
    # do whatever daemon is required to do.

回答 10

在python守护进程中要考虑的一件事:

如果您正在使用python 日志记录,并且希望在守护进程后继续使用它,请确保调用close()处理程序(尤其是文件处理程序)。

如果您不这样做,那么处理程序仍然可以认为它已打开文件,并且您的消息将完全消失-换句话说,请确保记录器知道其文件已关闭!

这假定您在守护程序时无差别地关闭所有打开的文件描述符-相反,您可以尝试关闭除日志文件以外的所有文件(但是通常先关闭所有文件然后再重新打开所需的文件更简单)。

One more to thing to think about when daemonizing in python:

If your are using python logging and you want to continue using it after daemonizing, make sure to call close() on the handlers (particularly the file handlers).

If you don’t do this the handler can still think it has files open, and your messages will simply disappear – in other words make sure the logger knows its files are closed!

This assumes when you daemonise you are closing ALL the open file descriptors indiscriminatingly – instead you could try closing all but the log files (but it’s usually simpler to close all then reopen the ones you want).


回答 11

尽管您可能更喜欢python-daemon模块提供的纯Python解决方案,但至少在BSDLinux上有一个daemon(3)功能libc可以正确执行操作。

从python调用它很容易:

import ctypes

ctypes.CDLL(None).daemon(0, 0) # Read the man-page for the arguments' meanings

剩下要做的唯一事情就是创建(和锁定)PID文件。但是你可以应付自己…

Though you may prefer the pure Python solution provided by the python-daemon module, there is a daemon(3) function in libc — at least, on BSD and Linux — which will do the right thing.

Calling it from python is easy:

import ctypes

ctypes.CDLL(None).daemon(0, 0) # Read the man-page for the arguments' meanings

The only remaining thing to do is creation (and locking) of the PID-file. But that you can handle yourself…


回答 12

我修改了Sander Marechal的代码示例中的几行(在接受的答案中由@JeffBauer提及),添加了quit()在守护程序停止之前执行的方法。有时这非常有用。

这里是。

注意:我不使用“ python-daemon”模块,因为该文档仍然丢失(另请参见许多其他SO问题)并且相当晦涩(如何使用此模块从命令行正确启动/停止守护程序?)

I modified a few lines in Sander Marechal’s code sample (mentioned by @JeffBauer in the accepted answer) to add a quit() method that gets executed before the daemon is stopped. This is sometimes very useful.

Here it is.

Note: I don’t use the “python-daemon” module because the documentation is still missing (see also many other SO questions) and is rather obscure (how to start/stop properly a daemon from command line with this module?)


回答 13

经过几年的尝试(我尝试了这里给出的所有答案,但最后都没有什么缺点),现在我意识到,有比直接从Python启动,停止,重新启动守护程序更好的方法:改用OS工具。

例如,对于Linux,我这样做不是启动python myapp start和,而是python myapp stop启动应用程序:

screen -S myapp python myapp.py    
CTRL+A, D to detach

screen -dmS myapp python myapp.py一个命令启动并分离它

然后:

screen -r myapp

再次连接到此终端。进入终端后,可以使用CTRL + C停止它。

After a few years and many attempts (I tried all the answers given here, but all of them had minor drawbacks at the end), now I realize that there is a better way than wanting to start, stop, restart a daemon directly from Python: use the OS tools instead.

For example, for Linux, instead of doing python myapp start and python myapp stop, I do this to start the app:

screen -S myapp python myapp.py    
CTRL+A, D to detach

or screen -dmS myapp python myapp.py to start and detach it in one command.

Then:

screen -r myapp

to attach to this terminal again. Once in the terminal, it’s possible to use CTRL+C to stop it.


回答 14

使用Python创建守护程序的最简单方法是使用Twisted事件驱动的框架。它为您处理守护进程所需的所有东西。它使用Reactor模式来处理并发请求。

The easiest way to create daemon with Python is to use the Twisted event-driven framework. It handles all of the stuff necessary for daemonization for you. It uses the Reactor Pattern to handle concurrent requests.


回答 15

80%的情况下,当人们说“守护程序”时,他们只想要一台服务器。由于这一点上的问题尚不清楚,因此很难说出答案的可能范围。由于服务器已足够,因此从那里开始。如果确实需要一个实际的“守护程序”(这种情况很少见),请继续阅读nohup作为守护服务器的一种方式。

在需要实际的守护程序之前,只需编写一个简单的服务器即可。

另请参阅WSGI参考实现。

另请参阅简单HTTP服务器

“还有其他需要考虑的事情吗?”是的。大约一百万的东西。什么协议?有多少个请求?每个请求服务多长时间?他们多久到达一次?您会使用专门的流程吗?线程?子流程?编写守护程序是一项艰巨的工作。

80% of the time, when folks say “daemon”, they only want a server. Since the question is perfectly unclear on this point, it’s hard to say what the possible domain of answers could be. Since a server is adequate, start there. If an actual “daemon” is actually needed (this is rare), read up on nohup as a way to daemonize a server.

Until such time as an actual daemon is actually required, just write a simple server.

Also look at the WSGI reference implementation.

Also look at the Simple HTTP Server.

“Are there any additional things that need to be considered? ” Yes. About a million things. What protocol? How many requests? How long to service each request? How frequently will they arrive? Will you use a dedicated process? Threads? Subprocesses? Writing a daemon is a big job.