Come faccio a demonizzare uno script arbitrario in unix?

Mi piacerebbe un demone che può trasformare uno script o un comando arbitrario, generico in un demone .

Ci sono due casi comuni che vorrei affrontare:

  1. Ho uno script che dovrebbe essere eseguito per sempre. Se dovesse mai morire (o al riavvio), riavvialo. Non lasciare mai due copie in esecuzione contemporaneamente (rileva se una copia è già in esecuzione e non avviarla in quel caso).

  2. Ho un semplice script o comando da riga di comando che mi piacerebbe continuare ad eseguire ripetutamente per sempre (con una breve pausa tra le esecuzioni). Ancora, non permettere che due copie dello script siano mai in esecuzione contemporaneamente.

Ovviamente è banale scrivere un ciclo “while (true)” attorno allo script nel caso 2 e quindi applicare una soluzione per il caso 1, ma una soluzione più generale risolverà il caso 2 direttamente poiché si applica allo script nel caso 1 come beh (si potrebbe desiderare una pausa più breve o nulla se la sceneggiatura non è destinata a morire (ovviamente se lo script non muore mai realmente , allora la pausa non conta davvero)).

Si noti che la soluzione non dovrebbe comportare, ad esempio, l’aggiunta di codice di blocco file o registrazione PID agli script esistenti.

Più specificamente, mi piacerebbe un programma “demonizzato” su cui possa funzionare

% daemonize myscript arg1 arg2 

o, per esempio,

 % daemonize 'echo `date` >> /tmp/times.txt' 

che manterrebbe un elenco crescente di date aggiunto a times.txt. (Nota che se gli argomenti da demonizzare sono uno script che gira per sempre come nel caso 1 sopra, allora daemonize farà comunque la cosa giusta, riavviandolo quando necessario.) Potrei quindi inserire un comando come sopra nel mio .login e / o cron a ora o minuziosamente (a seconda di quanto ero preoccupato che morisse inaspettatamente).

NB: Lo script daemonize dovrà ricordare la stringa di comando daemonizzare in modo che se la stessa stringa di comando viene di nuovo demonizzata, non avvia una seconda copia.

Inoltre, la soluzione dovrebbe idealmente funzionare sia su OS X che su Linux, ma le soluzioni per l’uno o l’altro sono benvenute.

EDIT: Va bene se devi invocarlo con sudo daemonize myscript myargs .

(Se sto pensando a tutto questo o ci sono soluzioni parziali rapide e sporche, mi piacerebbe sentirlo anche io.)


PS: Se è utile, ecco una domanda simile a Python.

E questa risposta a una domanda simile ha quello che sembra essere un utile idioma per una demonizzazione rapida e sporca di un copione arbitrario:

Puoi demonizzare qualsiasi eseguibile in Unix usando nohup e l’operatore &:

 nohup yourScript.sh script args& 

Il comando nohup ti consente di chiudere la sessione della shell senza che venga abbattuto lo script, mentre il & posiziona il tuo script in background in modo da ottenere un prompt della shell per continuare la sessione. L’unico problema minore è lo standard out e l’errore standard viene inviato a ./nohup.out, quindi se si avviano più script in questa proprietà, il loro output verrà intrecciato. Un comando migliore sarebbe:

 nohup yourScript.sh script args >script.out 2>script.error& 

Ciò invierà lo standard al file di tua scelta e l’errore standard a un altro file di tua scelta. Se vuoi usare solo un file per errori standard e standard puoi farlo noi:

 nohup yourScript.sh script args >script.out 2>&1 & 

Il 2> & 1 dice alla shell di redirect l’errore standard (descrittore di file 2) sullo stesso file come standard out (descrittore di file 1).

Per eseguire un comando solo una volta e riavviarlo se muore, puoi usare questo script:

 #!/bin/bash if [[ $# < 1 ]]; then echo "Name of pid file not given." exit fi # Get the pid file's name. PIDFILE=$1 shift if [[ $# < 1 ]]; then echo "No command given." exit fi echo "Checking pid in file $PIDFILE." #Check to see if process running. PID=$(cat $PIDFILE 2>/dev/null) if [[ $? = 0 ]]; then ps -p $PID >/dev/null 2>&1 if [[ $? = 0 ]]; then echo "Command $1 already running." exit fi fi # Write our pid to file. echo $$ >$PIDFILE # Get command. COMMAND=$1 shift # Run command until we're killed. while true; do $COMMAND "[email protected]" sleep 10 # if command dies immediately, don't go into un-ctrl-c-able loop done 

Il primo argomento è il nome del file pid da utilizzare. Il secondo argomento è il comando. E tutti gli altri argomenti sono gli argomenti del comando.

Se si nomina questo script restart.sh, questo è come lo chiameresti:

 nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 & 

Mi scuso per la lunga risposta (vedi i commenti su come la mia risposta inchioda le specifiche). Sto cercando di essere completo, quindi hai il vantaggio di una gamba il più ansible. 🙂

Se si è in grado di installare programmi (avere accesso root), e si è disposti a eseguire legwork una tantum per impostare lo script per l’esecuzione daemon (ovvero, più complicato della semplice specifica degli argomenti della riga di comando da eseguire sulla riga di comando, ma ho solo bisogno di essere fatto una volta per servizio), ho un modo più robusto.

Implica l’uso di daemontools . Il resto del post descrive come impostare i servizi usando daemontools.

Configurazione iniziale

  1. Seguire le istruzioni in Come installare daemontools . Alcune distribuzioni (es. Debian, Ubuntu) hanno già dei pacchetti per questo, quindi basta usarlo.
  2. Crea una directory chiamata /service . L’installatore dovrebbe aver già fatto questo, ma basta verificare, o se l’installazione manuale. Se non ti piace questa posizione, puoi cambiarla nello script svscanboot , sebbene la maggior parte degli utenti di daemontools siano abituati a usare /service e si confonderanno se non la usi.
  3. Se stai usando Ubuntu o un’altra distribuzione che non usa init standard (cioè, non usa /etc/inittab ), dovrai usare l’ inittab preinstallato come base per organizzare svscanboot da chiamare da init . Non è difficile, ma devi sapere come configurare l’ init che utilizza il tuo sistema operativo. svscanboot è uno script che chiama svscan , che svolge il lavoro principale di ricerca di servizi; è chiamato da init così init provvederà a riavviarlo se muore per qualsiasi motivo.

Impostazione per servizio

  1. Ogni servizio necessita di una directory di servizio , che memorizza le informazioni di manutenzione sul servizio. Puoi anche creare un luogo dove alloggiare queste directory di servizio in modo che siano tutte in un unico posto; di solito uso /var/lib/svscan , ma qualsiasi nuova posizione andrà bene.
  2. Di solito uso uno script per impostare la directory dei servizi, per risparmiare un sacco di lavoro ripetitivo manuale. per esempio,

     sudo mkservice -d /var/lib/svscan/some-service-name -l -u user -L loguser "command line here" 

    dove some-service-name è il nome che si desidera fornire al servizio, l’ user è l’utente che esegue tale servizio come e loguser è l’utente che esegue il logger come. (La registrazione è spiegata in un po ‘.)

  3. Il tuo servizio deve essere eseguito in primo piano . Se gli sfondi del tuo programma per impostazione predefinita, ma ha un’opzione per disabilitarlo, allora fallo. Se gli sfondi del tuo programma non sono in grado di disabilitarlo, leggi su fghack , anche se questo ha un compromesso: non puoi più controllare il programma usando svc .
  4. Modifica lo script di run per assicurarti che stia facendo ciò che vuoi. Potrebbe essere necessario effettuare una chiamata a sleep in alto, se si prevede che il servizio venga chiuso frequentemente.
  5. Quando tutto è impostato correttamente, creare un /service simbolico in /service punta alla directory dei servizi. (Non mettere directory di servizio direttamente all’interno di /service , rende più difficile rimuovere il servizio svscan di svscan .)

Registrazione

  1. Il modo di logging dei daemontools è di avere i messaggi del log di scrittura del servizio sullo standard output (o errore standard, se si stanno utilizzando script generati con mkservice ); svscan si occupa di inviare messaggi di log al servizio di registrazione.
  2. Il servizio di registrazione prende i messaggi di registro dallo standard input. Lo script del servizio di registrazione generato da mkservice creerà i file di registro con rotazione automatica e con data e ora nella directory log/main . Il file di registro corrente è chiamato current .
  3. Il servizio di registrazione può essere avviato e arrestato indipendentemente dal servizio principale.
  4. Piping i file di log attraverso tai64nlocal tradurrà i timestamp in un formato leggibile. (TAI64N è un timestamp atomico a 64 bit con un conteggio di nanosecondi.)

Servizi di controllo

  1. Usa svstat per ottenere lo stato di un servizio. Si noti che il servizio di registrazione è indipendente e ha il proprio stato.
  2. Tu controlli il tuo servizio (start, stop, restart, ecc.) Usando svc . Ad esempio, per riavviare il servizio, utilizzare svc -t /service/some-service-name ; -t significa “invia SIGTERM “.
  3. Altri segnali disponibili includono -h ( SIGHUP ), -a ( SIGALRM ), -1 ( SIGUSR1 ), -2 ( SIGUSR2 ) e -k ( SIGKILL ).
  4. Per disconnettere il servizio, usare -d . È inoltre ansible impedire a un servizio di avviarsi automaticamente all’avvio creando un file denominato nella directory del servizio.
  5. Per avviare il servizio, utilizzare -u . Questo non è necessario se non lo hai già scaricato (o configurato per non avviarlo automaticamente).
  6. Per chiedere al supervisore di uscire, usare -x ; di solito usato con -d per terminare il servizio pure. Questo è il solito modo per consentire la rimozione di un servizio, ma prima devi scolbind il servizio da /service , altrimenti svscan riavvierà il supervisore. Inoltre, se hai creato il tuo servizio con un servizio di registrazione ( mkservice -l ), ricorda di uscire anche dal supervisore della registrazione (ad esempio, svc -dx /var/lib/svscan/some-service-name/log ) prima di rimuovere la directory del servizio .

Sommario

Professionisti:

  1. daemontools fornisce un modo a prova di proiettile per creare e gestire i servizi. Lo uso per i miei server e lo consiglio vivamente.
  2. Il suo sistema di registrazione è molto robusto, così come la funzione di riavvio automatico del servizio.
  3. Poiché avvia servizi con uno script di shell che scrivi / tune, puoi personalizzare il tuo servizio come preferisci.
  4. Potenti strumenti di controllo del servizio: è ansible inviare la maggior parte dei segnali a un servizio e portare i servizi su e giù in modo affidabile.
  5. I tuoi servizi sono garantiti da un ambiente di esecuzione pulito: eseguiranno con lo stesso ambiente, i limiti del processo, ecc. Come ciò che fornisce init .

Contro:

  1. Ogni servizio richiede un po ‘di configurazione. Per fortuna, questo deve essere fatto solo una volta per servizio.
  2. I servizi devono essere impostati per l’esecuzione in primo piano. Inoltre, per ottenere i migliori risultati, dovrebbero essere impostati per accedere all’output standard / errore standard, piuttosto che a syslog o altri file.
  3. Curva di apprendimento ripido se sei nuovo nel modo di fare i daemontools. È necessario riavviare i servizi utilizzando svc e non è ansible eseguire direttamente gli script di esecuzione (poiché non sarebbero quindi sotto il controllo del supervisore).
  4. Un sacco di file di pulizia, e un sacco di processi di pulizia. Ogni servizio ha bisogno della propria directory di servizio e ogni servizio utilizza un processo di supervisione per riavviare automaticamente il servizio in caso di decesso. (Se hai molti servizi, vedrai molti processi di supervise nella tua tabella dei processi.)

In equilibrio, penso che daemontools sia un sistema eccellente per le vostre esigenze. Accolgo con favore qualsiasi domanda su come configurarlo e mantenerlo.

Dovresti dare un’occhiata a demone . Permette di rilevare la seconda copia (ma utilizza il meccanismo di blocco dei file). Funziona anche su diverse distribuzioni UNIX e Linux.

Se è necessario avviare automaticamente l’applicazione come daemon, è necessario creare script di init appropriati.

Puoi usare il seguente modello:

 #!/bin/sh # # mydaemon This shell script takes care of starting and stopping # the  # # Source function library . /etc/rc.d/init.d/functions # Do preliminary checks here, if any #### START of preliminary checks ######### ##### END of preliminary checks ####### # Handle manual control parameters like start, stop, status, restart, etc. case "$1" in start) # Start daemons. echo -n $"Starting  daemon: " echo daemon  echo ;; stop) # Stop daemons. echo -n $"Shutting down : " killproc  echo # Do clean-up works here like removing pid files from /var/run, etc. ;; status) status  ;; restart) $0 stop $0 start ;; *) echo $"Usage: $0 {start|stop|status|restart}" exit 1 esac exit 0 

Penso che potresti voler provare start-stop-daemon(8) . Controlla gli script in /etc/init.d in qualsiasi distribuzione Linux per esempi. È in grado di trovare i processi avviati tramite riga di comando o file PID, in modo che corrisponda a tutti i requisiti tranne che come watchdog per il tuo script. Ma puoi sempre avviare un altro script watchdemon daemon che, se necessario, riavvia lo script.

In alternativa al già menzionato daemonize e daemontools , c’è il comando daemon del pacchetto libslack.

daemon è abbastanza configurabile e si occupa di tutte le noiose cose daemon come il riavvio automatico, la registrazione o la gestione di file pid.

Se stai usando OS X in modo specifico, ti suggerisco di dare un’occhiata a come funziona launchd. Verificherà automaticamente che lo script sia in esecuzione e lo riavvii se necessario. Include anche tutti i tipi di funzionalità di pianificazione, ecc. Dovrebbe soddisfare entrambi i requisiti 1 e 2.

Per garantire che sia ansible eseguire solo una copia dello script, è necessario utilizzare un file PID. Generalmente scrivo un file su /var/run/.pid che contiene un PID dell’istanza corrente in esecuzione. se il file esiste quando il programma è in esecuzione, controlla se il PID nel file è effettivamente in esecuzione (il programma potrebbe essersi arrestato in modo anomalo o in altro modo dimenticato per eliminare il file PID). Se lo è, abortire. In caso contrario, avviare l’esecuzione e sovrascrivere il file PID.

Daemontools ( http://cr.yp.to/daemontools.html ) è un insieme di utility piuttosto complesse utilizzate per fare questo, scritto da dj bernstein. Ho usato questo con un certo successo. La parte fastidiosa è che nessuno degli script restituisce risultati visibili quando li si esegue: solo codici di ritorno invisibili. Ma una volta che è in esecuzione è a prova di proiettile.

Per prima cosa createDaemon() da http://code.activestate.com/recipes/278731/

Quindi il codice principale:

 import subprocess import time createDaemon() while True: subprocess.call(" ".join(sys.argv[1:]),shell=True) time.sleep(10) 

Questa è una versione funzionante completa di un esempio che è ansible copiare in una directory vuota e provare (dopo aver installato le dipendenze CPAN, che sono Getopt :: Long , File :: Spec , File :: Pid e IPC :: System: : Semplice : tutto abbastanza standard e altamente raccomandato per qualsiasi hacker: puoi installarli tutti insieme con cpan ... ).


keepAlive.pl:

 #!/usr/bin/perl # Usage: # 1. put this in your crontab, to run every minute: # keepAlive.pl --pidfile= --command=  # 2. put this code somewhere near the beginning of your script, # where $pidfile is the same value as used in the cron job above: # use File::Pid; # File::Pid->new({file => $pidfile})->write; # if you want to stop your program from restarting, you must first disable the # cron job, then manually stop your script. There is no need to clean up the # pidfile; it will be cleaned up automatically when you next call # keepAlive.pl. use strict; use warnings; use Getopt::Long; use File::Spec; use File::Pid; use IPC::System::Simple qw(system); my ($pid_file, $command); GetOptions("pidfile=s" => \$pid_file, "command=s" => \$command) or print "Usage: $0 --pidfile= --command= \n", exit; my @arguments = @ARGV; # check if process is still running my $pid_obj = File::Pid->new({file => $pid_file}); if ($pid_obj->running()) { # process is still running; nothing to do! exit 0; } # no? restart it print "Pid " . $pid_obj->pid . " no longer running; restarting $command @arguments\n"; system($command, @arguments); 

example.pl:

 #!/usr/bin/perl use strict; use warnings; use File::Pid; File::Pid->new({file => "pidfile"})->write; print "$0 got arguments: @ARGV\n"; 

Ora puoi richiamare l’esempio precedente con: ./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3 e il file pidfile verrà creato, e vedrai l’output:

 Pid  no longer running; restarting ./example.pl 1 2 3 ./example.pl got arguments: 1 2 3 

Potresti anche provare Monit . Monit è un servizio che monitora e segnala su altri servizi. Sebbene sia principalmente usato come un modo per notificare (via e-mail e sms) sui problemi di runtime, può anche fare ciò che la maggior parte degli altri suggerimenti qui ha sostenuto. Può auto (re) avviare e arrestare programmi, inviare e-mail, avviare altri script e mantenere un registro di output che è ansible raccogliere. Inoltre, ho trovato che è facile da installare e mantenere poiché c’è una solida documentazione.

Potresti provare a immortalare È un supervisore multipiattaforma * nix (OS agnostico).

Per una rapida prova su macOS:

 brew install immortal 

Nel caso tu stia usando FreeBSD dalle porte o usando pkg:

 pkg install immortal 

Per Linux scaricando i binari precompilati o dalla fonte: https://immortal.run/source/

Puoi usarlo in questo modo:

 immortal -l /var/log/date.log date 

O da un file YAML di configurazione che offre più opzioni, ad esempio:

 cmd: date log: file: /var/log/date.log age: 86400 # seconds num: 7 # int size: 1 # MegaBytes timestamp: true # will add timesamp to log 

Se si desidera mantenere anche l’output di errore standard in un file separato, è ansible utilizzare qualcosa come:

 cmd: date log: file: /var/log/date.log age: 86400 # seconds num: 7 # int size: 1 # MegaBytes stderr: file: /var/log/date-error.log age: 86400 # seconds num: 7 # int size: 1 # MegaBytes timestamp: true # will add timesamp to log 

Ho apportato una serie di miglioramenti all’altra risposta .

  1. stdout fuori da questo script è puramente fatto di stdout proveniente da suo figlio A MENO che si esce a causa del rilevamento che il comando è già in esecuzione
  2. ripulisce dopo il suo pidfile quando viene terminato
  3. periodo di timeout configurabile facoltativo (accetta qualsiasi argomento numerico positivo, invia a sleep )
  4. prompt di utilizzo su -h
  5. esecuzione di comandi arbitrari, piuttosto che esecuzione di un singolo comando. L’ultimo argomento O gli argomenti rimanenti (se più di un ultimo argomento) vengono inviati a eval , quindi puoi build qualsiasi tipo di script di shell come una stringa da inviare a questo script come un ultimo arg (o argomenti di trascinamento) per farlo demonizzare
  6. confronto di argomenti conteggio fatto con -lt invece di <

Ecco la sceneggiatura:

 #!/bin/sh # this script builds a mini-daemon, which isn't a real daemon because it # should die when the owning terminal dies, but what makes it useful is # that it will restart the command given to it when it completes, with a # configurable timeout period elapsing before doing so. if [ "$1" = '-h' ]; then echo "timeout defaults to 1 sec.\nUsage: $(basename "$0") sentinel-pidfile [timeout] command [command arg [more command args...]]" exit fi if [ $# -lt 2 ]; then echo "No command given." exit fi PIDFILE=$1 shift TIMEOUT=1 if [[ $1 =~ ^[0-9]+(\.[0-9]+)?$ ]]; then TIMEOUT=$1 [ $# -lt 2 ] && echo "No command given (timeout was given)." && exit shift fi echo "Checking pid in file ${PIDFILE}." >&2 #Check to see if process running. if [ -f "$PIDFILE" ]; then PID=$(< $PIDFILE) if [ $? = 0 ]; then ps -p $PID >/dev/null 2>&1 if [ $? = 0 ]; then echo "This script is (probably) already running as PID ${PID}." exit fi fi fi # Write our pid to file. echo $$ >$PIDFILE cleanup() { rm $PIDFILE } trap cleanup EXIT # Run command until we're killed. while true; do eval "[email protected]" echo "I am $$ and my child has exited; restart in ${TIMEOUT}s" >&2 sleep $TIMEOUT done 

Uso:

 $ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/' Checking pid in file pidfilefortesting. azzzcd I am 79281 and my child has exited; restart in 0.5s azzzcd I am 79281 and my child has exited; restart in 0.5s azzzcd I am 79281 and my child has exited; restart in 0.5s ^C $ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/' 2>/dev/null azzzcd azzzcd azzzcd ^C 

Fare attenzione che se si esegue questo script da diverse directory, è ansible utilizzare diversi file pid e non rilevare alcuna istanza in esecuzione esistente. Dato che è progettato per eseguire e riavviare comandi effimeri forniti tramite un argomento, non c'è modo di sapere se qualcosa è già stato avviato, perché chi può dire se è lo stesso comando o no? Per migliorare questa applicazione di esecuzione di una singola istanza di qualcosa, è necessaria una soluzione specifica per la situazione.

Inoltre, affinché funzioni come un demone appropriato, è necessario utilizzare (come minimo) nohup come menziona l'altra risposta. Non mi sono sforzato di fornire alcuna resilienza ai segnali che il processo potrebbe ricevere.

Un altro punto da prendere in considerazione è che uccidere questo script (se è stato chiamato da un altro script che viene ucciso o con un segnale) potrebbe non riuscire ad uccidere il bambino, specialmente se il bambino è ancora un altro script. Sono incerto sul perché questo sia, ma sembra essere qualcosa legato al modo in cui eval funziona, il che per me è misterioso. Quindi potrebbe essere prudente sostituire quella linea con qualcosa che accetta solo un singolo comando come nell'altra risposta.