Jak napisać skrypt bash, aby zrestartować proces, jeśli on umrze?

226

Mam skrypt w języku Python, który sprawdza kolejkę i wykonuje akcję na każdym elemencie:

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

Jak napisać skrypt bash, który sprawdzi, czy jest uruchomiony, a jeśli nie, uruchom go. Z grubsza następujący pseudo kod (a może powinien zrobić coś takiego ps | grep?):

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

run checkqueue.py
write processid to processidfile

Nazwie to od crontab:

# crontab
*/5 * * * * /path/to/keepalivescript.sh
Tomek
źródło
4
Po prostu dodaj to na rok 2017. Użyj superwizora. crontab nie jest przeznaczony do wykonywania tego rodzaju zadań. Skrypt bash jest straszny przy emitowaniu prawdziwego błędu. stackoverflow.com/questions/9301494/…
mootmoot
Co powiesz na użycie inittab i respawn zamiast innych rozwiązań niesystemowych? Zobacz superuser.com/a/507835/116705
Lars Nordin

Odpowiedzi:

635

Unikaj plików PID, cronów lub czegokolwiek innego, co próbuje ocenić procesy, które nie są ich dziećmi.

Istnieje bardzo dobry powód, dla którego w UNIX możesz TYLKO czekać na swoje dzieci. Każda metoda (ps parsowanie, pgrep, przechowywanie PID, ...), która próbuje obejść tę wadę i ma luki w niej. Po prostu powiedz nie .

Zamiast tego potrzebujesz procesu, który monitoruje proces, aby był jego rodzicem. Co to znaczy? Oznacza to, że tylko proces, który rozpoczyna proces, może niezawodnie czekać na jego zakończenie. W bashu jest to absolutnie trywialne.

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

Powyższy fragment kodu bash działa myserverw untilpętli. Pierwsza linia zaczyna się myserveri czeka na zakończenie. Po zakończeniu untilsprawdza status wyjścia. Jeśli status wyjścia to 0, oznacza to, że zakończył się z wdziękiem (co oznacza, że ​​poprosiłeś go o zamknięcie i udało się to pomyślnie). W takim przypadku nie chcemy go ponownie uruchamiać (poprosiliśmy tylko o zamknięcie!). Jeśli status wyjścia nie jest 0, untiluruchomi ciało pętli, które emituje komunikat o błędzie na STDERR i ponownie uruchamia pętlę (powrót do linii 1) po 1 sekundzie .

Dlaczego czekamy sekundę? Ponieważ jeśli coś jest nie tak z sekwencją uruchamiania myserveri natychmiast ulega awarii, będziesz mieć bardzo intensywną pętlę ciągłego restartowania i awarii na rękach. Odciąża sleep 1to od tego napięcia.

Teraz wszystko, co musisz zrobić, to uruchomić ten skrypt bash (prawdopodobnie asynchronicznie), a on będzie go monitorował myserveri restartował w razie potrzeby. Jeśli chcesz uruchomić monitor przy rozruchu (dzięki czemu serwer „przetrwa” ponowne uruchomienie), możesz zaplanować go w cronie użytkownika (1) z @rebootregułą. Otwórz swoje reguły cron za pomocą crontab:

crontab -e

Następnie dodaj regułę, aby uruchomić skrypt monitorowania:

@reboot /usr/local/bin/myservermonitor

Alternatywnie; spójrz na inittab (5) i / etc / inittab. Możesz dodać tam linię, aby myserverzacząć od określonego poziomu inicjacji i automatycznie się odradzać.


Edytować.

Pozwól, że dodam kilka informacji o tym, dlaczego nie używać plików PID. Chociaż są bardzo popularne; są również bardzo wadliwe i nie ma powodu, dla którego nie zrobiłbyś tego po prostu we właściwy sposób.

Rozważ to:

  1. Recykling PID (zabicie niewłaściwego procesu):

    • /etc/init.d/foo start: start foo, zapisz fooPID do/var/run/foo.pid
    • Chwilę później: foojakoś umiera.
    • Chwilę później: każdy losowy proces, który się rozpoczyna (nazwij go bar), przyjmuje losowy PID, wyobraź sobie, że bierze on foostary PID.
    • Zauważyłeś foo, że zniknął: /etc/init.d/foo/restartczyta /var/run/foo.pid, sprawdza, czy nadal żyje, znajduje bar, myśli, że to foozabija, zaczyna nowe foo.
  2. Pliki PID stają się nieaktualne. Potrzebujesz nadmiernie skomplikowanej (lub powinienem powiedzieć, nietrywialnej) logiki, aby sprawdzić, czy plik PID jest nieaktualny i czy taka logika jest ponownie podatna na atak 1..

  3. Co jeśli nie masz dostępu do zapisu lub jesteś w środowisku tylko do odczytu?

  4. To bezcelowa nadmierna komplikacja; zobacz, jak prosty jest mój przykład powyżej. W ogóle nie trzeba tego komplikować.

Zobacz także: Czy pliki PID są nadal wadliwe, gdy robią to „dobrze”?

Tak poza tym; nawet gorzej niż parsowanie plików PID ps! Nigdy tego nie rób.

  1. psjest bardzo nieprzenośny. Chociaż można go znaleźć w prawie każdym systemie UNIX; jego argumenty są bardzo różne, jeśli chcesz otrzymać niestandardowe dane wyjściowe. A standardowe wyjście jest WYŁĄCZNIE do spożycia przez ludzi, a nie do analizowania skryptów!
  2. Parsowanie psprowadzi do wielu fałszywych trafień. Weźmy ps aux | grep PIDprzykład, a teraz wyobraźmy sobie, że ktoś zaczyna gdzieś proces z liczbą jako argumentem, który akurat jest taki sam jak PID, którym patrzyłeś na swojego demona! Wyobraź sobie, że dwie osoby rozpoczynają sesję X, a ty żartujesz, że X zabija twoją. To tylko wszelkiego rodzaju złe.

Jeśli nie chcesz sam zarządzać procesem; istnieje kilka doskonale dobrych systemów, które będą działać jako monitor twoich procesów. Zobacz na przykład runit .

lhunath
źródło
1
@Chas. Ownes: Nie sądzę, żeby to było konieczne. Utrudniłoby to wdrożenie bez uzasadnionego powodu. Prostota jest zawsze ważniejsza; a jeśli często się restartuje, sen nie będzie miał żadnego negatywnego wpływu na zasoby systemowe. W każdym razie jest już wiadomość.
lhunath,
2
@orschiro Nie ma zużycia zasobów, gdy program się zachowuje. Jeśli istnieje natychmiast po uruchomieniu, w sposób ciągły zużycie zasobów w trybie uśpienia 1 jest nadal całkowicie znikome.
lhunath
7
Mogę uwierzyć, że właśnie widzę tę odpowiedź. Dzięki wielkie!
getWeberForStackExchange
2
@ TomášZato można wykonać powyższą pętlę bez testowania kodu wyjścia procesu, while true; do myprocess; doneale należy pamiętać, że nie ma już sposobu, aby zatrzymać proces.
lhunath,
2
@ SergeyP.akaazure Jedynym sposobem, aby zmusić rodziców do zabicia dziecka na wyjeździe w bash jest włączenie dziecka do pracy i sygnalizuje go:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath
33

Spójrz na monit ( http://mmonit.com/monit/ ). Obsługuje uruchamianie, zatrzymywanie i ponowne uruchamianie skryptu i może przeprowadzać kontrole kondycji oraz restartować w razie potrzeby.

Lub wykonaj prosty skrypt:

while true
do
/your/script
sleep 1
done
Bernd
źródło
4
Monit jest dokładnie tym, czego szukasz.
Sarke,
4
„while 1” nie działa. Potrzebujesz „while [1]” lub „while true” lub „while:”. Zobacz unix.stackexchange.com/questions/367108/what-does-while-mean
Curtis Yallop
8

Najłatwiej to zrobić za pomocą flokowania w pliku. Zrobiłbyś to w skrypcie 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()

W powłoce możesz faktycznie przetestować, czy działa:

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

Ale oczywiście nie musisz testować, ponieważ jeśli jest już uruchomiony i uruchomisz go ponownie, zakończy działanie 'other instance already running'

Kiedy proces umiera, wszystkie jego deskryptory plików są zamykane, a wszystkie blokady są automatycznie usuwane.

vartec
źródło
które mogłoby to nieco uprościć, usuwając skrypt bash. co się stanie, jeśli skrypt Pythona ulegnie awarii? czy plik jest odblokowany?
Tom
1
Blokada pliku jest zwalniana, gdy tylko aplikacja się zatrzyma, albo poprzez zabijanie, naturalnie, albo awarię.
Christian Witts
@Tom ... aby być nieco bardziej precyzyjnym - blokada nie jest już aktywna, gdy tylko uchwyt pliku zostanie zamknięty. Jeśli skrypt Pythona nigdy nie zamyka uchwytu pliku umyślnie i upewnia się, że nie zostanie on automatycznie zamknięty przez obiekt pliku, który jest zbierany w pamięci, wówczas zamknięcie prawdopodobnie oznacza, że ​​skrypt został zakończony / został zabity. Działa to nawet w przypadku restartów i tym podobnych.
Charles Duffy,
1
Są o wiele lepsze sposoby użycia flock... w rzeczywistości strona podręcznika wyraźnie pokazuje, jak! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"to odpowiednik bash dla Twojego Pythona i pozostawia blokadę wstrzymaną (więc jeśli wykonasz proces, blokada pozostanie zablokowana, dopóki proces się nie zakończy).
Charles Duffy
Głosowałem za tobą, ponieważ twój kod jest nieprawidłowy. Używanie flockjest poprawne, ale twoje skrypty są nieprawidłowe. Jedyne polecenie, które musisz ustawić w crontab, to:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus
6

Powinieneś użyć monit, standardowego narzędzia uniksowego, które może monitorować różne rzeczy w systemie i odpowiednio reagować.

Z dokumentacji: http://mmonit.com/monit/documentation/monit.html#pid_testing

sprawdź proces checkqueue.py za pomocą pidfile /var/run/checkqueue.pid
       jeśli zmieniono pid, to wykonaj „checkqueue_restart.sh”

Możesz także skonfigurować monit tak, aby wysyłał Ci e-maile po ponownym uruchomieniu.

clofresh
źródło
2
Monit jest świetnym narzędziem, ale to nie norma w formalnym sensie są określone w obu POSIX lub SUSV.
Charles Duffy
5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi
soulmerge
źródło
fajnie, to całkiem dobrze rozwija mój pseudo kod. dwa qns: 1) Jak wygenerować plik PIDFILE? 2) co to jest psgrep? nie ma go na serwerze Ubuntu.
Tom
ps grep to tylko mała aplikacja, która robi to samo co ps ax|grep .... Możesz po prostu zainstalować lub napisać dla tego funkcję: function psgrep () {ps ax | grep -v grep | grep -q "$ 1"}
soulmerge
Właśnie zauważyłem, że nie odpowiedziałem na twoje pierwsze pytanie.
soulmerge
7
Na naprawdę zapracowanym serwerze możliwe jest, że PID zostanie poddany recyklingowi przed sprawdzeniem.
vartec
2

Nie jestem pewien, jak przenośny jest w różnych systemach operacyjnych, ale możesz sprawdzić, czy twój system zawiera polecenie „run-one”, tj. „Man run-one”. W szczególności ten zestaw poleceń obejmuje „ciągłe uruchamianie”, co wydaje się być dokładnie tym, czego potrzeba.

Ze strony podręcznika:

Run-one-stale COMMAND [ARGS]

Uwaga: oczywiście można to wywoływać z poziomu skryptu, ale także eliminuje potrzebę posiadania skryptu.

Daniel Bradley
źródło
Czy oferuje to jakąkolwiek przewagę nad przyjętą odpowiedzią?
tripleee
1
Tak, myślę, że lepiej jest użyć wbudowanego polecenia niż napisać skrypt powłoki, który robi to samo, co będzie musiał być utrzymywany jako część systemowej bazy kodu. Nawet jeśli funkcjonalność jest wymagana jako część skryptu powłoki, powyższa komenda może być również użyta, więc jest istotna dla pytania dotyczącego skryptu powłoki.
Daniel Bradley
To nie jest „wbudowane”; jeśli jest instalowany domyślnie na niektórych dystrybucjach, twoja odpowiedź powinna prawdopodobnie określać dystrybucję (i najlepiej dołączyć wskaźnik, gdzie można go pobrać, jeśli twój nie jest jednym z nich).
tripleee
Wygląda na to, że jest to narzędzie Ubuntu; ale jest opcjonalny nawet w Ubuntu. manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee
Warto zauważyć: narzędzia run-one robią dokładnie to, co mówi ich nazwa - możesz uruchomić tylko jedną instancję dowolnego polecenia uruchamianego za pomocą run-one-nnnnn. Inne odpowiedzi tutaj są bardziej wykonalne agnostyczne - nie przejmują się w ogóle treścią polecenia.
David Kohen
1

Z wielkim sukcesem zastosowałem następujący skrypt na wielu serwerach:

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

uwagi:

  • Poszukuje procesu Java, więc mogę użyć jps, jest to o wiele bardziej spójne we wszystkich dystrybucjach niż ps
  • $INSTALLATION zawiera dość ścieżki procesu, co jest całkowicie jednoznaczne
  • Użyj snu, czekając na śmierć procesu, unikaj gromadzenia zasobów :)

Ten skrypt jest w rzeczywistości używany do zamykania działającej instancji tomcat, którą chcę zamknąć (i poczekać) w linii poleceń, więc uruchomienie go jako procesu potomnego po prostu nie jest dla mnie opcją.

Kevin Wright
źródło
1
grep | awkwciąż jest antypatternem - chcesz połączyćawk "/$INSTALLATION/ { print \$1 }" bezużyteczne grepze skryptem Awk, który potrafi bardzo dobrze znajdować wiersze poprzez wyrażenie regularne, dziękuję bardzo.
tripleee
0

Używam tego do mojego procesu npm

#!/bin/bash
for (( ; ; ))
do
date +"%T"
echo Start Process
cd /toFolder
sudo process
date +"%T"
echo Crash
sleep 1
done
BitDEVil2K16
źródło