Как мне написать bash-скрипт для перезапуска процесса, если он умирает?

226

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

# checkqueue.py
while True:
  check_queue()
  do_something()

Как мне написать bash-скрипт, который проверит, работает ли он, а если нет, запустите его. Примерно следующий псевдокод (или, может быть, он должен что-то вроде ps | grep?):

# keepalivescript.sh
if processidfile exists:
  if processid is running:
     exit, all ok

run checkqueue.py
write processid to processidfile

Я позвоню из crontab:

# crontab
*/5 * * * * /path/to/keepalivescript.sh
Том
источник
4
Просто чтобы добавить это на 2017 год. Используйте супервизор. crontab не означает, что нужно делать такую ​​задачу. Скрипт bash ужасен, когда выдает настоящую ошибку. stackoverflow.com/questions/9301494/…
mootmoot
Как насчет использования inittab и respawn вместо других несистемных решений? См. Superuser.com/a/507835/116705
Ларс Нордин

Ответы:

635

Избегайте PID-файлов, cron или чего-либо еще, что пытается оценить процессы, которые не являются их дочерними.

Есть очень веская причина, почему в UNIX вы можете ТОЛЬКО ждать своих детей. Любой метод (ps parsing, pgrep, хранение PID, ...), который пытается обойти проблему, имеет недостатки и имеет зияющие дыры в нем. Просто скажи нет .

Вместо этого вам нужен процесс, который контролирует ваш процесс, чтобы быть его родителем. Что это значит? Это означает, что только процесс, который запускает ваш процесс, может надежно ожидать его завершения. В bash это абсолютно тривиально.

until myserver; do
    echo "Server 'myserver' crashed with exit code $?.  Respawning.." >&2
    sleep 1
done

Приведенный выше фрагмент кода bash выполняется myserverв untilцикле. Первая строка начинается myserverи ждет окончания. Когда он заканчивается, untilпроверяет его статус выхода. Если статус выхода - 0это означает, что он закончился изящно (что означает, что вы попросили его как-то отключиться, и он сделал это успешно). В этом случае мы не хотим перезапускать его (мы просто попросили его выключить!). Если состояние выхода не является 0, untilзапустится тело цикла, которое выдает сообщение об ошибке на STDERR и перезапускает цикл (обратно к строке 1) через 1 секунду .

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

Теперь все, что вам нужно сделать, это запустить скрипт bash (вероятно, асинхронно), и он будет отслеживать myserverи перезапускать его по мере необходимости. Если вы хотите запустить монитор при загрузке (заставляя сервер «выживать», перезагружается), вы можете запланировать его в cron (1) вашего пользователя с помощью @rebootправила. Откройте свои правила cron с помощью crontab:

crontab -e

Затем добавьте правило для запуска скрипта монитора:

@reboot /usr/local/bin/myservermonitor

В качестве альтернативы; посмотрите на inittab (5) и / etc / inittab. Вы можете добавить туда строку, чтобы myserverначать с определенного уровня инициации и автоматически возродиться.


Редактировать.

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

Учти это:

  1. Утилизация ПИД (убивает неправильный процесс):

    • /etc/init.d/foo start: начать foo, написать fooPID для/var/run/foo.pid
    • Через некоторое время fooумирает как-то.
    • Некоторое время спустя: любой случайный процесс, который запускается (назовите его bar), берет случайный PID, представьте, что он использует fooстарый PID.
    • Вы замечаете, fooчто ушел: /etc/init.d/foo/restartчитает /var/run/foo.pid, проверяет, если он все еще жив, находит bar, думает, что это foo, убивает его, начинает новый foo.
  2. PID файлы устарели. Вам нужна слишком сложная (или, я бы сказал, нетривиальная) логика, чтобы проверить, не устарел ли файл PID, и любая ли такая логика снова уязвима для 1..

  3. Что если у вас даже нет прав на запись или вы находитесь в среде только для чтения?

  4. Это бессмысленное чрезмерное усложнение; Посмотрите, насколько простой мой пример выше. Нет необходимости усложнять это вообще.

Смотрите также: PID-файлы все еще имеют недостатки, когда делают это «правильно»?

Кстати; разбирается даже хуже, чем PID-файлы ps! Никогда не делай этого.

  1. psочень непереносимо. В то время как вы найдете его почти в каждой системе UNIX; его аргументы сильно различаются, если вы хотите нестандартный вывод. И стандартный вывод предназначен ТОЛЬКО для потребления человеком, а не для синтаксического анализа!
  2. Разбор psприводит к большому количеству ложных срабатываний. Возьмите ps aux | grep PIDпример, и теперь представьте, что кто-то начинает процесс с номером где-то в качестве аргумента, который совпадает с PID, с которым вы смотрели своего демона! Представьте, что два человека начинают сеанс Х, и вы ищете, чтобы Х убил ваш. Это просто все виды плохого.

Если вы не хотите сами управлять процессом; Есть несколько совершенно хороших систем, которые будут выполнять функции мониторинга ваших процессов. Посмотрите в рунит , например.

lhunath
источник
1
@Chas. Ownes: Я не думаю, что это необходимо. Это просто усложнит реализацию без уважительной причины. Простота всегда важнее; и если он будет часто перезагружаться, сон будет препятствовать его плохому воздействию на ваши системные ресурсы. В любом случае сообщение уже есть.
lhunath
2
@orschiro Когда программа ведет себя, потребление ресурсов не происходит. Если он существует сразу при запуске, непрерывно, потребление ресурсов в спящем режиме 1 по-прежнему совершенно незначительно.
июня
7
Можно поверить, что я просто вижу этот ответ. Спасибо!
getWeberForStackExchange
2
@ TomášZato Вы можете выполнить вышеуказанный цикл, не тестируя код выхода процесса, while true; do myprocess; doneно учтите, что теперь нет способа остановить процесс.
lhunath
2
@ SergeyP.akaazure Единственный способ заставить родителя убить ребенка при выходе в bash - это превратить ребенка в работу и подать сигнал:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath
33

Посмотрите на monit ( http://mmonit.com/monit/ ). Он обрабатывает запуск, остановку и перезапуск вашего скрипта и может выполнять проверки работоспособности и перезапускать при необходимости.

Или сделайте простой скрипт:

while true
do
/your/script
sleep 1
done
Бернд
источник
4
Монит это именно то, что вы ищете.
Сарк
4
«пока 1» не работает. Вам нужно "while [1]" или "while true" или "while:". См. Unix.stackexchange.com/questions/367108/what-does- while-mean
Кертис
8

Самый простой способ сделать это - использовать flock on file. В скрипте Python вы бы сделали

lf = open('/tmp/script.lock','w')
if(fcntl.flock(lf, fcntl.LOCK_EX|fcntl.LOCK_NB) != 0): 
   sys.exit('other instance already running')
lf.write('%d\n'%os.getpid())
lf.flush()

В оболочке вы можете проверить, работает ли он:

if [ `flock -xn /tmp/script.lock -c 'echo 1'` ]; then 
   echo 'it's not running'
   restart.
else
   echo -n 'it's already running with PID '
   cat /tmp/script.lock
fi

Но, конечно, вам не нужно тестировать, потому что, если он уже запущен и вы перезапустите его, он завершится с 'other instance already running'

Когда процесс умирает, все его файловые дескрипторы закрываются и все блокировки автоматически снимаются.

Vartec
источник
это могло бы немного упростить удаление скрипта bash. Что произойдет, если скрипт Python аварийно завершится? файл разблокирован?
Том
1
Блокировка файла снимается, как только приложение останавливается, убивая, естественно или сбой.
Кристиан Виттс
@ Том ... если быть более точным - блокировка больше не активна, как только закрывается дескриптор файла. Если скрипт Python никогда не закрывает дескриптор файла по назначению и гарантирует, что он не закрывается автоматически через сборщик мусора, тогда его закрытие, вероятно, означает, что скрипт вышел / был убит. Это работает даже для перезагрузок и тому подобное.
Чарльз Даффи
1
Есть гораздо лучшие способы использования flock... на самом деле, страница руководства явно демонстрирует как! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"является bash-эквивалентом вашего Python и оставляет блокировку удержанной (поэтому, если вы затем выполняете процесс, блокировка будет удерживаться до завершения этого процесса).
Чарльз Даффи
Я отказался от вас, потому что ваш код неверен. Использование flock- правильный путь, но ваши сценарии неверны. Единственная команда, которую вам нужно установить в crontab:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus
6

Вы должны использовать monit, стандартный инструмент Unix, который может отслеживать различные вещи в системе и реагировать соответствующим образом.

Из документов: http://mmonit.com/monit/documentation/monit.html#pid_testing

проверить процесс checkqueue.py с помощью pidfile /var/run/checkqueue.pid
       если изменен pid, то exec "checkqueue_restart.sh"

Вы также можете настроить monit, чтобы он отправлял вам электронное письмо при перезагрузке.

clofresh
источник
2
Monit - отличный инструмент, но он не является стандартным в формальном смысле, который указывается в POSIX или SUSV.
Чарльз Даффи
5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi
soulmerge
источник
круто, это очень хорошо раскрывает мой псевдокод. два qns: 1) как мне сгенерировать PIDFILE? 2) что такое psgrep? это не на сервере Ubuntu.
Том
ps grep - это небольшое приложение, которое делает то же самое, что и ps ax|grep .... Вы можете просто установить его или написать для этого функцию: function psgrep () {ps ax | grep -v grep | grep -q "$ 1"}
soulmerge
Просто заметил, что я не ответил на ваш первый вопрос.
soulmerge
7
На действительно загруженном сервере PID может быть переработан перед проверкой.
vartec
2

Я не уверен, насколько она переносима между операционными системами, но вы можете проверить, содержит ли ваша система команду «run-one», то есть «man run-one». В частности, этот набор команд включает в себя «run-one-постоянно», что, кажется, именно то, что нужно.

С man-страницы:

Run-One-постоянно КОМАНДА [ARGS]

Примечание: очевидно, что это может быть вызвано из вашего скрипта, но это также устраняет необходимость иметь скрипт вообще.

Дэниел Брэдли
источник
Это дает какое-то преимущество перед принятым ответом?
tripleee
1
Да, я думаю, что предпочтительнее использовать встроенную команду, чем писать сценарий оболочки, который делает то же самое, что необходимо поддерживать как часть системной кодовой базы. Даже если функциональность требуется как часть сценария оболочки, вышеупомянутая команда также может быть использована, поэтому она имеет отношение к вопросу сценариев оболочки.
Даниэль Брэдли,
Это не «встроенный»; если он установлен по умолчанию в каком-либо дистрибутиве, ваш ответ, вероятно, должен указывать дистрибутив (и в идеале включать указатель, где его можно скачать, если ваш не входит в их число).
tripleee
Похоже, это утилита Ubuntu; но это необязательно даже в Ubuntu. manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee
Стоит отметить: утилиты run-one делают именно то, что написано в их названии - вы можете запустить только один экземпляр любой команды, запущенной с run-one-nnnnn. Другие ответы здесь являются более исполняемыми, независимо от их содержания - они вообще не заботятся о содержимом команды.
Дэвид Коэн
1

Я использовал следующий скрипт с большим успехом на многочисленных серверах:

pid=`jps -v | grep $INSTALLATION | awk '{print $1}'`
echo $INSTALLATION found at PID $pid 
while [ -e /proc/$pid ]; do sleep 0.1; done

ноты:

  • Он ищет процесс Java, поэтому я могу использовать JPS, это гораздо более согласованно для всех дистрибутивов, чем PS
  • $INSTALLATION содержит достаточно пути процесса, это совершенно однозначно
  • Используйте режим сна, ожидая, пока процесс умрет, избегайте использования ресурсов :)

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

Кевин Райт
источник
1
grep | awkвсе еще антипаттерн - хочешь awk "/$INSTALLATION/ { print \$1 }"отождествлять бесполезноеgrep в скрипт Awk, который может очень хорошо находить строки по самому регулярному выражению, большое спасибо.
tripleee
0

Я использую это для моего процесса npm

#!/bin/bash
for (( ; ; ))
do
date +"%T"
echo Start Process
cd /toFolder
sudo process
date +"%T"
echo Crash
sleep 1
done
BitDEVil2K16
источник