web-dev-qa-db-fra.com

Comment démoniser un script arbitraire sous unix?

J'aimerais un démoniseur capable de transformer un script ou une commande générique arbitraire en un démon .

Je voudrais traiter deux cas courants:

  1. J'ai un script qui devrait fonctionner pour toujours. S'il meurt (ou au redémarrage), redémarrez-le. Ne laissez jamais deux copies en même temps (détectez si une copie est déjà en cours et ne la lancez pas dans ce cas).

  2. J'ai un script simple ou une commande en ligne de commande que j'aimerais continuer à exécuter de manière répétée pour toujours (avec une courte pause entre les exécutions). Encore une fois, n'autorisez jamais deux copies du script à s'exécuter en même temps.

Bien sûr, il est facile d'écrire une boucle "while (true)" autour du script dans le cas 2, puis d'appliquer une solution pour le cas 1, mais une solution plus générale résoudra simplement le cas 2 directement, car cela s'applique au script dans le cas 1 bien (vous pouvez simplement vouloir une pause plus courte ou aucune pause si le script n’est pas destiné à mourir (bien sûr, si le script est vraiment fait ne meurt jamais, alors la pause n’a pas d’importance)).

Notez que la solution ne devrait pas impliquer, par exemple, l’ajout de code de verrouillage de fichier ou d’enregistrement PID aux scripts existants.

Plus précisément, je voudrais un programme "démoniser" que je peux exécuter comme

% daemonize myscript arg1 arg2

ou, par exemple,

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

qui garderait une liste croissante de dates ajoutées à times.txt. (Notez que si l'argument (s) à démoniguer est un script qui s'exécute indéfiniment, comme dans le cas 1 ci-dessus, démonémiseur agira quand même comme il convient, en le redémarrant si nécessaire.) Je pourrais alors mettre une commande comme ci-dessus dans mon. et/ou cron à l'heure ou minutieusement (selon l'inquiétude que je ressentais à l'idée de mourir inopinément).

NB: Le script de démonisation devra garder à l'esprit la chaîne de commande qu'il démonise afin que, si la même chaîne de commande soit démonisée à nouveau, elle ne lance pas une seconde copie.

En outre, la solution devrait idéalement fonctionner à la fois sous OS X et sous Linux, mais les solutions pour l'un ou l'autre sont les bienvenues.

EDIT: C'est bien si vous devez l'invoquer avec Sudo daemonize myscript myargs.

(Si je pense que tout est faux ou s'il existe des solutions partielles rapides et désagréables, j'aimerais aussi l'entendre.)


PS: Au cas où cela serait utile, voici une question similaire spécifique à python.

Et this _ réponse à une question similaire a ce qui semble être un idiome utile pour une diabolisation rapide d'un script arbitraire:

90
dreeves

Vous pouvez démoniser n'importe quel exécutable sous Unix en utilisant Nohup et l'opérateur &:

Nohup yourScript.sh script args&

La commande Nohup vous permet d’arrêter votre session Shell sans détruire votre script, tandis que le & place votre script en arrière-plan afin que vous obteniez une invite du shell vous permettant de poursuivre votre session. Le seul problème mineur avec ceci est la sortie standard et les erreurs types sont toutes deux envoyées à ./Nohup.out. Par conséquent, si vous démarrez plusieurs scripts de cette manière, leur sortie sera liée. Une meilleure commande serait:

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

Cela enverra la sortie standard au fichier de votre choix et l'erreur standard à un fichier différent de votre choix. Si vous souhaitez utiliser un seul fichier à la fois pour l'erreur standard et l'erreur standard, vous pouvez nous utiliser ceci:

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

2> & 1 indique au shell de rediriger l'erreur standard (descripteur de fichier 2) vers le même fichier que la sortie standard (descripteur de fichier 1).

Pour exécuter une commande une seule fois et la redémarrer si elle meurt, vous pouvez utiliser ce 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 "$@"
    sleep 10 # if command dies immediately, don't go into un-ctrl-c-able loop
done

Le premier argument est le nom du fichier pid à utiliser. Le deuxième argument est la commande. Et tous les autres arguments sont les arguments de la commande.

Si vous appelez ce script restart.sh, voici comment vous l'appelez:

Nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 &
86
Robert Menteer

Je m'excuse pour la réponse longue (veuillez voir les commentaires sur la façon dont ma réponse définit la spécification). J'essaie d'être exhaustif pour que vous ayez le maximum de longueur. :-)

Si vous êtes en mesure d'installer des programmes (accès root) et que vous êtes prêt à faire un travail ponctuel pour configurer votre script pour l'exécution d'un démon (c'est-à-dire plus complexe que la simple spécification des arguments de ligne de commande à exécuter sur la ligne de commande, mais n’ayant besoin que d’être fait une fois par service), j’ai une méthode plus robuste.

Cela implique d'utiliser daemontools . Le reste de l'article décrit comment configurer des services à l'aide de daemontools.

La configuration initiale

  1. Suivez les instructions dans Comment installer daemontools . Certaines distributions (par exemple, Debian, Ubuntu) ont déjà des paquets pour cela, donc utilisez-le.
  2. Créez un répertoire appelé /service. Le programme d'installation devrait déjà l'avoir fait, mais il suffit de vérifier ou d'installer manuellement. Si vous n'aimez pas cet emplacement, vous pouvez le changer dans votre script svscanboot, bien que la plupart des utilisateurs de daemontools soient habitués à utiliser /service et vous laisseront perplexe si vous ne l'utilisez pas.
  3. Si vous utilisez Ubuntu ou une autre distribution n'utilisant pas la norme init (c'est-à-dire n'utilise pas /etc/inittab), vous devrez utiliser inittab préinstallé comme base pour organiser l'appel de svscanboot par init. Ce n'est pas difficile, mais vous devez savoir comment configurer le init utilisé par votre système d'exploitation .svscanboot est un script qui appelle svscan, qui effectue le travail principal de recherche de services. il est appelé depuis init afin que init puisse le redémarrer s'il meurt pour une raison quelconque.

Configuration par service

  1. Chaque service nécessite un répertoire service, qui stocke les informations de maintenance relatives au service. Vous pouvez également créer un emplacement pour héberger ces répertoires de services afin qu'ils soient tous au même endroit. J'utilise généralement /var/lib/svscan, mais tout nouvel emplacement conviendra.
  2. J'utilise généralement un script pour configurer le répertoire de service, afin de sauvegarder de nombreuses tâches manuelles répétitives. par exemple.,

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

    some-service-name est le nom que vous voulez donner à votre service, user est l'utilisateur sur lequel exécuter ce service, et loguser est l'utilisateur sur lequel exécuter le consignateur. (La journalisation est expliquée dans un petit peu.)

  3. Votre service doit exécuter au premier plan. Si votre programme est en arrière-plan par défaut, mais dispose d'une option pour le désactiver, faites-le. Si votre programme n'a pas le moyen de le désactiver, lisez la question sur fghack , bien que cela ne soit qu'un compromis: vous ne pouvez plus contrôler le programme à l'aide de svc.
  4. Editez le script run pour vous assurer qu’il fait ce que vous voulez. Vous devrez peut-être placer un appel sleep en haut si vous prévoyez que votre service se termine fréquemment.
  5. Lorsque tout est configuré correctement, créez un lien symbolique dans /service pointant vers votre répertoire de service. (Ne placez pas les répertoires de service directement dans /service; cela compliquerait la suppression du service de la surveillance de svscan.)

Enregistrement

  1. La méthode de journalisation daemontools consiste à ce que le service écrive les messages du journal sur la sortie standard (ou sur l'erreur standard, si vous utilisez des scripts générés avec mkservice); svscan se charge d’envoyer des messages de journalisation au service de journalisation.
  2. Le service de journalisation prend les messages du journal à partir de l'entrée standard. Le script de service de journalisation généré par mkservice créera des fichiers journaux horodatés à rotation automatique dans le répertoire log/main. Le fichier journal actuel s'appelle current.
  3. Le service de journalisation peut être démarré et arrêté indépendamment du service principal.
  4. Si vous transférez les fichiers journaux via tai64nlocal , les horodatages seront traduits dans un format lisible par l'homme. (TAI64N est un horodatage atomique de 64 bits avec un compte en nanosecondes.)

Services de contrôle

  1. Utilisez svstat pour obtenir le statut d'un service. Notez que le service de journalisation est indépendant et possède son propre statut.
  2. Vous contrôlez votre service (démarrage, arrêt, redémarrage, etc.) avec svc . Par exemple, pour redémarrer votre service, utilisez svc -t /service/some-service-name; -t signifie "envoyer SIGTERM".
  3. Les autres signaux disponibles sont -h (SIGHUP), -a (SIGALRM), -1 (SIGUSR1), -2 (SIGUSR2) et -k (SIGKILL).
  4. Pour arrêter le service, utilisez -d. Vous pouvez également empêcher un service de démarrer automatiquement au démarrage en créant un fichier nommé down dans le répertoire du service.
  5. Pour démarrer le service, utilisez -u. Ce n'est pas nécessaire, sauf si vous l'avez déjà réduit (ou configuré pour ne pas démarrer automatiquement).
  6. Pour demander au superviseur de quitter, utilisez -x; généralement utilisé avec -d pour mettre également fin au service. C'est le moyen habituel d'autoriser la suppression d'un service, mais vous devez d'abord dissocier le service de /service, sinon svscan redémarrera le superviseur . De plus, si vous avez créé votre service avec un service de journalisation (mkservice -l), n'oubliez pas de quitter également le superviseur de journalisation (par exemple, svc -dx /var/lib/svscan/some-service-name/log) avant de supprimer le répertoire de service.Résumé.

Avantages:

  1. son système de journalisation est très robuste, tout comme la fonction de redémarrage automatique du service.
  2. Comme il démarre les services avec un script Shell que vous écrivez/ajustez, vous pouvez personnaliser votre service à votre guise.
  3. De puissants outils de contrôle des services: vous pouvez envoyer la plupart des signaux à un service et les mettre en service de manière fiable.
  4. Vos services sont garantis d’un environnement d’exécution propre: ils fonctionneront dans le même environnement, les mêmes limites de processus, etc. que ce que init fournit.
  5. Les inconvénients:.

  1. Les services doivent être configurés pour fonctionner au premier plan. En outre, pour obtenir de meilleurs résultats, ils doivent être configurés pour se connecter à la sortie standard/erreur standard, plutôt qu’à syslog ou à d’autres fichiers.
  2. Courbe d'apprentissage abrupte si vous êtes novice dans la façon de faire de daemontools. Vous devez redémarrer les services à l'aide de svc et ne pouvez pas exécuter directement les scripts d'exécution (car ils ne seraient alors pas sous le contrôle du superviseur).
  3. Beaucoup de dossiers de ménage, et beaucoup de processus de ménage. Chaque service a besoin de son propre répertoire de services et chaque service utilise un processus de superviseur pour redémarrer automatiquement le service s'il meurt. (Si vous avez plusieurs services, vous verrez beaucoup de processus supervise dans votre table de processus.).
  4. En équilibre, je pense que daemontools est un excellent système pour vos besoins. Toutes les questions sur son installation et son entretien sont les bienvenues.

In balance, I think daemontools is an excellent system for your needs. I welcome any questions about how to set it up and maintain it.

31
Chris Jester-Young

Vous devriez jeter un oeil à daemonize . Il permet de détecter une deuxième copie (mais il utilise un mécanisme de verrouillage de fichier). En outre, cela fonctionne sur différentes distributions UNIX et Linux.

Si vous devez démarrer automatiquement votre application en tant que démon, vous devez créer le script init approprié.

Vous pouvez utiliser le modèle suivant:

#!/bin/sh
#
# mydaemon     This Shell script takes care of starting and stopping
#               the <mydaemon>
#

# 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 <mydaemon> daemon: "
    echo
    daemon <mydaemon>
    echo
    ;;

  stop)
    # Stop daemons.
    echo -n $"Shutting down <mydaemon>: "
    killproc <mydaemon>
    echo

    # Do clean-up works here like removing pid files from /var/run, etc.
    ;;
  status)
    status <mydaemon>

    ;;
  restart)
    $0 stop
    $0 start
    ;;

  *)
    echo $"Usage: $0 {start|stop|status|restart}"
    exit 1
esac

exit 0
12
uthark

Je pense que vous voudrez peut-être essayer start-stop-daemon(8) . Consultez les scripts dans /etc/init.d dans n’importe quelle distribution Linux pour obtenir des exemples. Il peut rechercher les processus démarrés à l'aide de la ligne de commande appelée ou du fichier PID. Ainsi, il correspond à toutes vos exigences, à l'exception du rôle de chien de garde de votre script. Mais vous pouvez toujours démarrer un autre script de surveillance de démon qui redémarre simplement votre script si nécessaire.

11
Alex B

Comme alternative aux daemonize et daemontools déjà mentionnés, il y a la commande daemon du paquet libslack.

daemon est tout à fait configurable et se soucie de tous les trucs fastidieux du démon tels que le redémarrage automatique, la journalisation ou la gestion du fichier pid.

7
jefz

Si vous utilisez OS X en particulier, je vous suggère de regarder le fonctionnement de launchd. Il vérifiera automatiquement que votre script est en cours d'exécution et le relancera si nécessaire. Il inclut également toutes sortes de fonctions de planification, etc. Il devrait satisfaire à la fois aux exigences 1 et 2. 

Pour vous assurer qu’une seule copie de votre script peut être exécutée, vous devez utiliser un fichier PID. Généralement, j'écris un fichier dans /var/run/.pid qui contient un PID de l'instance en cours d'exécution. si le fichier existe lors de l'exécution du programme, il vérifie si le PID du fichier est en cours d'exécution (le programme peut s'être bloqué ou avoir oublié de supprimer le fichier PID). Si c'est le cas, abandonnez. Sinon, démarrez et écrasez le fichier PID.

5
Kamil Kisiel

Daemontools ( http://cr.yp.to/daemontools.html ) est un ensemble d’utilitaires assez difficiles à utiliser, écrits par dj bernstein. J'ai utilisé cela avec un certain succès. Ce qui est gênant, c’est qu’aucun des scripts ne renvoie de résultat visible lorsque vous les exécutez, c’est juste des codes de retour invisibles. Mais une fois que cela fonctionne, il est à l'épreuve des balles.

5
fastmultiplication

Commencez par obtenir createDaemon() sur http://code.activestate.com/recipes/278731/

Puis le code principal:

import subprocess
import time

createDaemon()

while True:
    subprocess.call(" ".join(sys.argv[1:]),Shell=True)
    time.sleep(10)
3
Douglas Leeder

Vous pouvez aussi essayer Monit . Monit est un service qui surveille et fait rapport sur d'autres services. Bien qu’il soit principalement utilisé comme moyen de notifier (par courrier électronique et par sms) des problèmes d’exécution, il peut également faire l’objet de la plupart des suggestions présentées ici. Il peut automatiquement (re) démarrer et arrêter des programmes, envoyer des courriels, lancer d'autres scripts et gérer un journal de sortie que vous pouvez récupérer. De plus, j'ai trouvé qu'il était facile à installer et à entretenir car il existe une documentation solide.

1
Adestin

Ceci est une version de travail complète avec un exemple que vous pouvez copier dans un répertoire vide et essayer (après avoir installé les dépendances CPAN, qui sont Getopt :: Long , File :: Spec , File :: Pid , et IPC :: System :: Simple - tous assez standards et sont fortement recommandés pour tout pirate informatique: vous pouvez tous les installer en même temps avec cpan <modulename> <modulename> ...).


keepAlive.pl:

#!/usr/bin/Perl

# Usage:
# 1. put this in your crontab, to run every minute:
#     keepAlive.pl --pidfile=<pidfile> --command=<executable> <arguments>
# 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=<pidfile> --command=<executable> <arguments>\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);

exemple.pl:

#!/usr/bin/Perl

use strict;
use warnings;

use File::Pid;
File::Pid->new({file => "pidfile"})->write;

print "$0 got arguments: @ARGV\n";

Vous pouvez maintenant appeler l'exemple ci-dessus avec: ./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3. Le fichier pidfile sera créé et vous verrez le résultat:

Pid <random number here> no longer running; restarting ./example.pl 1 2 3
./example.pl got arguments: 1 2 3
1
Ether

Vous pouvez essayer immortal . Il s’agit d’un superviseur * nix multi-plateformes (indépendant du système d’exploitation). 

Pour un essai rapide sur macOS:

brew install immortal

Si vous utilisez FreeBSD depuis les ports ou en utilisant pkg:

pkg install immortal

Pour Linux en téléchargeant les fichiers binaires précompilés ou à partir du code source: https://immortal.run/source/

Vous pouvez soit l'utiliser comme ceci:

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

Ou par un fichier configuration YAML qui vous donne plus d'options, par exemple:

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

Si vous souhaitez conserver également la sortie d'erreur standard dans un fichier séparé, vous pouvez utiliser quelque chose comme:

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
1
nbari

J'ai apporté une série d'améliorations à la autre réponse .

  1. la sortie standard de ce script est purement composée de la sortie standard de son enfant, SAUF SI elle se ferme suite à la détection du fait que la commande est déjà en cours d'exécution.
  2. nettoie après son pidfile une fois terminé
  3. délai d'expiration configurable facultatif (accepte tout argument numérique positif, envoie à sleep)
  4. utilisation Invite sur -h
  5. exécution de commande arbitraire, plutôt que l'exécution de commande unique. Le dernier argument OR restant (si plus d'un dernier argument) est envoyé à eval, vous pouvez donc construire n'importe quel type de script Shell sous forme de chaîne à envoyer à ce script en tant que dernier argument (ou arguments de fin) pour démoniser
  6. comparaisons de nombre d'arguments effectuées avec -lt au lieu de <

Voici le script: 

#!/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 "$@"
    echo "I am $$ and my child has exited; restart in ${TIMEOUT}s" >&2
    sleep $TIMEOUT
done

Usage: 

$ 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

Attention, si vous exécutez ce script à partir de différents répertoires, il peut utiliser différents pidfiles et ne détecter aucune instance en cours d'exécution existante. Comme il est conçu pour exécuter et relancer des commandes éphémères fournies par un argument, il n’ya aucun moyen de savoir si quelque chose a déjà été démarré, car qui peut dire s’il s’agit de la même commande ou non? Pour améliorer l'application de la loi en n'exécutant qu'une seule instance, une solution spécifique à la situation est requise.

De plus, pour qu'il fonctionne comme un démon approprié, vous devez utiliser (au minimum) Nohup, comme le dit l'autre réponse. Je n'ai fait aucun effort pour fournir une résilience aux signaux que le processus pourrait recevoir. 

Un autre point à noter est que tuer ce script (s'il a été appelé par un autre script tué ou avec un signal) peut ne pas réussir à tuer l'enfant, surtout si l'enfant est encore un autre script . Je ne sais pas pourquoi, mais cela semble être lié à la façon dont fonctionne eval, qui m’est mystérieux. Il peut donc être prudent de remplacer cette ligne par quelque chose qui n'accepte qu'une seule commande, comme dans l'autre réponse.

0
Steven Lu