Як написати сценарій 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
*/5 * * * * /path/to/keepalivescript.sh

4
Просто для додання цього за 2017 рік. Використовуйте нагляд. crontab - це не означає виконувати подібне завдання. Баш-скрипт жахливий, коли видається реальна помилка. stackoverflow.com/questions/9301494 / ...
mootmoot

Як щодо використання inittab та respawn замість інших несистемних рішень? Дивіться superuser.com/a/507835/116705
Ларс Нордін

Відповіді:


635

Уникайте PID-файлів, кронів чи будь-чого іншого, що намагається оцінити процеси, які не є їхніми дітьми.

Є дуже вагома причина, чому в UNIX можна ТОЛЬКО чекати своїх дітей. Будь-який метод (ps-синтаксичний аналіз, pgrep, зберігання PID, ...), який намагається обійти цю проблему і має в собі отвори. Просто скажіть ні .

Натомість вам потрібно, щоб процес, який відстежує ваш процес, був батьківським процесом. Що це означає? Це означає, що лише той процес, який розпочинає ваш процес, може надійно чекати його закінчення. У баші це абсолютно тривіально.

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та перезапускати його за необхідності. Якщо ви хочете запустити монітор під час завантаження (змусивши сервер "пережити" перезавантаження), ви можете запланувати його в кроні користувача (1) за допомогою @rebootправила. Відкрийте свої правила cron за допомогою crontab:

crontab -e

Потім додайте правило для запуску сценарію монітора:

@reboot /usr/local/bin/myservermonitor

Альтернативно; подивіться на inittab (5) та / etc / inittab. Ви можете додати туди рядок, щоб він myserverпочався на певному рівні init і автоматично відновився.


Редагувати.

Дозвольте додати трохи інформації про те, чому не використовувати файли PID. Поки вони дуже популярні; вони також дуже хибні, і немає жодної причини, чому ви не зробили б це правильно.

Врахуйте це:

  1. Утилізація PID (знищення неправильного процесу):

    • /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, з яким ви дивились свого демона! Уявіть, що двоє людей починають X сеанс, і ви жалієтесь за X, щоб убити вашого. Це просто всі види поганого.

Якщо ви не хочете керувати процесом самостійно; є кілька ідеально хороших систем, які будуть виконувати функції монітора ваших процесів. Наприклад, погляньте на біг .


1
@Chas. Оунс: Я не думаю, що це потрібно. Це просто ускладнить реалізацію без поважних причин. Простота завжди важливіша; і якщо він часто перезавантажується, сон убереже його від негативного впливу на ресурси системи. У будь-якому випадку вже є повідомлення.
lhunath

2
@orschiro Немає споживання ресурсів, коли програма веде себе. Якщо воно існує відразу після запуску, безперервно, споживання ресурсів у режимі сну 1 все ще є незначним.
lhunath

7
Можу повірити, що я просто бачу цю відповідь. Дуже дякую!
getWeberForStackExchange

2
@ TomášZato ви можете зробити вищевказаний цикл, не перевіряючи код виходу процесу, while true; do myprocess; doneале зауважте, що зараз немає способу зупинити процес.
lhunath

2
@ SergeyP.akaazure Єдиний спосіб змусити батька вбити дитину під час виходу в Баш - це перетворити дитину на роботу і дати їй сигнал: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
Monit - це саме те, що ви шукаєте.
Сарк

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'

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


це могло б трохи спростити його, видаливши скрипт bash. що станеться, якщо сценарій python виходить з ладу? файл розблокований?
Том

1
Блокування файлу знімається, як тільки програма зупиняється, або вбиванням, природним шляхом або збоєм.
Крістіан Віттс

@Tom ... якщо бути більш точним - блокування більше не активне, як тільки робота файлу закриється. Якщо скрипт 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'
Рутрус

6

Вам слід використовувати monit, стандартний інструмент Unix, який може відстежувати різні речі в системі та реагувати відповідно.

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

перевірити процес checkqueue.py за допомогою pidfile /var/run/checkqueue.pid
       якщо pid змінено, то виконайте "checkqueue_restart.sh"

Ви також можете налаштувати monit, щоб надсилати вам електронні листи, коли він не перезапускається.


2
Monit - це чудовий інструмент, але він не є стандартним у формальному розумінні, що його визначають ні POSIX, ні SUSV.
Чарльз Даффі

5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi

класно, це добре розгортає частину мого псевдокоду. два qns: 1) як створити PIDFILE? 2) що psgrep? це не на сервері ubuntu.
Том,

ps grep - це лише невелике додаток, яке робить те ж саме ps ax|grep .... Ви можете просто встановити його або написати функцію для цього: функція psgrep () {ps ax | grep -v grep | grep -q "$ 1"}
соумерге

Щойно помітив, що я не відповів на ваше перше запитання.
соумергер

7
На дійсно зайнятому сервері можливо, що PID буде перероблений перед вами.
vartec

2

Я не впевнений, наскільки він портативний в операційних системах, але ви можете перевірити, чи містить ваша система команду '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, який може добре знаходити рядки шляхом регулярного висловлення, дуже дякую.
трійка

0

Я використовую це для свого npm Process

#!/bin/bash
for (( ; ; ))
do
date +"%T"
echo Start Process
cd /toFolder
sudo process
date +"%T"
echo Crash
sleep 1
done
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.