library - ¿Cómo configuro un daemon con python-daemon?
python daemonize (6)
Soy nuevo en los demonios, así que pido disculpas si esta es una pregunta de novato.
En otras respuestas (por ejemplo, esta pregunta ), las personas sugirieron que el paquete python-daemon era el camino a seguir porque implementa completamente el estándar PEP 3143 .
Desafortunadamente, python-daemon es un poco ligero en la documentación (o más probablemente soy un poco ligero en conocimiento / experiencia ...;)), y creo que probablemente me esté perdiendo algo realmente básico. Esto es lo que estoy haciendo:
Tengo los siguientes
import daemon
logfile = open(''daemon.log'', ''w'')
context = daemon.DaemonContext(stdout = logfile, stderr = logfile)
context.open()
with context:
do_something_1()
do_something_2()
Pregunta: ¿Cómo configuro un daemon con python-daemon, cómo puedo iniciarlo y detenerlo?
Notas al margen:
Básicamente, estoy adivinando cómo o si se debe usar el método .open()
aquí: los documentos no fueron muy claros en este punto. Lo mismo parece suceder ya sea que lo incluya o no.
Entonces, ¿qué hago ahora? Cuando intento ejecutar este archivo, por ejemplo:
python startConsumerDaemons.py
parece que se ejecuta do_something_1()
, pero no el segundo. Y, parece que deja el programa adjunto a la ventana del terminal. IE, stdout no se redirige, y cuando cierro la ventana de la terminal, el proceso se detiene. Entonces, estoy bastante seguro de que estoy haciendo algo mal aquí ... ¿qué debería hacer de manera diferente?
Y, por último, una vez que inicie el daemon, ¿cómo lo detengo / reinicio (por ejemplo, si hago cambios en el código subyacente)?
Como puede ver en la documentación de la declaración ''con'' , esta declaración realiza algo de ''magia'', que está relacionado con nuestro propósito. Específicamente:
La ejecución de la instrucción with con un "elemento" se realiza de la siguiente manera:
La expresión de contexto (la expresión dada en with_item) se evalúa para obtener un administrador de contexto.
El
__exit__()
del administrador de contexto se carga para su uso posterior.Se invoca el método
__enter__()
del administrador de contexto.Si se incluyó un objetivo en la declaración with, se le asigna el valor de retorno de
__enter__()
.La suite está ejecutada.
Se invoca el método
__exit__()
del administrador de contexto. Si una excepción causó la salida del conjunto, su tipo, valor y rastreo se pasan como argumentos a__exit__()
. De lo contrario, se proporcionan tres argumentos Ninguno.
¿Qué significa esto? Si te fijas bien en el PEP en cuestión , que también sirve como documentación de python-daemon (y que de hecho podría mejorar mucho), verás que implementa __enter__()
y __exit__()
:
La clase también implementa el protocolo del administrador de contexto a través de los métodos
__enter__
y__exit__
.
__enter__()
Llame al método open () de la instancia, luego devuelva la instancia.
__exit__(exc_type, exc_value, exc_traceback)
Llame al método close () de la instancia, luego devuelva True si la excepción fue manejada o False si no lo fue.
En otras palabras, no se necesita open (), el ejemplo dado en el PEP (aunque no se explica correctamente) funciona como está. Mientras que la declaración with significa algo, no mantiene ningún bucle, una vez que se alcanza el final de su alcance, llama a exit (), que en python-daemon significa close (). Por lo tanto, debe poner allí un tiempo Verdadero o cualquier bucle infinito que considere.
En cuanto a que tu segundo guión no funciona, no puedo decirte realmente, me sorprende que el primero ya funcione. Si su demonio se está deteniendo, seguro que hay un problema con sus scripts, puede verificar su consumerDaemonLogFile. (como nota al margen, tiene un error tipográfico ''sderr'' -> ''stderr'')
Además, puede ver en el PEP que, si no se especifica, la propiedad del directorio de trabajo se establece de forma predeterminada en ''/''. Esta podría ser la fuente de su problema si está utilizando rutas relativas en sus scripts.
Finalmente, sobre la última pregunta, puedes matar fácilmente a tu demonio encontrando su PID:
ps ax | grep startConsumerDaemons.py
y enviándole un SIGTERM:
kill <pid>
La respuesta proporcionada por gromain proporciona una manera más práctica de iniciarlo y detenerlo, con ''daemon.runner ()'', pero es mucho más complicado de configurar.
El constructor daemon.DaemonContext
acepta un parámetro de daemon.DaemonContext
lockfile
. Utilice una biblioteca de archivos de bloqueo que registrará el PID del proceso, como lockfile.PIDLockFile
.
Luego, el PID del proceso se encuentra simplemente leyendo el contenido del archivo PID nombrado. Utilice ese PID para enviar señales a su demonio en ejecución.
En Linux, puedes detener el Daemon ejecutando:
$ ps -x
y encuentre el PID que corresponde a su demonio y luego simplemente elimine el proceso.
Esto es lo que tengo, que funciona para mí. También tiene un script de inicio sysv. Repo está en GitHub , y también tengo una breve publicación en el blog con enlaces a otras posibles soluciones que encontré.
Solo puede haber un proceso de daemon en ejecución: este es administrado por el archivo de bloqueo PID, como la mayoría de los otros demonios de Linux. Para detenerlo, hacer
kill `cat /var/run/eg_daemon.pid`
Para ver si se está ejecutando:
ps -elf | grep `cat /var/run/eg_daemon.pid`
Usando el submódulo pidfile, el archivo PID se gestiona automáticamente. Cuando se detiene el demonio, se borra el archivo pid. Por favor, vea el repositorio de GitHub vinculado para el script de inicio.
Aquí está el código del demonio de 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)
Para completar, aquí está el script de inicio. Tenga en cuenta que "matar" es en realidad solo un método para enviar una señal POSIX. El contexto python-daemon capturará la señal, finalizará el proceso cerrando limpiamente los descriptores de archivos y eliminará el archivo PID automáticamente. Por lo tanto, realmente es una terminación limpia.
Puedes escribir tu código para capturar SIGUSR1 o algo similar, para hacer una recarga de la configuración del daemon. No hay ninguna ventaja al escribir Python para detener el demonio.
#!/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
Falta una documentación útil para el módulo "daemon de python". Personalmente renuncié a usarlo, y ahora uso con éxito el código daemon de Sander Marechal al que se hace referencia en esta respuesta .
Lo modifiqué ligeramente para poder hacer cosas cuando llamas a python testdaemon.py stop
.
Uso de la muestra:
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()
Un ejemplo completo está disponible aquí .
Debería poder comprender mejor el funcionamiento interno de python-daemon.
Además, el código proporcionado también proporciona un ejemplo de un script de inicio para simplemente iniciar / detener el daemon. Sin embargo, puede iniciarlo / detenerlo simplemente llamando de nuevo a la función original con el argumento detener:
python original_func.py stop