Comment puis-je démoniser un script arbitraire dans unix?
j'aimerais un daemoniseur qui peut transformer un script ou une commande arbitraire et générique en un daemon .
il y a deux cas courants que j'aimerais traiter:
-
j'ai un script qui devrait tourner pour toujours. Si elle meurt jamais (ou redémarrage), le redémarrer. Ne laissez jamais deux copies s'exécuter en même temps (détectez si une copie est déjà en cours d'exécution et ne la lancez pas dans ce cas).
-
j'ai un script simple ou une commande en ligne de commande que je voudrais continuer à exécuter à répétition pour toujours (avec une courte pause entre les passages). Encore une fois, ne permettez pas que deux copies du script soient lancées en même temps.
bien sûr, il est trivial d'écrire une boucle "while (true)" autour du script dans le cas 2 et ensuite appliquer une solution pour le cas 1, mais une solution plus générale sera juste résoudre le cas 2 directement car cela s'applique à la script dans le cas 1 aussi bien (vous pouvez juste vouloir une pause plus courte ou pas si le script n'est pas destiné à mourir un jour (bien sûr si le script vraiment fait jamais mourir alors la pause n'a pas réellement d'importance)).
notez que la solution ne devrait pas impliquer, par exemple, l'ajout d'un code de verrouillage de fichier ou d'un enregistrement PID aux scripts existants.
plus précisément, je voudrais un programme "daemonize" que je peux exécuter comme
% daemonize myscript arg1 arg2
ou, par exemple,
% daemonize 'echo `date` >> /tmp/times.txt'
qui conserverait une liste croissante de dates annexées aux horaires.txt. (Notez que si l'argument(S) à daemonize est un script qui tourne pour toujours comme dans le cas 1 ci-dessus, alors daemonize fera quand même la bonne chose, la redémarrant si nécessaire.), J'ai pu alors mettre une commande comme ci-dessus dans mon .login et / ou cron il Toutes les heures ou minutieusement (en fonction de la façon dont je m'inquiétais de mourir inopinément).
NB: Le daemonize script devra se souvenir de la chaîne de commande qu'il est en train de démonter de sorte que si la même chaîne de commande est à nouveau démonisée, il ne lance pas de 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 très bien si vous devez l'invoquer avec sudo daemonize myscript myargs
.
(si je pense à tout cela mal ou il ya des solutions partielles rapide-et-sale, j'aimerais d'entendre que trop.)
PS: Dans le cas où c'est utile, voici une question similaire, spécifique à python.
Et ce réponse à une question similaire a ce qui semble être un utile idiome pour un rapide et sale de diabolisation de l'arbitraire d'un script:
12 réponses
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 de fermer votre session shell sans qu'elle ne tue votre script, alors que le & place votre script en arrière-plan pour que vous obteniez une invite shell pour continuer votre session. Le seul problème mineur avec cela est standard out et l'erreur standard tous les deux sont envoyés à .- nohup.donc si vous démarrez plusieurs scripts dans ce manoir, leur sortie sera entrelacer. Une meilleure commande serait:
nohup yourScript.sh script args >script.out 2>script.error&
cela enverra standard out au fichier de votre choix et standard error à un fichier différent de votre choix. Si vous voulez utiliser un seul fichier pour standard out et standard error, vous pouvez utiliser ceci:
nohup yourScript.sh script args >script.out 2>&1 &
2>&1 indique au shell pour rediriger l'erreur standard (descripteur de fichier 2) pour le même fichier standard (descripteur de fichier 1).
Pour exécuter une commande une seule fois et redémarrez s'il 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=
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 already running."
exit
fi
fi
# Write our pid to file.
echo $$ >$PIDFILE
# Get command.
COMMAND=
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 c'est comme ça qu'on l'appelle:
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éhensive, pour que tu aies une longueur d'avance. :- )
Si vous êtes en mesure d'installer des programmes ("root"), et sont prêts à faire un temps de travail sur le terrain pour configurer votre script pour le démon de l'exécution (c'est à dire, plus que de simplement en spécifiant 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 une façon plus robuste.
Il s'agit d'utiliser daemontools . Le reste du post décrit comment mettre en place des services à l'aide de daemontools.
installation initiale
- suivre les instructions dans comment installer daemontools . Certaines distributions (par exemple, Debian, Ubuntu) ont déjà des paquets pour cela, utilisez-les simplement.
- créer un répertoire appelé
/service
. L'installateur aurait déjà dû le faire, mais il suffit de vérifier, ou si l'installation est manuelle. Si vous n'aimez pas cet emplacement, vous pouvez le changer dans votre scriptsvscanboot
, bien que la plupart des utilisateurs de daemontools soient habitués à utiliser/service
et seront confus si vous ne l'utilisez pas. - si vous utilisez Ubuntu ou une autre distribution qui n'utilise pas la norme
init
(i.e., n'utilise pas/etc/inittab
), vous devrez utiliser leinittab
préinstallé comme un base pour l'arrangementsvscanboot
à appeler parinit
. Ce n'est pas difficile, mais vous devez savoir comment configurer leinit
que votre système d'exploitation utilise.svscanboot
est un script qui appellesvscan
, qui fait le travail principal de la recherche de services; il est appelé deinit
soinit
s'arrangera pour le redémarrer s'il meurt pour une raison quelconque.
Par-service d'installation de
- chaque service a besoin d'un service directory , qui stocke des informations administratives sur le service. Vous pouvez également faire un emplacement pour héberger ces annuaires de service afin qu'ils soient tous en un seul endroit; habituellement , j'utilise
/var/lib/svscan
, mais tout nouvel emplacement sera parfait. -
j'utilise habituellement un script pour configurer le répertoire de service, pour sauver beaucoup de travail manuel répétitif. par exemple,
sudo mkservice -d /var/lib/svscan/some-service-name -l -u user -L loguser "command line here"
où
some-service-name
est le nom vous voulez donner votre service,user
est à l'utilisateur d'exécuter ce service aussi, etloguser
est à l'utilisateur d'exécuter le journal comme. (L'enregistrement est expliqué en juste un peu.) - votre service doit exécuter au premier plan . Si votre arrière-plan de programme par défaut, mais a une option pour désactiver cela, alors faites le. Si votre arrière-plan de programme sans un moyen de le désactiver, lire sur
fghack
, bien que cela arrive à un compromis: vous ne pouvez plus contrôler le programme en utilisantsvc
. - éditez le script
run
pour vous assurer qu'il fait ce que vous voulez. Vous devrez peut-être placer un appelsleep
en haut, si vous vous attendez à ce que votre service se termine fréquemment. - lorsque tout est réglé correctement, créez un lien symbolique dans
/service
pointant vers votre répertoire de service. (Ne mettez pas les annuaires de service directement dans/service
; it il est donc plus difficile de retirer le service de la montresvscan
.)
Journalisation
- la méthode de journalisation daemontools consiste à faire écrire les messages de journalisation par le service à la sortie standard (ou erreur standard, si vous utilisez des scripts générés par
mkservice
);svscan
s'occupe d'envoyer les messages de journalisation au service de journalisation. - le service de journalisation prend les messages de journal de l'entrée standard. Le le script de service de journalisation généré par
mkservice
va créer des fichiers de log auto-tournés et horodatés dans le répertoirelog/main
. Le fichier journal actuel s'appellecurrent
. - Le service de journalisation peut être démarré et arrêté indépendamment du service.
- Tuyauterie les fichiers journaux à travers
tai64nlocal
sera de traduire les horodateurs dans un format lisible par l'homme. (TAI64N est un timestamp atomique 64 bits avec un compte de nanosecondes.)
services de contrôle
- utilisez
svstat
pour obtenir le statut d'un service. Notez que le service de journalisation est indépendant et a son propre statut. - vous contrôlez votre service (démarrage, arrêt, redémarrage, etc.) en utilisant
svc
. Par exemple, pour redémarrer votre service, utilisezsvc -t /service/some-service-name
;-t
signifie " envoyerSIGTERM
". - autres signaux disponibles:
-h
(SIGHUP
),-a
(SIGALRM
),-1
(SIGUSR1
),-2
(SIGUSR2
), et-k
(SIGKILL
). - pour supprimer 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 service. - pour démarrer le service, utilisez
-u
. Ce n'est pas nécessaire, sauf si vous l'avez déjà descendu (ou configuré pour ne pas démarrer automatiquement). - pour demander au superviseur de quitter, utilisez
-x
; habituellement utilisé avec-d
pour mettre fin au service aussi. C'est la façon habituelle de permettre la suppression d'un service, mais vous devez débloquer le service de/service
d'abord, sinonsvscan
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é
Pour:
- daemontools fournit un moyen à l'épreuve des balles pour créer et gérer des services. Je l'utilise pour mes serveurs, et je le recommande fortement.
- son système de journalisation est très robuste, tout comme le service Auto-restart facility.
- Parce qu'il commence les services avec un script shell que vous écrivez/accordez, vous pouvez adapter votre service comme vous voulez.
- puissants outils de contrôle de service: vous pouvez envoyer la plupart des signaux à un service, et peut élever des services de façon fiable.
- vos services sont garantis un environnement d'exécution propre: ils seront exécutés avec le même environnement, les limites de processus, etc., comme ce que
init
fournit.
Cons:
- Chaque service prend un peu de configuration. Heureusement, cela n'a besoin d'être fait qu'une fois par service.
- Services doit être configuré pour s'exécuter en arrière-plan. En outre, pour de meilleurs résultats, ils devraient être configurés pour se connecter à la sortie standard/erreur standard, plutôt que syslog ou d'autres fichiers.
- courbe D'apprentissage raide si vous êtes nouveau à la façon de faire les choses daemontools. Vous devez redémarrer les services en utilisant
svc
, et ne peut pas exécutez les scripts directement (puisqu'ils ne seraient alors pas sous le contrôle du superviseur). - Beaucoup de ménage fichiers, et beaucoup de ménage processus. Chaque service a besoin de son propre répertoire de services, 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 lots de
supervise
processus dans votre table de processus.)
In l'équilibre, je pense que daemontools est un excellent système pour vos besoins. Je suis ouvert à toute question sur la façon de le mettre en place et de le maintenir.
vous devriez jeter un oeil à daemonize . Il permet de détecter la seconde copie (mais il utilise un mécanisme de verrouillage des fichiers). Il fonctionne également sur différentes distributions UNIX et Linux.
si vous devez démarrer automatiquement votre application en tant que démon, alors 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 "" 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)
"151900920" stop
"151900920" start
;;
*)
echo $"Usage: "151900920" {start|stop|status|restart}"
exit 1
esac
exit 0
je pense que vous pouvez essayer start-stop-daemon(8)
. Consultez les scripts dans /etc/init.d
dans N'importe quelle distribution Linux pour des exemples. Il peut trouver des processus lancés par ligne de commande invoquée ou fichier PID, donc il correspond à toutes vos exigences sauf être un chien de garde pour votre script. Mais vous pouvez toujours lancer un autre script daemon watchdog qui redémarre simplement votre script si nécessaire.
comme alternative à la commande déjà mentionnée daemonize
et daemontools
, il y a la commande démon du paquet libslack.
daemon
est tout à fait configurable et ne se soucie de tous les trucs de démon fastidieux tels que le redémarrage automatique, la journalisation ou la manipulation de pidfile.
si vous utilisez OS X spécifiquement, je vous suggère de jeter un oeil au fonctionnement de launchd. Il vérifiera automatiquement que votre script est en cours d'exécution et le relancera si nécessaire. Il comprend également toutes sortes de fonctionnalités de planification, etc. Elle doit 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 /var/run/.pid qui contient un PID de l'instance courante. si le fichier existe lorsque le programme tourne, il vérifie si le PID dans le fichier est réellement en cours d'exécution (le programme peut avoir crashé ou oublié de supprimer le fichier PID). Si elle l'est, abandonner. Si ce n'est pas le cas, exécutez et écrasez le fichier PID.
Daemontools ( http://cr.yp.to/daemontools.html ) est un ensemble d'Utilités assez hard-core utilisé pour faire cela, écrit par DJ bernstein. J'ai utilisé cela avec un certain succès. L'ennuyeux, c'est que aucun des scripts de retour les résultats visibles lorsque vous les exécutez - juste invisible codes de retour. Mais une fois lancé, il est à l'épreuve des balles.
de la Première à obtenir createDaemon()
à partir de 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)
il S'agit D'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 , fichier::Spec , fichier::: Pid , et IPC:: système:: Simple -- tous assez standard et sont fortement recommandés pour tout hacker: vous pouvez les installer tous 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: "151900920" --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);
example.pl:
#!/usr/bin/perl
use strict;
use warnings;
use File::Pid;
File::Pid->new({file => "pidfile"})->write;
print ""151910920" got arguments: @ARGV\n";
Maintenant, vous pouvez invoquer l'exemple ci-dessus avec: ./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3
et 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
, Vous pouvez aussi essayer de Monit . Monit est un service qui surveille les autres services et en rend compte. Bien qu'il soit principalement utilisé comme un moyen de notifier (par courriel et sms) les problèmes d'exécution, il peut également faire ce que la plupart des autres suggestions ici ont préconisé. Il peut (re)démarrer et arrêter des programmes, envoyer des e-mails, lancer d'Autres scripts, et maintenir un journal de sortie que vous pouvez ramasser. De plus, j'ai trouvé que c'est facile à installer et à entretenir puisqu'il y a des documentation.
, Vous pouvez essayer de immortel C'est un *nix croix-plate-forme (système d'exploitation agnostique) superviseur.
pour un essai rapide sur macOS:
brew install immortal
dans le cas où vous utilisez FreeBSD depuis les ports ou en utilisant pkg:
pkg install immortal
pour Linux en téléchargeant les binaires précompilés ou à partir de la source: https://immortal.exécuter/source/
vous pouvez 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 aussi 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 à la autre réponse .
- stdout hors de ce script est purement constitué de stdout provenant de son enfant à moins qu'il ne sorte en raison de la détection que la commande est déjà en cours d'exécution
- nettoie après son pidfile lorsque terminé
- option configurable délai d'attente (Accepte aucun argument numérique, envoie
sleep
) - indication d'utilisation sur
-h
- exécution de commande arbitraire, plutôt que l'exécution d'une seule commande. Le dernier arg ou args restant (si plus d'un dernier arg) sont envoyés à
eval
, de sorte que vous pouvez construire n'importe quelle sorte de script shell comme une chaîne de caractères pour envoyer à ce script comme dernier arg (ou args arrière) pour qu'il daemonize - argument count comparaison faite 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 [ "" = '-h' ]; then
echo "timeout defaults to 1 sec.\nUsage: $(basename ""151900920"") sentinel-pidfile [timeout] command [command arg [more command args...]]"
exit
fi
if [ $# -lt 2 ]; then
echo "No command given."
exit
fi
PIDFILE=
shift
TIMEOUT=1
if [[ =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
TIMEOUT=
[ $# -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
Utilisation:
$ 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 répertoires différents, il peut utiliser des fichiers pidfiles différents et ne pas détecter d'instances existantes. Puisqu'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à commencé, car qui peut dire si c'est le même commande ou pas? Pour améliorer cette application de n'exécuter qu'une seule instance de quelque chose, une solution spécifique de la situation est nécessaire.
Aussi, pour qu'il fonctionne comme un véritable démon, vous devez utiliser (au minimum) nohup que l'autre réponse mentionne. Je n'ai fait aucun effort pour fournir une résistance aux signaux que le processus pourrait recevoir.
un autre point à noter est que tuer ce script (s'il a été appelé de encore un autre script qui est tué, ou avec un signal) peut ne pas réussir à tuer l'enfant, surtout si l'enfant est encore une autre "1519320920 de script". Je ne sais pas pourquoi, mais il semble que cela soit lié à la façon dont eval
fonctionne, ce qui est mystérieux pour moi. Ainsi, il peut être prudent de remplacer cette ligne avec quelque chose qui n'accepte qu'une seule commande comme dans l'autre réponse.