ฉันจะเขียนสคริปต์ทุบตีเพื่อเริ่มต้นกระบวนการใหม่ได้อย่างไรถ้ามันตาย?


226

ฉันมีสคริปต์หลามที่จะตรวจสอบคิวและดำเนินการกับแต่ละรายการ:

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

ฉันจะเขียนสคริปต์ทุบตีที่จะตรวจสอบว่ามันทำงานอยู่หรือไม่และเริ่มต้นได้อย่างไร รหัสหลอกต่อไปนี้คร่าวๆ (หรือบางทีมันควรจะทำอะไรเช่น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 ใช้ supervisord crontab ไม่ได้หมายถึงการทำงานประเภทนี้ สคริปต์ทุบตีนั้นแย่มากในการเปล่งความผิดพลาดที่แท้จริง stackoverflow.com/questions/9301494/…
mootmoot

วิธีการเกี่ยวกับการใช้ inittab และ respawn แทนการใช้โซลูชั่นที่ไม่ใช่ระบบอื่น ๆ ? ดูsuperuser.com/a/507835/116705
Lars Nordin

คำตอบ:


635

หลีกเลี่ยงไฟล์ PID, crons หรืออะไรก็ตามที่พยายามประเมินกระบวนการที่ไม่ใช่ลูกของพวกเขา

มีเหตุผลที่ดีมากที่ทำไมใน UNIX คุณสามารถรอลูก ๆ ของคุณได้เท่านั้น วิธีการใด ๆ (การแยกวิเคราะห์ ps, pgrep, การจัดเก็บ PID, ... ) ที่พยายามที่จะหลีกเลี่ยงข้อบกพร่องและมีช่องโหว่อยู่ในนั้น เพียงแค่บอกว่าไม่มี

แต่คุณต้องการกระบวนการที่ตรวจสอบกระบวนการของคุณให้เป็นกระบวนการหลัก สิ่งนี้หมายความว่า? หมายความว่าเฉพาะกระบวนการที่เริ่มกระบวนการของคุณเท่านั้นที่สามารถรอจนจบได้อย่างน่าเชื่อถือ ในทุบตีนี้เป็นเรื่องเล็กน้อยอย่างแน่นอน

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

โค้ดทุบตีส่วนบนทำงานmyserverเป็นuntilวนรอบ บรรทัดแรกเริ่มต้นmyserverและรอจนจบ เมื่อมันสิ้นสุดให้untilตรวจสอบสถานะการออก หากสถานะการออกคือ0มันหมายความว่ามันจบลงอย่างสง่างาม (ซึ่งหมายความว่าคุณขอให้ปิดอย่างใดและมันก็ประสบความสำเร็จ) ในกรณีนั้นเราไม่ต้องการเริ่มต้นใหม่ (เราเพิ่งขอให้ปิดระบบ!) ถ้าสถานะทางออกคือไม่ 0 , untilจะทำงานร่างกายห่วงซึ่งส่งเสียงข้อความข้อผิดพลาดใน STDERR และเริ่มต้นใหม่วง (กลับไปสาย 1) หลัง 1 วินาที

เราจะรออีกทำไม เพราะหากมีบางอย่างผิดปกติกับลำดับการเริ่มต้นmyserverและมันล้มเหลวทันทีคุณจะมีวงวนที่เข้มข้นมากในการรีสตาร์ทและหยุดอย่างต่อเนื่อง ที่sleep 1จะไปความเครียดจากที่

ตอนนี้สิ่งที่คุณต้องทำคือเริ่มต้นสคริปต์ทุบตีนี้ (แบบอะซิงโครนัสอาจ) และมันจะตรวจสอบmyserverและเริ่มใหม่ตามความจำเป็น หากคุณต้องการเริ่มต้นมอนิเตอร์เมื่อบู๊ตเครื่อง (ทำให้เซิร์ฟเวอร์ "รอดใหม่" ทำการรีบู๊ต) คุณสามารถกำหนดเวลาใน cron ของผู้ใช้ (1) ด้วย@rebootกฎ เปิดกฎ cron ของคุณด้วยcrontab:

crontab -e

จากนั้นเพิ่มกฎเพื่อเริ่มสคริปต์การตรวจสอบของคุณ:

@reboot /usr/local/bin/myservermonitor

อีกทางเลือกหนึ่ง; ดูที่ inittab (5) และ / etc / inittab คุณสามารถเพิ่มบรรทัดที่นั่นเพื่อmyserverเริ่มต้นในระดับเริ่มต้นที่แน่นอนและจะเกิดขึ้นใหม่โดยอัตโนมัติ


แก้ไข

ให้ฉันเพิ่มข้อมูลบางอย่างเกี่ยวกับสาเหตุที่ไม่ใช้ไฟล์ PID ในขณะที่พวกเขาเป็นที่นิยมมาก พวกเขายังมีข้อบกพร่องมากและไม่มีเหตุผลว่าทำไมคุณไม่ทำตามวิธีที่ถูกต้อง

พิจารณาสิ่งนี้:

  1. การรีไซเคิล PID (ฆ่ากระบวนการที่ไม่ถูกต้อง):

    • /etc/init.d/foo start: เริ่มfooเขียนfooPID ของไปที่/var/run/foo.pid
    • ในขณะที่ภายหลัง: fooตายอย่างใด
    • ครู่ต่อมา: กระบวนการสุ่มใด ๆ ที่เริ่มต้น (เรียกว่าbar) จะใช้ PID แบบสุ่มลองจินตนาการว่ามันใช้fooPID เก่าของ
    • คุณสังเกตเห็นfoo's หายไป: /etc/init.d/foo/restartอ่าน/var/run/foo.pid, การตรวจสอบเพื่อดูว่ามันยังมีชีวิตอยู่พบbar, คิดว่ามันฆ่ามันเริ่มต้นใหม่foofoo
  2. ไฟล์ PID ไม่เสถียร คุณต้องการที่ซับซ้อนมากกว่า (หรือฉันควรจะพูดว่าไม่น่ารำคาญ) ตรรกะในการตรวจสอบว่าไฟล์ PID จะค้างและตรรกะใด ๆ 1.ดังกล่าวเป็นอีกความเสี่ยงที่จะ

  3. ถ้าคุณไม่มีการเข้าถึงการเขียนหรืออยู่ในสภาพแวดล้อมแบบอ่านอย่างเดียวล่ะ?

  4. มันเป็นเรื่องที่ไม่มีจุดหมาย ดูตัวอย่างง่ายๆของฉันด้านบน ไม่จำเป็นต้องมีความซับซ้อนเลย

ดูเพิ่มเติม: ไฟล์ PID ยังมีข้อบกพร่องเมื่อทำในสิ่งที่ 'ถูกต้อง' หรือไม่?

ยังไงซะ; ยิ่งเลวกว่าไฟล์ PID กำลังแยกps! ไม่เคยทำเช่นนี้

  1. psunportable มาก ในขณะที่คุณพบมันในเกือบทุกระบบ UNIX; อาร์กิวเมนต์จะแตกต่างกันมากหากคุณต้องการเอาต์พุตที่ไม่ได้มาตรฐาน และเอาต์พุตมาตรฐานมีไว้สำหรับการบริโภคของมนุษย์เท่านั้นไม่ใช่เพื่อการแยกวิเคราะห์แบบมีสคริปต์!
  2. การแยกวิเคราะห์psนำไปสู่การบวกเท็จจำนวนมาก ใช้ps aux | grep PIDตัวอย่างและตอนนี้คิดว่ามีคนที่จะเริ่มกระบวนการที่มีอยู่ที่ไหนสักแห่งจำนวนเป็นอาร์กิวเมนต์ที่เกิดขึ้นจะเป็นเช่นเดียวกับคุณ PID จ้องภูตของคุณด้วย! ลองนึกภาพคนสองคนที่เริ่มเซสชัน X และคุณต้องการให้ X ฆ่าคุณ มันเป็นเรื่องเลวร้ายทุกชนิด

หากคุณไม่ต้องการจัดการกระบวนการด้วยตนเอง มีบางระบบที่ดีอย่างสมบูรณ์ออกมีที่จะทำหน้าที่ตรวจสอบกระบวนการของคุณ ดูเป็นrunitเช่น


1
@Chas Ownes: ฉันไม่คิดว่ามันจำเป็น มันจะทำให้การใช้งานยุ่งยากโดยไม่มีเหตุผลที่ดี ความเรียบง่ายมีความสำคัญมากกว่าเสมอ และถ้ามันเริ่มใหม่บ่อยครั้งสลีปจะป้องกันไม่ให้มีผลกระทบกับทรัพยากรระบบ มีข้อความอยู่แล้ว
lhunath

2
@orschiro ไม่มีการสิ้นเปลืองทรัพยากรเมื่อโปรแกรมทำงาน หากมีอยู่ในทันทีที่เปิดใช้งานอย่างต่อเนื่องการใช้ทรัพยากรด้วยโหมดสลีป 1 ก็ยังไม่มีความสำคัญมากนัก
lununath

7
เชื่อได้ไหมว่าฉันเพิ่งเห็นคำตอบนี้ ขอบคุณมาก!
getWeberForStackExchange

2
@ TomášZatoคุณสามารถทำลูปข้างต้นได้โดยไม่ต้องทดสอบรหัสออกของกระบวนการwhile true; do myprocess; doneแต่โปรดทราบว่าขณะนี้ไม่มีวิธีที่จะหยุดกระบวนการได้
lununath

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 เป็นสิ่งที่คุณกำลังมองหา
Sarke

4
"ในขณะที่ 1" ไม่ทำงาน คุณต้องการ "ในขณะที่ [1]" หรือ "ในขณะที่จริง" หรือ "ในขณะที่:" ดูunix.stackexchange.com/questions/367108/what-does-while-mean
Curtis Yallop

8

วิธีที่ง่ายที่สุดที่จะทำคือใช้ flock ในไฟล์ ในสคริปต์ 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 ขัดข้อง ไฟล์ถูกปลดล็อคหรือไม่
Tom

1
การล็อกไฟล์จะถูกปล่อยทันทีที่แอปพลิเคชันหยุดไม่ว่าจะเป็นการฆ่าตามธรรมชาติหรือการหยุดทำงาน
Christian Witts

@Tom ... ให้แม่นยำยิ่งขึ้นอีกเล็กน้อย - ล็อคไม่ทำงานอีกต่อไปเมื่อมีการจัดการไฟล์ที่ปิดอยู่ หากสคริปต์ Python ไม่เคยปิดการจัดการไฟล์โดยเจตนาและตรวจสอบให้แน่ใจว่าจะไม่ถูกปิดโดยอัตโนมัติผ่านทางวัตถุไฟล์ที่ถูกรวบรวมขยะแสดงว่าการปิดอาจหมายความว่าสคริปต์ออก / ถูกฆ่า มันใช้งานได้แม้กระทั่งการรีบูตและเช่นนี้
Charles Duffy

1
มีวิธีที่ดีกว่ามากในการใช้flock... ในความเป็นจริงหน้าคนแสดงให้เห็นอย่างชัดเจนว่า! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"เป็น bash เทียบเท่ากับ Python ของคุณและปล่อยให้ล็อคค้างไว้ (ดังนั้นหากคุณดำเนินการตามกระบวนการการล็อคจะยังคงอยู่จนกว่ากระบวนการนั้นจะออก)
Charles Duffy

ฉันลงคะแนนให้คุณเพราะรหัสของคุณผิด การใช้flockเป็นวิธีที่ถูกต้อง แต่สคริปต์ของคุณผิด คำสั่งเดียวที่คุณต้องตั้งค่าใน crontab คือ:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus

6

คุณควรใช้ monit ซึ่งเป็นเครื่องมือยูนิกซ์มาตรฐานที่สามารถตรวจสอบสิ่งต่าง ๆ ในระบบและตอบสนองได้

จากเอกสาร: http://mmonit.com/monit/documentation/monit.html#pid_testing

ตรวจสอบกระบวนการ checkqueue.py ด้วย pidfile /var/run/checkqueue.pid
       ถ้าเปลี่ยน pid แล้ว exec "checkqueue_restart.sh"

คุณสามารถกำหนดค่า monit เพื่อส่งอีเมลถึงคุณเมื่อทำการรีสตาร์ท


2
Monit เป็นเครื่องมือที่ยอดเยี่ยม แต่มันไม่ได้เป็นมาตรฐานในแง่ของการระบุอย่างเป็นทางการใน POSIX หรือ SUSV
Charles Duffy

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

เจ๋งมากนั่นคือ fleshing out ของโค้ดหลอกฉันบางอย่างดี สอง qns: 1) ฉันจะสร้าง PIDFILE ได้อย่างไร 2) psgrep คืออะไร มันไม่ได้อยู่บนเซิร์ฟเวอร์อูบุนตู
Tom

PS grep เป็นเพียง app ps ax|grep ...ขนาดเล็กที่ไม่เป็นเช่นเดียวกับ คุณสามารถติดตั้งหรือเขียนฟังก์ชั่นสำหรับ: function psgrep () {ps axe | grep -v grep | grep -q "$ 1"}
Soulmerge

เพิ่งสังเกตเห็นว่าฉันไม่ได้ตอบคำถามแรกของคุณ
Soulmerge

7
บนเซิร์ฟเวอร์ที่ยุ่งมากอาจเป็นไปได้ว่า PID จะถูกรีไซเคิลก่อนที่คุณจะตรวจสอบ
vartec

2

ฉันไม่แน่ใจว่ามันพกพาได้อย่างไรในระบบปฏิบัติการ แต่คุณอาจตรวจสอบว่าระบบของคุณมีคำสั่ง 'run-one' หรือไม่นั่นคือ "man run-one" โดยเฉพาะชุดคำสั่งนี้รวมถึง 'เรียกใช้อย่างใดอย่างหนึ่งอย่างต่อเนื่อง' ซึ่งดูเหมือนจะเป็นสิ่งที่จำเป็น

จากหน้าคน:

เรียกใช้คำสั่งอย่างต่อเนื่อง [ARGS]

หมายเหตุ: แน่นอนว่าสิ่งนี้สามารถเรียกได้จากภายในสคริปต์ของคุณ แต่มันก็ไม่จำเป็นต้องมีสคริปต์เลย


ข้อเสนอนี้มีข้อได้เปรียบเหนือคำตอบที่ยอมรับหรือไม่
tripleee

1
ใช่ฉันคิดว่าการใช้คำสั่งในตัวดีกว่าการเขียนเชลล์สคริปต์ที่ทำสิ่งเดียวกันกับที่จะต้องได้รับการดูแลเป็นส่วนหนึ่งของรหัสฐานระบบ แม้ว่าจำเป็นต้องมีการใช้งานในฐานะส่วนหนึ่งของเชลล์สคริปต์สามารถใช้คำสั่งด้านบนได้ดังนั้นจึงเกี่ยวข้องกับคำถามการสคริปต์เชลล์
Daniel Bradley

นี่ไม่ใช่ "สร้างขึ้น"; หากมีการติดตั้งตามค่าเริ่มต้นใน distro บางคำตอบของคุณควรระบุ distro (และควรมีตัวชี้สำหรับที่จะดาวน์โหลดหากคุณไม่ได้เป็นหนึ่งในนั้น)
tripleee

ดูเหมือนว่าเป็นยูทิลิตี้ Ubuntu แต่มันเป็นตัวเลือกแม้ใน Ubuntu manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee

น่าสังเกต: ยูทิลิตี run-one ทำตามที่ชื่อของมันบอกไว้ - คุณสามารถเรียกใช้หนึ่งอินสแตนซ์ของคำสั่งใด ๆ ที่รันด้วย run-one-nnnnn คำตอบอื่น ๆ ที่นี่เป็นผู้ไม่เชื่อเรื่องพระเจ้าปฏิบัติการมากขึ้น - พวกเขาไม่สนใจเนื้อหาของคำสั่งเลย
David Kohen

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ยังคงเป็นantipattern - คุณต้องการawk "/$INSTALLATION/ { print \$1 }"ทำให้ conflate ไร้ประโยชน์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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.