Мне нужен демон, который может превратить произвольную, общую script или команду в daemon.
Есть два распространенных случая, с которыми мне хотелось бы иметь дело:
У меня есть script, который должен работать вечно. Если он когда-либо умирает (или при перезагрузке), перезагрузите его. Не допускайте одновременного запуска двух копий (проверьте, не скопирована ли копия в этом случае).
У меня есть простая команда script или командной строки, которую я хотел бы продолжать выполнять повторно навсегда (с небольшой паузой между прогонами). Опять же, не позволяйте запускать сразу две копии script.
Конечно, тривиально писать цикл while (true) вокруг script в случае 2, а затем применить решение для случая 1, но более общее решение будет просто решать случай 2 непосредственно, поскольку это относится к script в случае 1 (вы можете просто захотеть более короткую или не сделать паузу, если script не собирается умирать (конечно, если script действительно никогда не умирает, то пауза на самом деле не имеет значения)).
Обратите внимание, что решение не должно включать, скажем, добавления кода блокировки файлов или записи PID в существующие скрипты.
В частности, мне нужна программа "daemonize", которую я могу запустить, например
% daemonize myscript arg1 arg2
или, например,
% daemonize 'echo `date` >> /tmp/times.txt'
который сохранит растущий список дат, добавленных к times.txt. (Обратите внимание, что если аргумент для daemonize - это script, который выполняется вечно, как в случае 1 выше, тогда daemonize все равно пойдет правильно, перезапустив его, когда это необходимо.) Тогда я мог бы добавить команду, как указано выше в моем .login и/или cron это ежечасно или в мельчайших подробностях (в зависимости от того, как я волновался, что я о нем умираю неожиданно).
NB: daemonize script необходимо будет запомнить строку команды, которую он демонизирует, так что, если одна и та же командная строка снова будет демоннизирована, она не запускает вторую копию.
Кроме того, решение должно идеально работать как на OS X, так и на Linux, но решения для одного или другого приветствуются.
РЕДАКТИРОВАТЬ: Это нормально, если вы должны вызвать его с помощью sudo daemonize myscript myargs
.
(Если я думаю о том, что все это неправильно или есть быстрые и грязные частичные решения, я тоже хотел бы это услышать.)
PS: В случае, если это полезно, здесь аналогичный вопрос, относящийся к python.
И этот ответ на аналогичный вопрос имеет то, что кажется полезной идиомой для быстрой и грязной демонизации произвольного script:
Вы можете демонировать любой исполняемый файл в Unix с помощью nohup и оператора and:
nohup yourScript.sh script args&
Команда nohup позволяет завершить сеанс оболочки, не убивая ваш script, а затем помещает ваш script в фоновый режим, чтобы вы получили приглашение оболочки продолжить сеанс. Единственная незначительная проблема с этим - стандартная, и стандартная ошибка отправляется на. /nohup.out, поэтому, если вы запустите несколько скриптов в этой усадьбе, их выход будет переплетаться. Лучшая команда:
nohup yourScript.sh script args >script.out 2>script.error&
Это приведет к отправке стандартного файла в файл по вашему выбору и стандартной ошибке в другой файл по вашему выбору. Если вы хотите использовать только один файл для стандартной и стандартной ошибки, вы можете это сделать:
nohup yourScript.sh script args >script.out 2>&1 &
2 > & 1 сообщает оболочке перенаправить стандартную ошибку (дескриптор файла 2) в тот же файл, что и стандартный выход (дескриптор файла 1).
Чтобы запустить команду только один раз и перезапустить ее, если она умирает, вы можете использовать этот script:
#!/bin/bash
if [[ $# < 1 ]]; then
echo "Name of pid file not given."
exit
fi
# Get the pid file 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
Первый аргумент - это имя используемого файла pid. Второй аргумент - это команда. И все остальные аргументы являются аргументами команды.
Если вы назовете это script restart.sh, то вы так и будете называть его:
nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 &
Я прошу прощения за длинный ответ (см. комментарии о том, как мой ответ называет спецификацию). Я стараюсь быть всеобъемлющим, поэтому у вас как можно больше ноги.: -)
Если вы можете устанавливать программы (иметь корневой доступ) и готовы выполнять одноразовую работу, чтобы настроить ваш script для выполнения демона (т.е. более активное участие, чем просто указать аргументы командной строки для запуска в командной строке, но только нужно сделать один раз за сервис), у меня есть способ, который более надежный.
Он включает в себя использование daemontools. В остальной части сообщения описано, как настроить службы с помощью daemontools.
/service
. Установщик должен был уже это сделать, но просто проверить или установить вручную. Если вам не нравится это местоположение, вы можете изменить его в своем svscanboot
script, хотя большинство пользователей daemontools используются для использования /service
и будут запутаны, если вы его не используете.init
(т.е. не использует /etc/inittab
), вам нужно будет использовать предварительно установленный inittab
в качестве базы для организации svscanboot
называть init
. Это не сложно, но вам нужно знать, как настроить init
, который использует ваша ОС.
svscanboot
- это script, который вызывает svscan
, который выполняет основную работу по поиску сервисов; он вызывается из init
, поэтому init
организует его перезапуск, если он по какой-либо причине умирает./var/lib/svscan
, но любое новое местоположение будет в порядке.Я обычно использую a script для настройки каталога службы, чтобы сэкономить много ручной повторяющейся работы. например.
sudo mkservice -d /var/lib/svscan/some-service-name -l -u user -L loguser "command line here"
где some-service-name
- это имя, которое вы хотите предоставить своей службе, user
- это пользователь, который будет запускать эту службу как, а loguser
- это пользователь для запуска регистратора как. (Ведение журнала объясняется чуть-чуть.)
fghack
, хотя это происходит в компромиссе: вы больше не можете управляйте программой с помощью svc
.run
script, чтобы убедиться, что он делает то, что вы хотите. Возможно, вам придется разместить вызов sleep
вверху, если вы ожидаете, что ваш сервис выйдет часто./service
, указывая на вашу служебную директорию. (Не ставьте каталоги службы непосредственно в /service
, что затрудняет удаление службы из чата svscan
.)mkservice
); svscan
выполняет посылку сообщений журнала в службу ведения журнала.mkservice
, создаст автоматически вращающиеся, временные файлы журнала в каталоге log/main
. Текущий файл журнала называется current
.tai64nlocal
, перевести метки времени в удобочитаемый для человека формат. (TAI64N - это 64-разрядная атомная метка времени с наносекундным счетчиком.)svstat
, чтобы получить статус службы. Обратите внимание, что служба ведения журнала является независимой и имеет свой собственный статус.svc
. Например, чтобы перезапустить службу, используйте svc -t /service/some-service-name
; -t
означает "отправить SIGTERM
".-h
(SIGHUP
), -a
(SIGALRM
), -1
(SIGUSR1
), -2
(SIGUSR2
) и -k
(SIGKILL
).-d
. Вы также можете запретить автоматический запуск службы при загрузке, создав файл с именем down
в каталоге службы.-u
. Это необязательно, если вы не сбили его ранее (или настроили его не на автозапуск).-x
; обычно используется с -d
для прекращения службы. Это обычный способ разрешить удаление службы, но сначала необходимо отключить службу от /service
, иначе svscan
перезапустит супервизор.
Кроме того, если вы создали свою службу с помощью службы ведения журнала (mkservice -l
), не забудьте также выйти из диспетчера протоколирования (например, svc -dx /var/lib/svscan/some-service-name/log
) перед удалением каталога службы.Плюсы:
init
.Минусы:
svc
, и не может запускать сценарии запуска напрямую (так как тогда они не будут находиться под контролем супервизора).supervise
в вашей таблице процессов.)В балансе, я думаю, daemontools - отличная система для ваших нужд. Я приветствую любые вопросы о том, как его настроить и поддерживать.
supervise
, supervisor, заботится о перезапуске любой службы, которая выходит. Он ждет одну секунду между рестартами; если вам не хватает времени, поместите его в верхнюю часть сценария запуска службы.
supervise
поддерживается svscan
, поэтому, если супервизор умрет, он будет перезапущен. 2b. svscan
поддерживается init
, который автоматически перезапускает svscan
при необходимости. 2с. Если ваш init
умирает по какой-либо причине, вы все равно облажались. :-П
Вы должны посмотреть daemonize. Он позволяет обнаруживать вторую копию (но использует механизм блокировки файлов). Также он работает с различными дистрибутивами UNIX и Linux.
Если вам нужно автоматически запустить приложение в качестве демона, вам необходимо создать соответствующий init- script.
Вы можете использовать следующий шаблон:
#!/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
Я думаю, вы можете попробовать start-stop-daemon(8)
. Просмотрите сценарии в /etc/init.d
в любом дистрибутиве Linux для примеров. Он может найти начатые процессы с помощью командной строки или PID файла, поэтому он соответствует всем вашим требованиям, кроме как сторожевой таймер для вашего script. Но вы всегда можете запустить еще один сторожевой таймер демона script, который при необходимости перезапустит ваш script.
start-stop-daemon
(по состоянию на 10.9).
В качестве альтернативы уже упомянутым daemonize
и daemontools
существует команда daemon пакета libslack.
daemon
вполне конфигурируется и заботится обо всех утомительных демонах, таких как автоматический перезапуск, ведение журнала или обработка pidfile.
Daemontools (http://cr.yp.to/daemontools.html) представляет собой набор довольно жестких утилит, используемых для этого, написанный dj bernstein. Я использовал это с некоторым успехом. Досадная часть об этом заключается в том, что ни один из сценариев не возвращает никаких видимых результатов при их запуске - просто невидимые коды возврата. Но как только он запускает его пуленепробиваемым.
Если вы используете OS X специально, я предлагаю вам взглянуть на то, как работает startd. Он будет автоматически проверяться, чтобы убедиться, что ваш script запущен и перезапустит его, если это необходимо. Он также включает в себя всевозможные функции планирования и т.д. Он должен удовлетворять требованиям 1 и 2.
Что касается обеспечения выполнения только одной копии вашего script, вам нужно использовать файл PID. Обычно я пишу файл в /var/run/.pid, содержащий PID текущего запущенного экземпляра. если файл существует, когда программа запускается, он проверяет, действительно ли PID в файле запущен (программа может быть повреждена или забыта удалить файл PID). Если это так, отмените. Если нет, запустите и перезапишите PID файл.
Сначала получите createDaemon()
из http://code.activestate.com/recipes/278731/
Затем основной код:
import subprocess
import time
createDaemon()
while True:
subprocess.call(" ".join(sys.argv[1:]),shell=True)
time.sleep(10)
Вы можете попробовать immortal Это кросс-платформенный (OS агностик).
Для быстрой проверки macOS:
brew install immortal
Если вы используете FreeBSD из портов или с помощью pkg:
pkg install immortal
Для Linux, загрузив предварительно скомпилированные исполняемые файлы или из источника: https://immortal.run/source/
Вы можете использовать его так:
immortal -l /var/log/date.log date
Или через конфигурационный файл YAML, который дает вам больше параметров, например:
cmd: date
log:
file: /var/log/date.log
age: 86400 # seconds
num: 7 # int
size: 1 # MegaBytes
timestamp: true # will add timesamp to log
Если вы хотите сохранить также стандартный вывод ошибки в отдельном файле, вы можете использовать что-то вроде:
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
Вы также можете попробовать Monit. Монит - это служба, которая контролирует и сообщает о других услугах. Хотя он в основном используется как способ уведомления (по электронной почте и смс) о проблемах времени выполнения, он также может делать то, что большинство других предложений здесь отстаивали. Он может автоматически запускать и останавливать программы, отправлять электронные письма, запускать другие скрипты и вести журнал вывода, который вы можете получить. Кроме того, я нашел его простым в установке и обслуживании, поскольку там есть документация.
Это рабочая версия с примером, который вы можете скопировать в пустой каталог и попробовать (после установки зависимостей CPAN, которые Getopt::Long, File::Spec, File::Pid и IPC::System::Simple - все довольно стандартно и настоятельно рекомендуется для любого хакера: вы можете установить их все сразу с помощью 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);
example.pl:
#!/usr/bin/perl
use strict;
use warnings;
use File::Pid;
File::Pid->new({file => "pidfile"})->write;
print "$0 got arguments: @ARGV\n";
Теперь вы можете вызвать пример выше: ./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3
и файл pidfile
будет создан, и вы увидите вывод:
Pid <random number here> no longer running; restarting ./example.pl 1 2 3
./example.pl got arguments: 1 2 3
Я сделал ряд улучшений в другом ответе.
sleep
)-h
eval
, поэтому вы можете построить любую оболочку script в качестве строки для отправки на этот script в качестве последнего аргумента arg (или завершающих аргументов ) для его демоннизации-lt
вместо <
Вот 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
Использование:
$ 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
Помните, что если вы запустите этот script из разных каталогов, он может использовать разные pidfiles и не обнаруживать какие-либо существующие экземпляры. Поскольку он предназначен для запуска и перезапуска эфемерных команд, предоставляемых через аргумент, нет способа узнать, что-то уже начато, потому что кто должен сказать, является ли это той же самой командой или нет? Чтобы улучшить это принудительное исполнение только запуска одного экземпляра чего-то, требуется решение, специфичное для ситуации.
Кроме того, для того, чтобы он функционировал как надлежащий демон, вы должны использовать (по минимуму) nohup, как упоминает другой ответ. Я не прилагал никаких усилий для обеспечения устойчивости к сигналам, которые может получить процесс.
Еще один момент, который следует принять к сведению, заключается в том, что убить этого script (если он был вызван из еще одного script, который убит или с сигналом), возможно, не удастся убить ребенка, особенно если ребенок еще один script. Я не знаю, почему это так, но похоже, что это связано с тем, как работает eval
, что для меня таинственно. Поэтому может быть разумным заменить эту строку тем, что принимает только одну команду, как в другом ответе.