Как настроить демона с помощью 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 не перенаправляется, и когда я закрываю окно терминала, процесс уничтожается. Итак, я уверен, что я делаю что-то неправильно здесь... что я должен делать по-другому?
И, наконец, как только я запустил демона, как его остановить или перезапустить (например, если я вношу изменения в базовый код)?
Ответы
Ответ 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 для отправки сигналов вашему запущенному демону.