it-swarm.com.ru

Как мне демонизировать произвольный скрипт в unix?

Мне нужен демонизатор, который может превратить произвольный, общий сценарий или команду в daemon .

Есть два общих случая, с которыми я хотел бы иметь дело:

  1. У меня есть скрипт, который должен работать вечно. Если он когда-нибудь умрет (или перезагрузится), перезапустите его. Не допускайте одновременного запуска двух копий (определите, запущена ли уже копия и не запускайте ее в этом случае).

  2. У меня есть простой скрипт или команда командной строки, которую я хотел бы продолжать выполнять несколько раз навсегда (с небольшой паузой между запусками). Опять же, не позволяйте одновременно запускать две копии скрипта.

Конечно, тривиально написать цикл while (true) вокруг сценария в случае 2, а затем применить решение для случая 1, но более общее решение просто решит случай 2 напрямую, поскольку это относится к сценарию в случае 1 как ну (вы можете просто захотеть сделать более короткую паузу или вообще не делать паузу, если скрипт не предназначен для того, чтобы когда-либо умереть (конечно, если скрипт действительно действительно никогда не умрет, тогда пауза на самом деле не имеет значения)).

Обратите внимание, что решение не должно включать, скажем, добавление кода блокировки файла или записи PID в существующие сценарии.

В частности, я хотел бы, чтобы программа "daemonize" могла запускаться как

% daemonize myscript arg1 arg2

или, например, 

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

который будет содержать растущий список дат, добавленный в times.txt. (Обратите внимание, что если аргумент (ы) для daemonize является сценарием, который выполняется вечно, как в случае 1 выше, тогда daemonize все равно будет делать правильные вещи, перезапуская его при необходимости.) Затем я мог бы поместить команду, как указано выше, в мой .login и/или cron это ежечасно или ежеминутно (в зависимости от того, насколько я волновался, что он внезапно умрет).

NB. Сценарий daemonize должен будет запомнить командную строку, которую он демонтирует, чтобы при повторной демонизации той же командной строки он не запускал вторую копию.

Кроме того, решение в идеале должно работать как на OS X, так и на Linux, но решения для одного или другого приветствуются.

Правка: Это хорошо, если вам нужно вызвать его с Sudo daemonize myscript myargs.

(Если я думаю об этом все неправильно или есть быстрые и грязные частичные решения, я бы тоже хотел это услышать.)


PS: В случае, если это полезно, вот похожий вопрос, специфичный для python.

И этот ответ на аналогичный вопрос имеет то, что кажется полезным для быстрой и грязной демонизации произвольного сценария:

90
dreeves

Вы можете демонизировать любой исполняемый файл в Unix, используя Nohup и оператор &:

Nohup yourScript.sh script args&

Команда Nohup позволяет вам завершить сеанс Shell, не убивая ваш скрипт, в то время как & помещает ваш скрипт в фоновый режим, так что вы получаете приглашение Shell для продолжения вашего сеанса. Единственная небольшая проблема с этим - стандартный вывод и стандартная ошибка, и они отправляются на ./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).

Чтобы запустить команду только один раз и перезапустить ее, если она умирает, вы можете использовать этот скрипт:

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

Первый аргумент - это имя файла pid, который нужно использовать. Второй аргумент - это команда. И все остальные аргументы являются аргументами команды.

Если вы назовете этот скрипт restart.sh, вы бы назвали его так:

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

Я прошу прощения за длинный ответ (пожалуйста, смотрите комментарии о том, как мой ответ прибивает спецификацию). Я пытаюсь быть всеобъемлющим, так что вы как можно лучше. :-)

Если вы можете устанавливать программы (иметь доступ с правами root) и готовы сделать единовременную работу по настройке сценария для выполнения демона (т. Е. Более сложным, чем просто указание аргументов командной строки для запуска в командной строке, но только нужно сделать один раз за услугу), у меня есть способ, который является более надежным.

Это включает использование daemontools . В оставшейся части поста описывается, как настроить сервисы с помощью daemontools.

Начальная настройка

  1. Следуйте инструкциям в Как установить daemontools . Некоторые дистрибутивы (например, Debian, Ubuntu) уже имеют пакеты для него, так что просто используйте это.
  2. Создайте каталог с именем /service. Установщик должен был это сделать, но просто проверить или установить вручную. Если вам не нравится это местоположение, вы можете изменить его в своем скрипте svscanboot, хотя большинство пользователей daemontools привыкли к использованию /service и запутаются, если вы его не используете.
  3. Если вы используете Ubuntu или другой дистрибутив, который не использует стандартную init (т.е. не использует /etc/inittab), вам нужно будет использовать предустановленный inittab в качестве основы для организации svscanboot, которая будет вызываться init. Это не сложно, но вам нужно знать, как настроить init, который ваша ОС использует .svscanboot, это скрипт, который вызывает svscan, который выполняет основную работу по поиску сервисов; он вызывается из init, поэтому init организует его перезапуск, если он по какой-либо причине умирает.

Индивидуальная настройка

  1. Каждой службе требуется каталог службы, в которой хранится служебная информация об услуге. Вы также можете создать место для размещения этих сервисных каталогов, чтобы они все были в одном месте; обычно я использую /var/lib/svscan, но с любым новым местоположением все будет в порядке.
  2. Я обычно использую скрипт , чтобы настроить каталог службы, чтобы сохранить много ручной повторяющейся работы. например.,

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

    где some-service-name - это имя, которое вы хотите присвоить своей службе, user - это пользователь, который будет запускать эту службу, а loguser - это пользователь, который будет запускать регистратор как. (Ведение журнала объясняется чуть-чуть.)

  3. Ваш сервис должен работать на переднем плане. Если ваша программа по умолчанию имеет фон, но имеет возможность отключить это, сделайте это. Если фон вашей программы не имеет возможности отключить ее, прочитайте fghack , хотя это может быть компромиссом: вы больше не можете управлять программой с помощью svc.
  4. Отредактируйте скрипт run, чтобы убедиться, что он делает то, что вам нужно. Возможно, вам придется разместить вызов sleep сверху, если вы ожидаете частого выхода из службы.
  5. Когда все настроено правильно, создайте символическую ссылку в /service, указывающую на каталог вашей службы. (Не помещайте каталоги служб непосредственно в /service; это усложняет удаление службы из наблюдения svscan.)

Логирование

  1. Способ ведения журнала daemontools заключается в том, чтобы служба записывала сообщения журнала в стандартный вывод (или в стандартную ошибку, если вы используете сценарии, созданные с помощью mkservice); svscan заботится об отправке сообщений журнала в службу регистрации.
  2. Служба регистрации берет сообщения журнала со стандартного ввода. Сценарий службы ведения журнала, сгенерированный mkservice, создаст автоматически повернутые файлы журнала с метками времени в каталоге log/main. Текущий файл журнала называется current.
  3. Служба ведения журнала может быть запущена и остановлена ​​независимо от основной службы.
  4. Передача файлов журнала через tai64nlocal переведет временные метки в понятный человеку формат. (TAI64N - это 64-разрядная атомная временная метка с наносекундным счетом.)

Контроллерские услуги

  1. Используйте svstat , чтобы получить статус сервиса. Обратите внимание, что служба ведения журнала является независимой и имеет собственный статус.
  2. Вы управляете своим сервисом (запуск, остановка, перезапуск и т.д.), Используя svc . Например, чтобы перезапустить службу, используйте svc -t /service/some-service-name; -t означает «отправить SIGTERM».
  3. Другие доступные сигналы включают -h (SIGHUP), -a (SIGALRM), -1 (SIGUSR1), -2 (SIGUSR2) и -k (SIGKILL).
  4. Чтобы отключить службу, используйте -d. Вы также можете запретить автоматический запуск службы при загрузке, создав файл с именем down в каталоге службы.
  5. Чтобы запустить службу, используйте -u. В этом нет необходимости, если вы не отключили его ранее (или не настроили автоматический запуск).
  6. Чтобы попросить супервизора выйти, используйте -x; обычно используется вместе с -d для прекращения службы. Это обычный способ разрешить удаление службы, но сначала необходимо отсоединить службу от /service, иначе svscan перезапустит супервизор . Кроме того, если вы создали службу с помощью службы ведения журнала (mkservice -l), не забудьте также выйти из супервизора ведения журнала (например, svc -dx /var/lib/svscan/some-service-name/log) перед удалением каталога службы.Резюме.

Плюсы:

  1. система регистрации очень надежна, как и функция автоматического перезапуска службы.
  2. Поскольку он запускает службы с помощью сценария Shell, который вы пишете/настраиваете, вы можете адаптировать свой сервис так, как вам нравится.
  3. Мощные инструменты управления сервисами: вы можете отправлять в сервис практически любой сигнал, а также надежно повышать или понижать сервисы.
  4. Вашим сервисам гарантируется чистая среда выполнения: они будут работать в той же среде, с лимитами процессов и т.д., Которые предоставляет init.
  5. Минусы:.

  1. Службы должны быть настроены для работы на переднем плане. Кроме того, для достижения наилучших результатов они должны быть настроены на запись в стандартный вывод/стандартную ошибку, а не на системный журнал или другие файлы.
  2. Крутая кривая обучения, если вы новичок в способе работы daemontools. Вы должны перезапустить службы, используя svc, и не можете запускать сценарии запуска напрямую (так как они не будут находиться под контролем супервизора).
  3. Множество служебных файлов и множество служебных процессов. Каждой службе нужен свой собственный каталог службы, и каждая служба использует один процесс супервизора для автоматического перезапуска службы, если она умирает. (Если у вас много служб, вы увидите много процессов supervise в вашей таблице процессов.).
  4. В целом, я думаю, daemontools - это отличная система для ваших нужд. Я приветствую любые вопросы о том, как его настроить и поддерживать.

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

Вы должны взглянуть на daemonize . Позволяет обнаружить вторую копию (но использует механизм блокировки файлов). Также он работает на разных дистрибутивах UNIX и Linux.

Если вам нужно автоматически запустить приложение как демон, то вам нужно создать соответствующий init-скрипт.

Вы можете использовать следующий шаблон:

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

Я думаю, что вы можете попробовать start-stop-daemon(8) . Посмотрите сценарии в /etc/init.d в любом дистрибутиве Linux для примеров. Он может найти запущенные процессы по вызванной командной строке или PID-файлу, поэтому он соответствует всем вашим требованиям, за исключением того, что он является сторожевым таймером для вашего скрипта. Но вы всегда можете запустить другой сторожевой скрипт демона, который при необходимости просто перезапускает ваш скрипт.

11
Alex B

В качестве альтернативы уже упомянутым daemonize и daemontools есть команда daemon пакета libslack.

daemon достаточно настраивается и заботится обо всех утомительных вещах демона, таких как автоматический перезапуск, ведение журнала или обработка pidfile.

7
jefz

Если вы используете OS X специально, я советую вам посмотреть, как работает launchd. Он автоматически проверит, что ваш скрипт запущен, и при необходимости перезапустит его. Он также включает в себя все виды функций планирования и т.д. Он должен удовлетворять требованиям 1 и 2. 

Что касается обеспечения работы только одной копии вашего скрипта, вам нужно использовать файл PID. Обычно я пишу файл в /var/run/.pid, который содержит PID текущего запущенного экземпляра. если файл существует при запуске программы, он проверяет, действительно ли запущен PID в файле (возможно, программа потерпела крах или иным образом забыла удалить файл PID). Если это так, отменить. Если нет, запустите и перезапишите файл PID.

5
Kamil Kisiel

Daemontools ( http://cr.yp.to/daemontools.html ) - это набор довольно жестких утилит, используемых для этого, написанный dj bernstein. Я использовал это с некоторым успехом. Раздражает то, что ни один из сценариев не возвращает видимых результатов при запуске - только невидимые коды возврата. Но когда он работает, он пуленепробиваемый.

5
fastmultiplication

Сначала получите 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)
3
Douglas Leeder

Вы также можете попробовать Monit . Monit - это сервис, который отслеживает и сообщает о других сервисах. Хотя он в основном используется как способ уведомления (по электронной почте и смс) о проблемах во время выполнения, он также может делать то, что отстаивало большинство других предложений здесь. Он может автоматически (повторно) запускать и останавливать программы, отправлять электронные письма, запускать другие сценарии и вести журнал вывода, который вы можете получить. Кроме того, я обнаружил, что его легко устанавливать и обслуживать, так как есть надежная документация.

1
Adestin

Это рабочая версия, дополненная примером, который можно скопировать в пустой каталог и опробовать (после установки зависимостей 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
1
Ether

Вы можете попробовать immortal Это кроссплатформенный (независимый от ОС) супервизор * nix. 

Для быстрого знакомства с 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
1
nbari

Я сделал ряд улучшений в другой ответ .

  1. stdout из этого сценария состоит исключительно из stdout, исходящего от его дочернего элемента, ЕСЛИ он не завершается из-за обнаружения, что команда уже выполняется
  2. очищается после pid-файла после завершения
  3. необязательный настраиваемый период ожидания (принимает любой положительный числовой аргумент, отправляет sleep)
  4. подсказка об использовании -h
  5. выполнение произвольной команды, а не выполнение одной команды. Последний аргумент OR оставшихся аргументов (если их более одного) отправляется в eval, поэтому вы можете создать любой сценарий Shell в виде строки для отправки этому сценарию в качестве последнего аргумента (или конечных аргументов) для это демонизировать
  6. сравнение количества аргументов выполняется с помощью -lt вместо <

Вот сценарий: 

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

Использование: 

$ 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

Помните, что если вы запускаете этот скрипт из разных каталогов, он может использовать разные pid-файлы и не обнаруживать существующие запущенные экземпляры. Поскольку он предназначен для запуска и перезапуска эфемерных команд, предоставляемых через аргумент, нет способа узнать, было ли что-то уже запущено, потому что кто скажет, является ли это той же командой или нет? Чтобы улучшить это принудительное выполнение только одного экземпляра чего-либо, требуется решение, специфичное для данной ситуации.

Кроме того, чтобы он функционировал как надлежащий демон, вы должны использовать (как минимум) Nohup, как упоминается в другом ответе. Я не приложил никаких усилий для обеспечения устойчивости сигналов, которые может получить процесс. 

Еще один момент, на который следует обратить внимание, заключается в том, что уничтожение этого сценария (если он был вызван из еще одного сценария, который был убит или с помощью сигнала) может не привести к успешному завершению убийства дочернего элемента, особенно если дочерний элемент является еще одним сценарием , Я не уверен, почему это так, но, похоже, что-то связано с тем, как работает eval, что для меня загадочно. Поэтому может быть целесообразно заменить эту строку чем-то, что принимает только одну команду, как в другом ответе.

0
Steven Lu