Làm thế nào để bạn tạo một daemon trong Python?


244

Tìm kiếm trên Google cho thấy các đoạn mã x2. Kết quả đầu tiên là công thức mã này có rất nhiều tài liệu và giải thích, cùng với một số thảo luận hữu ích bên dưới.

Tuy nhiên, một mẫu mã khác , trong khi không chứa quá nhiều tài liệu, bao gồm mã mẫu để truyền các lệnh như bắt đầu, dừng và khởi động lại. Nó cũng tạo ra một tệp PID có thể thuận tiện cho việc kiểm tra nếu daemon đang chạy, v.v.

Các mẫu này đều giải thích cách tạo daemon. Có bất kỳ điều bổ sung cần phải được xem xét? Là một mẫu tốt hơn mẫu kia, và tại sao?


1
Tôi đã luôn luôn tìm thấy mã daemonization không cần thiết. Tại sao không chỉ để vỏ làm điều đó?
emil.p.stanchev

17
Bởi vì nó không làm setsid hoặc setpgrp.
bmargulies

4
Sử dụng giám sát.org . Bằng cách này, bạn không cần rẽ nhánh () hoặc chuyển hướng bạn stdin / stderr. Chỉ cần viết một chương trình bình thường.
guettli

Câu trả lời:


169

Giải pháp tạm thời

Một triển khai tham chiếu của PEP 3143 (Thư viện quy trình daemon tiêu chuẩn) hiện có sẵn dưới dạng python-daemon .

Câu trả lời lịch sử

Mẫu mã của Sander Marechal vượt trội so với bản gốc, được đăng lần đầu vào năm 2004. Tôi đã từng đóng góp một trình nền cho Pyro, nhưng có lẽ sẽ sử dụng mã của Sander nếu tôi phải làm điều đó.


72
Chỉnh sửa: Vì ban đầu tôi đã đăng bài trả lời này, nên đã triển khai tham chiếu PEP 3143 ngay bây giờ: pypi.python.org/pypi/python-daemon
Jeff Bauer

@JeffBauer Liên kết ban đầu đã chết, tôi nhớ nó rất hữu ích, bạn sẽ không biết một liên kết trực tiếp cho điều đó chứ?
CrazyCasta

1
@CrazyCasta: Phiên bản của Sander Marechal vẫn có sẵn trên Wayback Machine
Jeff Bauer

1
@JeffBauer: Mã của Sander vẫn tốt hơn http://pypi.python.org/pypi/python-daemon. Đáng tin cậy hơn. Chỉ cần một ví dụ: cố gắng bắt đầu hai lần cùng một daemon với python-daemon: lỗi lớn xấu xí. Với mã của Sander: một thông báo hay "Daemon đã chạy."
Basj

2
Vì tài liệu mô-đun "python-daemon" vẫn còn thiếu (xem thêm nhiều câu hỏi SO khác) và khá mơ hồ (làm thế nào để bắt đầu / dừng đúng một daemon từ dòng lệnh với mô-đun này?), Tôi đã sửa đổi mẫu mã của Sander Marechal để thêm quit()phương thức được thực thi trước khi daemon bị dừng. Nó đây rồi
Basj

163

Có rất nhiều điều khó khăn để chăm sóc khi trở thành một quy trình daemon hoạt động tốt :

  • ngăn chặn các bãi chứa lõi (nhiều trình tiện ích chạy dưới quyền root và các bãi chứa lõi có thể chứa thông tin nhạy cảm)

  • cư xử đúng trong chrootgaol

  • đặt UID, GID, thư mục làm việc, ô và các tham số quy trình khác một cách thích hợp cho trường hợp sử dụng

  • từ bỏ nâng cao suid, sgidđặc quyền

  • đóng tất cả các mô tả tệp đang mở, với các loại trừ tùy thuộc vào trường hợp sử dụng

  • cư xử một cách chính xác nếu bắt đầu bên trong một bối cảnh đã kề, chẳng hạn như init, inetdvv

  • thiết lập trình xử lý tín hiệu cho hành vi daemon hợp lý, nhưng cũng với các trình xử lý cụ thể được xác định bởi trường hợp sử dụng

  • chuyển hướng dòng tiêu chuẩn stdin, stdout, stderrkể từ khi một tiến trình daemon không còn có một thiết bị đầu cuối kiểm soát

  • xử lý một tập tin PID như một khóa tư vấn hợp tác, đó là toàn bộ một con giun trong chính nó với nhiều cách đối nghịch nhưng hợp lệ để hành xử

  • cho phép dọn dẹp đúng cách khi quá trình kết thúc

  • thực sự trở thành một quá trình daemon mà không dẫn đến zombie

Một số trong số này là tiêu chuẩn , như được mô tả trong tài liệu Unix chính tắc ( Lập trình nâng cao trong môi trường UNIX , của cố W. Richard Stevens, Addison-Wesley, 1992). Những người khác, chẳng hạn như chuyển hướng dòng và xử lý tệp PID , là hành vi thông thường mà hầu hết người dùng daemon mong đợi nhưng điều đó ít được tiêu chuẩn hóa.

Tất cả những điều này được bao phủ bởi đặc tả thư viện quy trình daemon tiêu chuẩn PEP 3143 . Việc triển khai tham chiếu python-daemon hoạt động trên Python 2.7 trở lên và Python 3.2 trở lên.


26
Tiếng gaol được viết đúng chính tả, bởi vì đó là cách W. Richard Stevens đánh vần nó :-)
bignose

7
Gaol là một thứ tiếng Anh . Các poster là từ Úc vì vậy nó có ý nghĩa.
devin

1
Bất kỳ kế hoạch làm cho một phiên bản thân thiện py3k?
Tim Tonomall

97

Đây là trình nền Python 'Howdy World' cơ bản của tôi mà tôi bắt đầu, khi tôi đang phát triển một ứng dụng trình nền mới.

#!/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()

Lưu ý rằng bạn sẽ cần python-daemonthư viện. Bạn có thể cài đặt nó bằng cách:

pip install python-daemon

Sau đó, chỉ cần bắt đầu với nó ./howdy.py start, và dừng lại với ./howdy.py stop.


5
Đó là daemonmô-đun bạn nhập không phải là một phần tiêu chuẩn của Python (chưa). Nó cần phải được cài đặt với pip install python-daemonhoặc tương đương.
Nate

6
Tôi đã cài đặt python-daemon như bạn mô tả, nhưng khi tôi cố chạy ứng dụng của mình (giống như 3 dòng cuối cùng của bạn), tôi nhận được ImportError: không thể nhập tên người chạy
Nostradamnit

Bạn có thể kiểm tra nếu nó được cài đặt đúng cách? $ dpkg -L trăn-daemon | người chạy grep /usr/share/pyspl/daemon/runner.py
Dustin Kirkland

4
Gợi ý này dường như đã lỗi thời - kể từ tháng 9 năm 2013, dù sao, python.org/dev/peps/pep-3143 không đề cập đến "người chạy" có thể được nhập. Điều này tất nhiên sẽ giải thích quan sát của @ Nostradamnit.
offby1

2
Điều này vẫn hoạt động tốt đối với tôi, vào tháng 9 năm 2013, trên Ubuntu 13.04, với các gói Python, python2.7 và python-daemon được cài đặt. Tuy nhiên, với python3, tôi thấy lỗi, "từ trình chạy nhập daemon ImportError: Không có mô-đun nào có tên 'daemon'"
Dustin Kirkland

42

Lưu ý gói python-daemon giúp giải quyết rất nhiều vấn đề đằng sau daemon.

Trong số các tính năng khác, nó cho phép (từ mô tả gói Debian):

  • Tách quy trình thành nhóm quy trình riêng của mình.
  • Đặt môi trường quá trình thích hợp để chạy bên trong một chroot.
  • Từ bỏ các đặc quyền suid và sgid.
  • Đóng tất cả các mô tả tập tin mở.
  • Thay đổi thư mục làm việc, uid, gid và umask.
  • Đặt bộ xử lý tín hiệu thích hợp.
  • Mở mô tả tệp mới cho stdin, stdout và stderr.
  • Quản lý một tệp khóa PID được chỉ định.
  • Đăng ký chức năng dọn dẹp để xử lý tại lối ra.

35

Một cách khác - tạo một chương trình Python bình thường, không có daemon, sau đó đưa ra bên ngoài nó bằng cách sử dụng giám sát . Điều này có thể tiết kiệm rất nhiều đau đầu, và * nix- và ngôn ngữ di động.


1
Tôi nghĩ rằng đây là cách tốt nhất. Đặc biệt nếu bạn muốn chạy một số trình nền trên một hệ điều hành. Đừng viết mã, sử dụng lại.
guettli

Nó đơn giản hóa rất nhiều vấn đề. Tôi đã viết daemon thật - chúng không dễ.
Chris Johnson

1
Câu trả lời hay nhất được ẩn ở đây :)
kawing-chiu

1
Đây là vàng. Sau nhiều giờ cố gắng chạy qua python-daemon, đây là giải pháp vượt trội phù hợp với tôi. Tài liệu và ví dụ tuyệt vời đã làm cho daemon của tôi hoạt động trong vài phút.
Nikhil Sahu

17

Có lẽ không phải là một câu trả lời trực tiếp cho câu hỏi, nhưng systemd có thể được sử dụng để chạy ứng dụng của bạn như một daemon. Đây là một ví dụ:

[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

Tôi thích phương pháp này vì rất nhiều công việc được thực hiện cho bạn, và sau đó tập lệnh daemon của bạn hoạt động tương tự với phần còn lại của hệ thống.

-Hoặc bằng cách


Đây là cách thích hợp và lành mạnh. 1) Cần được lưu vào /etc/systemd/system/control.service 2) được quản lý sudosystemctl start control.service
jimper

7

YapDi là một mô-đun python tương đối mới xuất hiện trên Hacker News. Trông khá hữu ích, có thể được sử dụng để chuyển đổi tập lệnh python sang chế độ daemon từ bên trong tập lệnh.


6

Vì python-daemon chưa hỗ trợ python 3.x, và từ những gì có thể đọc được trong danh sách gửi thư, nên có thể sẽ không bao giờ, tôi đã viết một bản triển khai mới của PEP 3143: pep3143daemon

pep3143daemon nên hỗ trợ ít nhất python 2.6, 2.7 và 3.x

Nó cũng chứa một lớp PidFile.

Thư viện chỉ phụ thuộc vào thư viện tiêu chuẩn và trên sáu mô-đun.

Nó có thể được sử dụng như một sự thay thế cho python-daemon.

Đây là tài liệu .


6

Hàm này sẽ biến đổi một ứng dụng thành một 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())

5

Tôi sợ mô-đun daemon được đề cập bởi @Dustin không hoạt động với tôi. Thay vào đó tôi đã cài đặt python-daemon và sử dụng đoạn mã sau:

# 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.

Chạy rất dễ

> python myDaemon.py

chỉ để hoàn thiện ở đây là nội dung thư mục samplemodule

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

Nội dung của moduleclass.py có thể là

class moduleclass():
    ...

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

2

Một điều nữa cần suy nghĩ khi daemonizing trong python:

Nếu bạn đang sử dụng ghi nhật ký python và bạn muốn tiếp tục sử dụng nó sau khi daemonizing, hãy đảm bảo gọi close()trình xử lý (đặc biệt là trình xử lý tệp).

Nếu bạn không làm điều này, trình xử lý vẫn có thể nghĩ rằng nó có các tệp đang mở và các thông điệp của bạn sẽ biến mất - nói cách khác là đảm bảo rằng trình ghi nhật ký biết các tệp của nó đã bị đóng!

Điều này giả định khi bạn daemonise bạn đang đóng TẤT CẢ các mô tả tệp đang mở một cách bừa bãi - thay vào đó bạn có thể thử đóng tất cả trừ các tệp nhật ký (nhưng thường đơn giản hơn để đóng tất cả các tệp bạn muốn).


Bạn có nghĩ việc mở trình xử lý ghi nhật ký mới tốt hơn là chuyển trình xử lý ghi nhật ký đến trình nền bằng cách sử dụng tùy chọn files_preserve của DaemonContext không?
HeyWatch This

Bạn chỉ đóng logger, bạn không tạo một cái mới (nó sẽ chỉ mở lại khi cần). Nhưng mặc dù thực sự dễ dàng để làm điều đó, nhưng có lẽ tốt hơn là sử dụng DaemonContext vì nó có thể thực hiện một số điều thông minh khác (giả sử việc bảo quản vẫn cho phép tạo da đúng cách).
Matthew Wilcoxson

2

Mặc dù bạn có thể thích giải pháp Python thuần túy được cung cấp bởi mô-đun python-daemon, nhưng có một daemon(3)chức năng trong libc- ít nhất là trên BSDLinux - sẽ thực hiện đúng.

Gọi nó từ python thật dễ dàng:

import ctypes

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

Điều duy nhất còn lại phải làm là tạo (và khóa) tệp PID. Nhưng bạn có thể tự xử lý ...


1

Tôi đã sửa đổi một vài dòng trong mẫu mã của Sander Marechal (được đề cập bởi @JeffBauer trong câu trả lời được chấp nhận ) để thêm một quit()phương thức được thực thi trước khi daemon bị dừng. Điều này đôi khi rất hữu ích.

Nó đây rồi

Lưu ý: Tôi không sử dụng mô-đun "python-daemon" vì tài liệu vẫn còn thiếu (xem thêm nhiều câu hỏi SO khác) và khá mơ hồ (làm thế nào để bắt đầu / dừng đúng cách một daemon từ dòng lệnh với mô-đun này?)


-1

Sau một vài năm và nhiều nỗ lực (tôi đã thử tất cả các câu trả lời ở đây, nhưng tất cả chúng đều có nhược điểm nhỏ ở cuối), bây giờ tôi nhận ra rằng có một cách tốt hơn là muốn bắt đầu, dừng lại, khởi động lại một trình nền trực tiếp từ Python : sử dụng các công cụ hệ điều hành thay thế.

Ví dụ, đối với Linux, thay vì làm python myapp startpython myapp stop, tôi làm điều này để khởi động ứng dụng:

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

hoặc screen -dmS myapp python myapp.pyđể bắt đầu và tách nó trong một lệnh .

Sau đó:

screen -r myapp

để gắn vào thiết bị đầu cuối này một lần nữa. Khi ở trong thiết bị đầu cuối, có thể sử dụng CTRL + C để dừng nó.


-2

Cách dễ nhất để tạo daemon với Python là sử dụng khung theo hướng sự kiện Twisted . Nó xử lý tất cả những thứ cần thiết để trình bày cho bạn. Nó sử dụng Mẫu Lò phản ứng để xử lý các yêu cầu đồng thời.


5
Đó là một cái búa quá lớn để sử dụng. Hầu hết mọi người chỉ muốn chạy một đoạn mã Python ngắn mà họ đã viết dưới dạng daemon. python-daemon, như được mô tả ở trên, là câu trả lời chính xác.
Tom Swirly

2
Mặc dù câu trả lời này khá kiêu ngạo, nhưng nó rất hữu ích.
fiatjaf

-28

80% thời gian, khi mọi người nói "daemon", họ chỉ muốn một máy chủ. Vì câu hỏi hoàn toàn không rõ ràng về điểm này, thật khó để nói miền trả lời có thể là gì. Vì một máy chủ là đủ, bắt đầu từ đó. Nếu một "daemon" thực sự thực sự cần thiết (điều này rất hiếm), hãy đọc tiếpnohup như một cách để daemon hóa một máy chủ.

Cho đến khi thực sự cần một trình nền thực sự, chỉ cần viết một máy chủ đơn giản.

Cũng xem xét triển khai tham chiếu WSGI .

Cũng nhìn vào Máy chủ HTTP đơn giản .

"Có bất kỳ điều gì thêm cần được xem xét?" Có. Khoảng một triệu thứ. Giao thức gì? Có bao nhiêu yêu cầu? Bao lâu để phục vụ mỗi yêu cầu? Bao lâu thì họ sẽ đến? Bạn sẽ sử dụng một quy trình chuyên dụng? Chủ đề? Các quy trình con? Viết một daemon là một công việc lớn.


12
Cả hai thư viện đó thậm chí không làm một fork(), chỉ có hai. Họ không có gì để làm với daemonization.
Brandon Rhodes

8
Trên các hệ điều hành Unix, một quy trình của Da daem - giống như các tiếp viên hàng không mà người Hy Lạp gọi là da daonsons - là một quy trình mà bên đứng bên cạnh. Thay vì trực tiếp phục vụ một người dùng thông qua TTY của người dùng đó, một daemon không thuộc về TTY, nhưng có thể trả lời các yêu cầu từ nhiều người dùng trên hệ thống, hoặc - như crondhoặc syslogd- thực hiện các dịch vụ vệ sinh cho toàn bộ hệ thống. Để tạo một quy trình trình nền, ít nhất người ta phải thực hiện gấp đôi - fork()với tất cả các mô tả tệp được đóng, để một người miễn nhiễm với các tín hiệu từ tất cả các thiết bị đầu cuối kiểm soát, bao gồm cả bảng điều khiển hệ thống. Xem câu trả lời của bignose.
Brandon Rhodes

5
@S Lott - Một máy chủ, một máy chủ mô tả những gì một quá trình thực hiện (lắng nghe các yêu cầu đến thay vì bắt đầu các hành động của chính nó); Đây là một trình nền mô tả cách một quá trình chạy (không có cửa sổ hoặc thiết bị đầu cuối kiểm soát). SimpleHTTPServerthực sự là một máy chủ, nhưng một máy chủ không biết cách tự tạo daemon (ví dụ bạn có thể Ctrl-C). nohuplà một tiện ích để tạo ra một quy trình ngây thơ - vì vậy máy chủ không bị hư hỏng của bạn thực sự là cả một trình nền máy chủ, chính xác như bạn yêu cầu. Câu hỏi về Stack Overflow này về cơ bản là hỏi: Làm sao tôi có thể thực hiện nohuptrong Python?
Brandon Rhodes

5
Có, nhưng sự hiểu biết của tôi về câu hỏi của OP là anh ấy muốn thực hiện các thao tác từ bên trong chương trình python của mình và không sử dụng cái gì khác.
Noufal Ibrahim

4
@S Lott - Bạn không cần phải ấn tượng! Tác giả của mọi câu trả lời khác đều biết ý nghĩa của da da, vì vậy khả năng diễn giải câu hỏi này của tôi hầu như không phải là duy nhất. :) Và bạn lấy ý tưởng ở đâu mà tôi muốn tác giả phát minh lại một bánh xe? Tôi nghĩ nohuplà một công cụ tốt và tôi sẽ xóa phiếu -1 của mình nếu bạn chỉ cần chuyển ý tưởng hữu ích đó vào câu trả lời thực tế của bạn. Trên thực tế, nếu bạn đề cập supervisordvà làm thế nào nó cũng sẽ cứu tác giả khỏi phải ghi nhật ký, tập lệnh bắt đầu và khởi động lại điều chỉnh, thì tôi thậm chí sẽ +1 bạn. :)
Brandon Rhodes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.