Bir işlemi ölürse yeniden başlatmak için bash betiğini nasıl yazarım?


226

Bir kuyruk kontrol ve her öğe üzerinde bir eylem gerçekleştirecek bir python komut dosyası var:

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

Çalışıp çalışmadığını kontrol edecek bir bash betiği nasıl yazarım? Kabaca aşağıdaki sahte kod (veya belki böyle bir şey yapmalı ps | grep?):

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

run checkqueue.py
write processid to processidfile

Bunu bir crontab'dan arayacağım:

# crontab
*/5 * * * * /path/to/keepalivescript.sh

4
Sadece 2017 için eklemek için. Denetleyiciyi kullanın. crontab bu tür bir görev yapmak anlamına gelmez. Bir bash betiği gerçek hatayı yayarken korkunçtur. stackoverflow.com/questions/9301494/…
mootmoot

Diğer sistem dışı çözümler yerine inittab ve respawn kullanmaya ne dersiniz? Bkz. Superuser.com/a/507835/116705
Lars Nordin

Yanıtlar:


635

PID dosyalarından, dolandırıcılardan veya çocukları olmayan süreçleri değerlendirmeye çalışan herhangi bir şeyden kaçının.

UNIX'te SADECE çocuklarınızı beklemenizin çok iyi bir nedeni var. Etrafında çalışmaya çalışan herhangi bir yöntem (ps ayrıştırma, pgrep, bir PID depolamak, ...) kusurludur ve içinde boşluklar vardır. Sadece hayır de .

Bunun yerine, sürecin üst öğesi olmak için sürecinizi izleyen sürece ihtiyacınız vardır. Ne anlama geliyor? Bu, yalnızca işleminizi başlatan işlemin güvenli bir şekilde sona ermesini bekleyebileceği anlamına gelir . Bash'da bu kesinlikle önemsiz.

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

Yukarıdaki bash kodu myserverbir untildöngüde çalışır . İlk satır başlar myserverve bitmesini bekler. O bittiğinde, untilonun çıkış durumunu kontrol eder. Çıkış durumu ise 0, zarif bir şekilde sona erdiği anlamına gelir (bu, bir şekilde kapatmasını istediniz ve başarılı bir şekilde yaptığını gösterir). Bu durumda yeniden başlatmak istemiyoruz (sadece kapatmasını istedik!). Çıkış durumu değilse 0 , untilSTDERR üzerinde bir hata mesajı yayan ve 1 saniye sonra döngüyü yeniden başlatan döngü gövdesini çalıştırır .

Neden bir saniye bekleyelim? Çünkü başlangıç ​​dizisi ile ilgili bir sorun varsa myserverve hemen çökerse, ellerinizde sürekli yeniden başlatma ve çökme yoğun bir döngüye sahip olacaksınız. sleep 1Bundan zorlanma alır götürür.

Şimdi yapmanız gereken tek şey bu bash betiğini (eşzamansız olarak, muhtemelen) myserverbaşlatmaktır ve gerektiği şekilde izleyip yeniden başlatacaktır. Monitörü önyükleme sırasında başlatmak istiyorsanız (sunucuyu "hayatta kalmayı" başlatarak), @rebootkuralı kullanarak kullanıcının cron'unda (1) programlayabilirsiniz . Cron kurallarınızı aşağıdakilerle açın crontab:

crontab -e

Ardından, monitör komut dosyanızı başlatmak için bir kural ekleyin:

@reboot /usr/local/bin/myservermonitor

Alternatif olarak; inittab (5) ve / etc / inittab'a bakın. myserverBelirli bir başlangıç ​​düzeyinde başlamak ve otomatik olarak yeniden doğmak için oraya bir satır ekleyebilirsiniz .


Düzenle.

Bana neden bazı bilgileri ekleyelim değil PID dosyalarını kullanmak. Çok popüler olsalar da; onlar da çok kusurlu ve sadece doğru şekilde yapmamanızın bir nedeni yok.

Bunu düşün:

  1. PID geri dönüşümü (yanlış işlemi öldürmek):

    • /etc/init.d/foo start: Başlat foo, foo's PID'sini yaz/var/run/foo.pid
    • Bir süre sonra: bir fooşekilde ölür.
    • Bir süre sonra: başlayan herhangi bir rastgele işlem (onu çağır bar) rastgele bir PID alır, fooeski PID'yi aldığını hayal edin .
    • Gittiğini fark ettin foo: /etc/init.d/foo/restartokur /var/run/foo.pid, hala hayatta olup olmadığını kontrol eder, bulur bar, düşünür foo, öldürür, yeni bir başlangıç ​​yapar foo.
  2. PID dosyaları bayat. PID dosyasının eski olup olmadığını kontrol etmek için aşırı karmaşık (ya da önemsiz değil) mantığına ihtiyacınız var ve böyle bir mantığa karşı yine savunmasız 1..

  3. Yazma erişiminiz bile yoksa veya salt okunur bir ortamdaysanız ne olur?

  4. Anlamsız bir aşırı komplikasyondur; yukarıdaki örneğimin ne kadar basit olduğunu görün. Bunu karmaşıklaştırmaya gerek yok.

Ayrıca bkz: PID dosyaları 'doğru' yaparken hala kusurlu mu?

Bu arada; PID dosyalarından bile daha kötü ayrışıyor ps! Bunu asla yapma.

  1. psçok taşınabilir. Hemen hemen her UNIX sisteminde bulduğunuzda; standart dışı çıktılar istiyorsanız argümanları büyük ölçüde değişir. Ve standart çıktı SADECE insan tüketimi için, komut dosyası ayrıştırma için değil!
  2. Ayrıştırma ps, bir sürü yanlış pozitif yol açar. ps aux | grep PIDÖrneği ele alın ve şimdi birisinin, artalanınıza baktığınız PID ile aynı olan, argüman olarak bir yerde bir sayı ile işleme başladığını hayal edin! Bir X seansına başladığınızı ve X'in sizinkini öldürmesi için selamladığınızı düşünün. Sadece her türlü kötü.

Süreci kendiniz yönetmek istemiyorsanız; süreçleriniz için monitör görevi görecek bazı mükemmel sistemler var. Örneğin runit'e bakın .


1
@Chas. Sahipleri: Bunun gerekli olduğunu düşünmüyorum. Sadece iyi bir sebep olmadan uygulamayı zorlaştırır. Sadelik her zaman daha önemlidir; ve sık sık yeniden başlatılırsa, uyku sistem kaynaklarınız üzerinde herhangi bir kötü etkiye sahip olmayacaktır. Zaten bir mesaj zaten var.
lhunath

2
@orschiro Program davrandığında kaynak tüketimi yoktur. Eğer lansman anında, sürekli olarak mevcutsa, uyku 1 ile kaynak tüketimi hala tamamen ihmal edilebilir düzeydedir.
lhunath

7
Sadece bu cevabı gördüğüme inanabilirim . Çok teşekkürler!
getWeberForStackExchange

2
@ TomášZato, işlemin çıkış kodunu test etmeden yukarıdaki döngüyü yapabilirsiniz, while true; do myprocess; doneancak şimdi işlemi durdurmanın bir yolu olmadığını unutmayın.
lhunath

2
@ SergeyP.akaazure Ebeveynini bash çıkışında çocuğu öldürmeye zorlamanın tek yolu çocuğu bir işe dönüştürmek ve ona işaret etmektir:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath

33

Monit'e bir göz atın ( http://mmonit.com/monit/ ). Komut dosyanızın başlatılmasını, durdurulmasını ve yeniden başlatılmasını yönetir ve gerekirse sağlık denetimleri ve yeniden başlatmalar yapabilir.

Veya basit bir komut dosyası yapın:

while true
do
/your/script
sleep 1
done

4
Monit tam olarak aradığınız şeydir.
Sarke

4
"while 1" çalışmıyor. "While [1]" veya "while true" veya "while:" gereklidir. Bkz. Unix.stackexchange.com/questions/367108/what-does-while-mean
Curtis Yallop

8

Bunu yapmanın en kolay yolu flock on file kullanmaktır. Python betiğinde

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()

Kabukta gerçekten çalışıp çalışmadığını test edebilirsiniz:

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

Ama tabii ki test etmek zorunda değilsiniz, çünkü zaten çalışıyorsa ve yeniden başlatırsanız, 'other instance already running'

İşlem bittiğinde, dosya tanımlayıcılarının tümü kapatılır ve tüm kilitler otomatik olarak kaldırılır.


bash betiğini kaldırarak bunu biraz basitleştirebilir. python betiği çökerse ne olur? dosya kilidi açıldı mı?
Tom

1
Dosya kilidi, uygulama durduğunda, öldürerek, doğal olarak veya çökerek bırakılır.
Christian Witts

@Tom ... biraz daha hassas olmak gerekirse - dosya tanıtıcısı kapandığında kilit artık etkin değil. Python betiği dosya tanıtıcısını hiçbir zaman niyetle kapatmazsa ve çöp toplanan dosya nesnesi aracılığıyla otomatik olarak kapatılmadığından emin olursa, kapatılması muhtemelen betiğin çıktığı / öldürüldüğü anlamına gelir. Bu, yeniden başlatmalar ve benzerleri için bile çalışır.
Charles Duffy

1
Kullanmanın çok daha iyi yolları var flock... Aslında, man sayfası açıkça nasıl olduğunu gösteriyor! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"Python'unuza eşdeğerdir ve kilidi açık bırakır (böylece bir işlemi yürütürseniz, kilit bu işlem bitene kadar beklemeye devam eder).
Charles Duffy

Kodunuz yanlış olduğu için sizi reddettim. Kullanmak flockdoğru yoldur, ancak komut dosyalarınız yanlıştır. flock -n /tmp/script.lock -c '/path/to/my/script.py'
Crontab'da

6

Sistemdeki farklı şeyleri izleyebilen ve buna göre tepki verebilen standart bir unix aracı olan monit'i kullanmalısınız.

Dokümanlardan: http://mmonit.com/monit/documentation/monit.html#pid_testing

checkqueue.py işlemini pidfile /var/run/checkqueue.pid ile kontrol edin
       pid değiştirilirse "checkqueue_restart.sh" komutunu çalıştırın.

Monit'i, yeniden başlatma yaptığında size e-posta gönderecek şekilde de yapılandırabilirsiniz.


2
Monit harika bir araçtır, ancak POSIX veya SUSV'de belirtilen resmi anlamda standart değildir .
Charles Duffy

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

güzel, bu benim sahte kodu bazı oldukça etli. two qns: 1) PIDFILE'ı nasıl oluştururum? 2) Psgrep nedir? Ubuntu sunucusunda değil.
Tom

ps grep aynı şeyi yapan sadece küçük bir uygulama ps ax|grep .... Sadece yükleyebilir veya bunun için bir işlev yazabilirsiniz: function psgrep () {ps axe | grep -v grep | grep -q "$ 1"}
soulmerge

Sadece ilk sorunuzu cevaplamadığımı fark ettim.
soulmerge

7
Gerçekten meşgul sunucuda, kontrol etmeden önce PID'nin geri dönüştürülmesi mümkündür.
vartec

2

İşletim sistemlerinde ne kadar taşınabilir olduğundan emin değilim, ancak sisteminizin 'run-one' komutunu, yani "man run-one" içerip içermediğini kontrol edebilirsiniz. Özellikle, bu komut kümesi tam olarak ihtiyaç duyulan şey gibi görünen 'sürekli bir şekilde' içerir.

Man sayfasından:

sürekli çalışan KOMUT [ARGS]

Not: açıkçası bu sizin betiğinizden çağrılabilir, ama aynı zamanda bir betiğe sahip olma ihtiyacını ortadan kaldırır.


Bu, kabul edilen cevaba göre herhangi bir avantaj sağlıyor mu?
Üçlü

1
Evet, sistem kod tabanının bir parçası olarak korunması gereken aynı şeyi yapan bir kabuk komut dosyası yazmak yerine yerleşik bir komut kullanmanın daha iyi olduğunu düşünüyorum. Bir kabuk betiğinin parçası olarak işlevsellik gerekli olsa bile, yukarıdaki komut bir kabuk betiği sorusuyla ilgili olması için de kullanılabilir.
Daniel Bradley

Bu "yerleşik" değildir; bazı dağıtımlarda varsayılan olarak yüklüyse, cevabınız dağıtımın büyük olasılıkla dağıtımını belirtmelidir (ve sizinkinden biri değilse, nereden indireceğinize dair bir işaretçi içermelidir).
üçlü

Bir Ubuntu yardımcı programı gibi görünüyor; ancak Ubuntu'da bile isteğe bağlıdır. manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee

Dikkat çekmeye değer: run-one yardımcı programları tam olarak adlarının söylediklerini yapar - run-one-nnnnn ile çalıştırılan herhangi bir komutun yalnızca bir örneğini çalıştırabilirsiniz. Buradaki diğer cevaplar daha yürütülebilir agnostiktir - komutun içeriğini hiç umursamıyorum.
David Kohen

1

Aşağıdaki komut dosyasını çok sayıda sunucuda büyük bir başarıyla kullandım:

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

notlar:

  • Bir java süreci arıyor, bu yüzden jps kullanabilirsiniz, bu ps'den daha dağıtımlar arasında çok daha tutarlı
  • $INSTALLATION Tamamen açık olan süreç yolunu yeterince içeriyor
  • Sürecin ölmesini beklerken uykuyu kullanın, kaynak atmaktan kaçının :)

Bu komut dosyası, komut satırında kapatmak (ve beklemek) istediğim tomcat'in çalışan bir örneğini kapatmak için kullanılır, bu nedenle bir alt işlem olarak başlatmak benim için bir seçenek değildir.


1
grep | awkhala bir antipattern - awk "/$INSTALLATION/ { print \$1 }"işe yaramaz grepolan Awk betiğiyle sınırlamak istiyorsun , bu da düzenli ifadenin kendisini çok iyi bulabilen satırlar, çok teşekkür ederim.
Üçlü

0

Bunu npm Süreci için kullanıyorum

#!/bin/bash
for (( ; ; ))
do
date +"%T"
echo Start Process
cd /toFolder
sudo process
date +"%T"
echo Crash
sleep 1
done
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.