Comment démoniser un script arbitraire dans Unix?

Je voudrais un démonizer qui peut transformer un script ou une commande arbitraire, générique en un démon .

Je voudrais traiter de deux cas courants:

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

  2. J’ai un script simple ou une commande en ligne de commande que je souhaite continuer à exécuter de manière répétée pour toujours (avec une courte pause entre les exécutions). Encore une fois, ne laissez jamais deux copies du script s’exécuter simultanément.

Bien sûr, il est sortingvial d’écrire une boucle “while (true)” autour du script dans le cas 2, puis d’appliquer une solution au cas 1, mais une solution plus générale résoudra directement le cas 2 eh bien (vous voudrez peut-être une pause plus courte ou aucune pause si le script n’est pas destiné à mourir) (bien sûr, si le script ne meurt jamais vraiment, la pause n’a pas vraiment d’importance).

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

Plus précisément, je voudrais un programme “daemonize” que je peux utiliser comme

% daemonize myscript arg1 arg2 

ou, par exemple,

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

ce qui garderait une liste croissante de dates ajoutées à times.txt. (Notez que si les arguments à démoniser sont un script qui s’exécute pour toujours comme dans le cas 1 ci-dessus, alors daemonize fera toujours le bon choix en le redémarrant si nécessaire.) Je pourrais alors mettre une commande comme ci-dessus dans mon .login et / ou le faire toutes les heures ou minutieusement (en fonction de la façon dont je m’inquiétais de sa mort inattendue).

NB: Le script daemonize devra se souvenir de la chaîne de commande qu’il est en train de démoniser pour que, si la même chaîne de commande est démonisée à nouveau, elle ne lance pas une deuxième copie.

En outre, la solution devrait idéalement fonctionner à la fois sur OS X et 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 cela est faux ou qu’il existe des solutions partielles rapides et sales, j’adorerais l’entendre aussi.)


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

Et cette réponse à une question similaire a ce qui semble être un idiome utile pour une diabolisation rapide et sale d’un script arbitraire:

Vous pouvez démoniser tout 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 que cela ne tue votre script, tandis que la commande & place votre script en arrière-plan pour que vous obteniez une invite de shell pour continuer votre session. Le seul problème mineur avec ceci est la sortie standard et les erreurs standard sont toutes les deux envoyées à ./nohup.out, donc si vous lancez plusieurs scripts dans ce manoir, leur sortie sera étroitement 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 la sortie standard et l’erreur standard, vous pouvez nous:

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

Le 2> & 1 indique au shell de redirect l’erreur standard (descripteur de fichier 2) vers le même fichier que celui de 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 second argument est la commande. Et tous les autres arguments sont les arguments de la commande.

Si vous nommez ce script restart.sh, vous l’appelez ainsi:

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

Je m’excuse pour la longue réponse (s’il vous plaît voir les commentaires sur la façon dont ma réponse cloue la spécification). J’essaie d’être compréhensif, donc vous avez le moins de problèmes possible. 🙂

Si vous parvenez à installer des programmes (avec un access root), et êtes prêt à faire des démarches ponctuelles pour configurer votre script pour l’exécution du démon (plus complexe que de simplement spécifier les arguments de ligne de commande à exécuter sur la ligne de commande) mais seulement besoin d’être fait une fois par service), j’ai un moyen qui est plus robuste.

Cela implique l’utilisation de daemontools . Le rest 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 dissortingbutions (par exemple, Debian, Ubuntu) ont déjà des paquets pour cela, utilisez-les simplement.
  2. Créez un répertoire appelé /service . L’installateur aurait déjà dû le faire, 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 que vous ne l’utiliserez pas.
  3. Si vous utilisez Ubuntu ou une autre dissortingbution qui n’utilise pas l’ init standard (c.-à-d. N’utilise pas /etc/inittab ), vous devrez utiliser l’ inittab pré-installé comme base pour que svscanboot soit appelé par init . Ce n’est pas difficile, mais vous devez savoir comment configurer l’ init 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 donc init organisera le redémarrage s’il meurt pour une raison quelconque.

Configuration par service

  1. Chaque service nécessite un annuaire de services , qui stocke des informations de maintenance sur le 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. En général, j’utilise /var/lib/svscan , mais tout nouvel emplacement sera /var/lib/svscan .
  2. J’utilise généralement un script pour configurer le répertoire de service, afin d’économiser beaucoup de travail répétitif manuel. 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 souhaitez atsortingbuer à votre service, l’ user est l’utilisateur avec lequel ce service doit être exécuté et loguser est l’utilisateur avec loguser exécuter le journal. (La journalisation est expliquée en peu de temps.)

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

Enregistrement

  1. La méthode de journalisation de daemontools consiste à demander au service d’écrire des messages de journalisation sur la sortie standard (ou sur une erreur standard, si vous utilisez des scripts générés avec mkservice ); svscan s’occupe d’envoyer des messages de journal au service de journalisation.
  2. Le service de journalisation prend les messages de journal de l’entrée standard. Le script du 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 est appelé current .
  3. Le service de journalisation peut être démarré et arrêté indépendamment du service principal.
  4. tai64nlocal les fichiers journaux via tai64nlocal traduira les horodatages dans un format lisible par l’homme. (TAI64N est un horodatage atomique de 64 bits avec un nombre de 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.) en utilisant svc . Par exemple, pour redémarrer votre service, utilisez svc -t /service/some-service-name ; -t signifie “envoyer SIGTERM “.
  3. Les autres signaux disponibles incluent -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é dans le répertoire du service.
  5. Pour démarrer le service, utilisez -u . Ce n’est pas nécessaire à moins que vous ne l’ayez déjà descendu (ou que vous ne le définissiez pas pour démarrer automatiquement).
  6. Pour demander au superviseur de quitter, utilisez -x ; généralement utilisé avec -d pour terminer le service également. C’est la manière habituelle de supprimer un service, mais vous devez d’abord dissocier le service de /service , sinon svscan redémarrera le superviseur. De même, 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. daemontools fournit un moyen à toute épreuve pour créer et gérer des services. Je l’utilise pour mes serveurs et je le recommande fortement.
  2. Son système de journalisation est très robuste, tout comme la fonction de redémarrage automatique du service.
  3. Parce qu’il démarre les services avec un script shell que vous écrivez / accordez, vous pouvez personnaliser votre service comme vous le souhaitez.
  4. Puissants outils de contrôle des services: vous pouvez envoyer la plupart des signaux à un service et assurer la fiabilité et la fiabilité des services.
  5. Un environnement d’exécution propre est garanti à vos services: ils s’exécuteront avec le même environnement, les mêmes limites de processus, etc.

Les inconvénients:

  1. Chaque service nécessite un peu de configuration. Heureusement, cela ne nécessite qu’une seule fois par service.
  2. Les services doivent être configurés pour s’exécuter au premier plan. De plus, pour obtenir de meilleurs résultats, ils doivent être configurés pour se connecter à une sortie standard / erreur standard, plutôt qu’à des fichiers syslog ou autres.
  3. Une courbe d’apprentissage abrupte si vous êtes novice dans la manière de faire les choses. Vous devez redémarrer les services en utilisant svc , et ne pouvez pas exécuter les scripts d’exécution directement (car ils ne seraient alors pas sous le contrôle du superviseur).
  4. Beaucoup de fichiers de maintenance et de nombreux processus de maintenance. Chaque service a besoin de son propre répertoire de service et chaque service utilise un processus de supervision pour redémarrer automatiquement le service s’il meurt. (Si vous avez beaucoup de services, vous verrez beaucoup de processus de supervise dans votre table de processus.)

En équilibre, je pense que daemontools est un excellent système pour vos besoins. Toutes les questions sur la façon de le configurer et de le maintenir sont les bienvenues.

Vous devriez jeter un coup d’oeil au démon . Il permet de détecter une deuxième copie (mais il utilise un mécanisme de locking de fichier). Il fonctionne également sur différentes dissortingbutions UNIX et Linux.

Si vous devez démarrer automatiquement votre application en tant que démon, vous devez créer un script d’initialisation approprié.

Vous pouvez utiliser le modèle suivant:

 #!/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 

Je pense que vous pouvez essayer start-stop-daemon(8) . Découvrez des scripts dans /etc/init.d dans n’importe quelle dissortingbution Linux pour des exemples. Il peut trouver des processus démarrés par invocation de ligne de commande ou par fichier PID, de sorte qu’il corresponde à toutes vos exigences, sauf qu’il s’agit d’un chien de garde pour votre script. Mais vous pouvez toujours démarrer un autre script de surveillance du démon qui redémarre simplement votre script si nécessaire.

Au lieu du daemonize et des daemontools déjà mentionnés, il y a la commande daemon du paquet libslack.

daemon est assez configurable et se soucie de tous les démons fastidieux tels que le redémarrage automatique, la journalisation ou la gestion des fichiers pidfile.

Si vous utilisez spécifiquement OS X, je vous suggère de jeter un oeil à la façon dont launchd fonctionne. Il vérifie automatiquement que votre script est en cours d’exécution et le relance si nécessaire. Il comprend également toutes les fonctionnalités de planification, etc. Il doit satisfaire aux exigences 1 et 2.

Pour vous assurer que seule une copie de votre script peut être exécutée, vous devez utiliser un fichier PID. En général, 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 avoir planté ou oublié de supprimer le fichier PID). Si c’est le cas, avortez. Sinon, commencez à exécuter et écrasez le fichier PID.

Daemontools ( http://cr.yp.to/daemontools.html ) est un ensemble de jolis utilitaires utilisés pour cela, écrits par dj bernstein. Je l’ai utilisé avec un certain succès. La partie ennuyeuse à ce sujet est qu’aucun des scripts ne renvoie de résultats visibles lorsque vous les exécutez – juste des codes de retour invisibles. Mais une fois qu’il fonctionne, il est à l’épreuve des balles.

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

Ensuite, le code principal:

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

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 – tout à fait standard et fortement recommandé pour tout pirate: vous pouvez tous les installer en même temps avec 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 ssortingct; 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 ssortingct; 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 et le fichier pidfile sera créé et vous verrez la sortie:

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

Vous pourriez aussi essayer Monit . Monit est un service qui surveille et rend compte d’autres services. Bien qu’il soit principalement utilisé comme un moyen de notifier (par e-mail et sms) les problèmes d’exécution, il peut également faire ce que la plupart des autres suggestions ont préconisé ici. Il peut automatiquement (re) démarrer et arrêter des programmes, envoyer des e-mails, lancer d’autres scripts et gérer un journal des résultats que vous pouvez récupérer. De plus, je trouve que c’est facile à installer et à entretenir, car la documentation est solide.

Vous pouvez essayer immortel C’est un superviseur multi-plateforme (OS agnostic) nix.

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 binarys précompilés ou à partir du source: https://immortal.run/source/

Vous pouvez soit l’utiliser comme ceci:

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

Ou par un fichier de 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 

J’ai apporté une série d’améliorations à l’ autre réponse .

  1. stdout en dehors de ce script est purement constitué de stdout provenant de son fils UNLESS sauf qu’il se ferme à cause de la détection de l’exécution de la commande
  2. nettoie après son fichier pid une fois terminé
  3. Délai d’expiration configurable facultatif (Accepte tout argument numérique positif, envoie à sleep )
  4. invite d’utilisation sur -h
  5. exécution de commande arbitraire, plutôt que l’exécution d’une seule commande. Le dernier argument ou les autres arguments restants (si plusieurs derniers arguments) sont envoyés à eval , vous pouvez donc construire tout type de script shell sous forme de chaîne à envoyer à ce script en dernier argument (ou en argument final) pour le démoniser
  6. comparaisons des -lt 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 depuis des répertoires différents, il peut utiliser différents fichiers pid et ne pas détecter les instances en cours d'exécution. Comme il est conçu pour exécuter et redémarrer des commandes éphémères fournies par un argument, il n'y a aucun moyen de savoir si quelque chose a déjà été démarré, car qui doit dire si c'est la même commande ou non? Pour améliorer cette mise en application consistant à n'exécuter qu'une seule instance de quelque chose, une solution spécifique à la situation est requirejse.

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

Un autre point à prendre en compte est que la mise à mort de ce script (s'il a été appelé depuis un autre script tué ou avec un signal) peut ne pas réussir à tuer l'enfant, en particulier si l'enfant est encore un autre script. Je ne suis pas sûr de savoir pourquoi, mais cela semble être lié à la façon dont fonctionne eval , ce qui est mystérieux pour moi. 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.