Подтвердить что ты не робот

Как настроить демона с помощью python-daemon?

Я новичок в демонах, так что извиняюсь, если это вопрос новичков.

В нескольких других ответах (например, этот вопрос) люди предложили python-daemon, так как он полностью реализует стандарт PEP 3143.

К сожалению, python-daemon немного освещает документацию (или, скорее всего, я немного озадачен знаниями/опытом...;)), и я думаю, что у меня, вероятно, отсутствует что-то действительно основное. Вот что я делаю:

У меня есть следующее:

import daemon

logfile = open('daemon.log', 'w')

context = daemon.DaemonContext(stdout = logfile, stderr = logfile)

context.open()

with context:
    do_something_1()
    do_something_2()

Вопрос: Как настроить демона с помощью python-daemon, как я могу его запустить и остановить?


Боковые заметки:

Я в основном угадываю, как/здесь следует использовать метод .open() - docs не были ясны в этом вопросе. То же самое происходит, если я включаю это или нет.

Итак, что мне теперь делать? Когда я пытаюсь запустить этот файл, например:

python startConsumerDaemons.py

он работает do_something_1(), но не второй. И, похоже, программа остается в окне терминала. IE, stdout не перенаправляется, и когда я закрываю окно терминала, процесс уничтожается. Итак, я уверен, что я делаю что-то неправильно здесь... что я должен делать по-другому?

И, наконец, как только я запустил демона, как его остановить или перезапустить (например, если я вношу изменения в базовый код)?

4b9b3361

Ответ 1

Вот что я имею, это работает для меня. Он также имеет sysv init script. Репо находится в GitHub, и у меня также есть короткое сообщение в блоге со ссылками на другие возможные решения, которые я нашел.

Работает только один процесс демона: он управляется файлом блокировки PID, как и большинство других демонов Linux. Чтобы остановить его, сделайте

kill `cat /var/run/eg_daemon.pid`

Чтобы узнать, работает ли он:

ps -elf | grep `cat /var/run/eg_daemon.pid`

Используя подмодуль pidfile, файл PID управляется автоматически. Когда демон остановлен, pidfile очищается. См. Связанный репозиторий GitHub для init script.

Здесь приведен код демона Python:

#!/usr/bin/env python3.5
import sys
import os
import time
import argparse
import logging
import daemon
from daemon import pidfile

debug_p = False

def do_something(logf):
    ### This does the "work" of the daemon

    logger = logging.getLogger('eg_daemon')
    logger.setLevel(logging.INFO)

    fh = logging.FileHandler(logf)
    fh.setLevel(logging.INFO)

    formatstr = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(formatstr)

    fh.setFormatter(formatter)

    logger.addHandler(fh)

    while True:
        logger.debug("this is a DEBUG message")
        logger.info("this is an INFO message")
        logger.error("this is an ERROR message")
        time.sleep(5)


def start_daemon(pidf, logf):
    ### This launches the daemon in its context

    ### XXX pidfile is a context
    with daemon.DaemonContext(
        working_directory='/var/lib/eg_daemon',
        umask=0o002,
        pidfile=pidfile.TimeoutPIDLockFile(pidf),
        ) as context:
        do_something(logf)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Example daemon in Python")
    parser.add_argument('-p', '--pid-file', default='/var/run/eg_daemon.pid')
    parser.add_argument('-l', '--log-file', default='/var/log/eg_daemon.log')

    args = parser.parse_args()

    start_daemon(pidf=args.pid_file, logf=args.log_file)

Для полноты, вот init script. Обратите внимание, что "kill" - это просто метод отправки сигнала POSIX - см. Справочную страницу для сигнала (7) для обзора. Контекст python-daemon поймает сигнал, завершит процесс, полностью закрывая дескрипторы файлов, и автоматически удалит файл PID. Таким образом, это действительно чистое завершение.

Вы можете написать свой код, чтобы поймать SIGUSR1 или что-то подобное, чтобы выполнить перезагрузку конфигурации демона. Нет смысла писать Python, останавливать демона.

#!/bin/bash
#
# eg_daemon      Startup script for eg_daemon
#
# chkconfig: - 87 12
# description: eg_daemon is a dummy Python-based daemon
# config: /etc/eg_daemon/eg_daemon.conf
# config: /etc/sysconfig/eg_daemon
# pidfile: /var/run/eg_daemon.pid
#
### BEGIN INIT INFO
# Provides: eg_daemon
# Required-Start: $local_fs
# Required-Stop: $local_fs
# Short-Description: start and stop eg_daemon server
# Description: eg_daemon is a dummy Python-based daemon
### END INIT INFO

# Source function library.
. /etc/rc.d/init.d/functions

if [ -f /etc/sysconfig/eg_daemon ]; then
        . /etc/sysconfig/eg_daemon
fi

eg_daemon=/var/lib/eg_daemon/eg_daemon.py
prog=eg_daemon
pidfile=${PIDFILE-/var/run/eg_daemon.pid}
logfile=${LOGFILE-/var/log/eg_daemon.log}
RETVAL=0

OPTIONS=""

start() {
        echo -n $"Starting $prog: "

        if [[ -f ${pidfile} ]] ; then
            pid=$( cat $pidfile  )
            isrunning=$( ps -elf | grep  $pid | grep $prog | grep -v grep )

            if [[ -n ${isrunning} ]] ; then
                echo $"$prog already running"
                return 0
            fi
        fi
        $eg_daemon -p $pidfile -l $logfile $OPTIONS
        RETVAL=$?
        [ $RETVAL = 0 ] && success || failure
        echo
        return $RETVAL
}

stop() {
    if [[ -f ${pidfile} ]] ; then
        pid=$( cat $pidfile )
        isrunning=$( ps -elf | grep $pid | grep $prog | grep -v grep | awk '{print $4}' )

        if [[ ${isrunning} -eq ${pid} ]] ; then
            echo -n $"Stopping $prog: "
            kill $pid
        else
            echo -n $"Stopping $prog: "
            success
        fi
        RETVAL=$?
    fi
    echo
    return $RETVAL
}

reload() {
    echo -n $"Reloading $prog: "
    echo
}

# See how we were called.
case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  status)
    status -p $pidfile $eg_daemon
    RETVAL=$?
    ;;
  restart)
    stop
    start
    ;;
  force-reload|reload)
    reload
    ;;
  *)
    echo $"Usage: $prog {start|stop|restart|force-reload|reload|status}"
    RETVAL=2
esac

exit $RETVAL

Ответ 2

Полный пример доступен здесь.

Вы должны уметь лучше понять внутреннюю работу python-daemon.

Кроме того, приведенный код также дает пример init script, чтобы просто запустить/остановить демона. Однако вы можете запустить/остановить его просто, снова вызвав исходную функцию с остановкой аргумента:

python original_func.py stop

Ответ 3

Как вы можете видеть в с документацией с инструкциями, этот оператор выполняет некоторые "магии", которые связаны с нашей целью. В частности:

Выполнение оператора with с одним "элементом" продолжается как следующим образом:

  • Контекстное выражение (выражение, указанное в параметре with_item) оценивается для получения диспетчера контекстов.

  • Менеджеры контекста __exit__() загружаются для последующего использования.

  • Он запускает контекстные программы __enter__().

  • Если цель была включена в оператор с, ей присваивается возвращаемое значение __enter__().

  • Выполняется пакет.

  • Вызывается метод управления контекстами __exit__(). Если исключение вызвало выход из класса, его тип, значение и traceback передаются как аргументы __exit__(). В противном случае три аргументы.

Что это значит? Если вы внимательно посмотрите на рассматриваемый PEP, который также служит документацией на python-daemon (и который действительно может быть значительно улучшен) увидим, что он реализует __enter__() и __exit__():

Класс также реализует протокол менеджера контекста через __enter__и __exit__.

__enter__()

Вызвать метод open() экземпляра, затем вернуть экземпляр.

__exit__(exc_type, exc_value, exc_traceback)

Вызвать метод close() экземпляра, затем вернуть True, если обработано исключение, или False, если это не так.

Другими словами, open() не требуется, пример, указанный в PEP (хотя и не объясненный правильно) работает как есть. В то время как оператор with означает что-то, он не удерживает какой-либо цикл, после достижения конца его области он вызывает exit(), который в python-daemon означает close(). Поэтому вам нужно поместить туда True или любой бесконечный цикл, который вы считаете.

В то время как ваш второй script не работает, я не могу сказать вам, я удивлен, что первый уже работает. Если ваш демон останавливается, есть проблемы с вашими сценариями, вы можете проверить свой потребительскийDeemonLogFile. (как примечание, у вас есть опечатка 'sderr' → 'stderr')

Кроме того, вы можете видеть в PEP, что если не указано, свойство рабочего каталога по умолчанию равно '/'. это может быть источником вашей проблемы, если вы используете относительные пути в своих сценариях.

Наконец, о последнем вопросе, вы можете легко убить своего демона, найдя его PID:

ps ax | grep startConsumerDaemons.py

и отправив ему SIGTERM:

kill <pid>

Ответ, предоставленный gromain, обеспечивает более удобный способ запускать и останавливать его с помощью "daemon.runner()", но гораздо сложнее настроить.

Ответ 4

В linux вы можете остановить Daemon, запустив:

$ ps -x

и найдите PID, который соответствует вашему демона, а затем просто запустите процесс.

Ответ 5

Полезная документация по-прежнему отсутствует для модуля "python-daemon". Я лично отказался от его использования, и теперь я успешно использую демонный код Sander Marechal указанный в этом ответе.

Я немного изменил его, чтобы иметь возможность делать что-то, когда вы вызываете python testdaemon.py stop.

Вот код.


Использование образца:

import sys, daemon, time

class testdaemon(daemon.Daemon):
    def run(self):
        self.i = 0
        with open('test1.txt', 'w') as f:
            f.write(str(self.i))
        while True:
            self.i += 1
            time.sleep(1)

    def quit(self):
        with open('test2.txt', 'w') as f:
            f.write(str(self.i))

daemon = testdaemon()

if 'start' == sys.argv[1]: 
    daemon.start()
elif 'stop' == sys.argv[1]: 
    daemon.stop()
elif 'restart' == sys.argv[1]: 
    daemon.restart()

Ответ 6

Конструктор daemon.DaemonContext принимает параметр lockfile. Используйте библиотеку блокировки, которая будет записывать PID процесса, например lockfile.PIDLockFile.

Затем PID процесса обнаруживается просто путем чтения содержимого указанного PID файла. Используйте этот PID для отправки сигналов вашему запущенному демону.