Làm cách nào để viết tập lệnh bash để khởi động lại một tiến trình nếu nó chết?


226

Tôi có một kịch bản python sẽ kiểm tra hàng đợi và thực hiện một hành động trên mỗi mục:

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

Làm cách nào để tôi viết một tập lệnh bash sẽ kiểm tra xem nó có đang chạy hay không, và nếu không, hãy khởi động nó. Gần như mã giả sau đây (hoặc có lẽ nó nên làm gì đó như thế ps | grepnào?):

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

run checkqueue.py
write processid to processidfile

Tôi sẽ gọi nó từ một crontab:

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

4
Chỉ cần thêm điều này cho năm 2017. Sử dụng giám sát. crontab không có nghĩa là để làm loại nhiệm vụ này. Một kịch bản bash là khủng khiếp khi phát ra lỗi thực sự. stackoverflow.com/questions/9301494/ Lời
mootmoot

Làm thế nào về việc sử dụng inittab và hồi sinh thay vì các giải pháp phi hệ thống khác? Xem superuser.com/a/507835/116705
Lars Nordin

Câu trả lời:


635

Tránh các tệp PID, crons hoặc bất cứ thứ gì khác cố gắng đánh giá các quá trình không phải là con của họ.

Có một lý do rất chính đáng tại sao trong UNIX, bạn CHỈ có thể đợi con bạn. Bất kỳ phương pháp nào (phân tích cú pháp ps, pgrep, lưu trữ một PID, ...) cố gắng làm việc xung quanh đó là thiếu sót và có lỗ hổng trong đó. Chỉ cần nói không .

Thay vào đó, bạn cần quá trình theo dõi quá trình của bạn để trở thành cha mẹ của quá trình. Điều đó có nghĩa là gì? Nó có nghĩa là chỉ có quá trình bắt đầu quá trình của bạn có thể chờ đợi nó kết thúc một cách đáng tin cậy. Trong bash, điều này là hoàn toàn tầm thường.

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

Đoạn mã bash trên chạy myservertrong một untilvòng lặp. Dòng đầu tiên bắt đầu myservervà chờ cho nó kết thúc. Khi nó kết thúc, untilkiểm tra trạng thái thoát của nó. Nếu trạng thái thoát là 0, nó có nghĩa là nó đã kết thúc một cách duyên dáng (có nghĩa là bạn đã yêu cầu nó tắt bằng cách nào đó và nó đã thực hiện thành công). Trong trường hợp đó, chúng tôi không muốn khởi động lại nó (chúng tôi chỉ yêu cầu tắt nó!). Nếu trạng thái thoát là không 0 , untilsẽ chạy phần thân vòng lặp, nó sẽ phát ra một thông báo lỗi trên STDERR và khởi động lại vòng lặp (quay lại dòng 1) sau 1 giây .

Tại sao chúng ta đợi một giây? Bởi vì nếu có gì đó không đúng với trình tự khởi động myservervà nó gặp sự cố ngay lập tức, bạn sẽ có một vòng lặp rất nặng nề liên tục khởi động lại và sụp đổ trên tay. Việc sleep 1lấy đi sự căng thẳng từ đó.

Bây giờ tất cả những gì bạn cần làm là bắt đầu tập lệnh bash này (không đồng bộ, có thể), và nó sẽ theo dõi myservervà khởi động lại nó khi cần thiết. Nếu bạn muốn khởi động màn hình khi khởi động (làm cho máy chủ "sống sót" khởi động lại), bạn có thể lên lịch cho nó trong cron của người dùng (1) theo @rebootquy tắc. Mở quy tắc định kỳ của bạn với crontab:

crontab -e

Sau đó thêm quy tắc để bắt đầu tập lệnh màn hình của bạn:

@reboot /usr/local/bin/myservermonitor

Cách khác; nhìn vào inittab (5) và / etc / inittab. Bạn có thể thêm một dòng trong đó để myserverbắt đầu ở một mức init nhất định và được tự động trả lời.


Biên tập.

Hãy để tôi thêm một số thông tin về lý do tại sao không sử dụng các tệp PID. Trong khi chúng rất phổ biến; họ cũng rất thiếu sót và không có lý do tại sao bạn không làm đúng cách.

Xem xét điều này:

  1. Tái chế PID (giết quá trình sai):

    • /etc/init.d/foo start: bắt đầu foo, viết fooPID của/var/run/foo.pid
    • Một lúc sau: foochết bằng cách nào đó.
    • Một lát sau: bất kỳ quá trình ngẫu nhiên nào bắt đầu (gọi nó bar) đều có một PID ngẫu nhiên, hãy tưởng tượng nó đang sử foodụng PID cũ.
    • Bạn nhận thấy foođã biến mất: /etc/init.d/foo/restartđọc /var/run/foo.pid, kiểm tra xem liệu nó còn sống hay không, tìm thấy bar, nghĩ rằng nó foo, giết chết nó, bắt đầu một cái mới foo.
  2. Các tập tin PID trở nên cũ kỹ. Bạn cần logic quá phức tạp (hoặc tôi nên nói là không tầm thường) để kiểm tra xem tệp PID có bị cũ hay không, và bất kỳ logic nào như vậy lại dễ bị tổn thương 1..

  3. Điều gì xảy ra nếu bạn thậm chí không có quyền truy cập ghi hoặc đang ở trong môi trường chỉ đọc?

  4. Đó là sự quá mức vô nghĩa; xem ví dụ đơn giản của tôi ở trên là như thế nào Không cần phải phức tạp điều đó, tất cả.

Xem thêm: Các tệp PID vẫn còn thiếu sót khi thực hiện 'đúng'?

Nhân tiện; thậm chí còn tệ hơn các tệp PID đang phân tích cú pháp ps! Đừng bao giờ làm điều này.

  1. pslà rất không quan trọng. Trong khi bạn tìm thấy nó trên hầu hết mọi hệ thống UNIX; đối số của nó rất khác nhau nếu bạn muốn đầu ra không chuẩn. Và đầu ra tiêu chuẩn CHỈ dành cho tiêu dùng của con người, không phải cho phân tích cú pháp theo kịch bản!
  2. Phân tích cú pháp psdẫn đến rất nhiều tích cực sai. Lấy ps aux | grep PIDví dụ, và bây giờ hãy tưởng tượng ai đó bắt đầu một quá trình với một số ở đâu đó như là đối số xảy ra giống như PID mà bạn nhìn chằm chằm vào daemon của mình! Hãy tưởng tượng hai người bắt đầu một phiên X và bạn tham gia vào X để giết bạn. Nó chỉ là tất cả các loại xấu.

Nếu bạn không muốn tự mình quản lý quy trình; có một số hệ thống hoàn toàn tốt ngoài kia sẽ đóng vai trò giám sát các quy trình của bạn. Nhìn vào runit , ví dụ.


1
@Chas. Ownes: Tôi không nghĩ điều đó là cần thiết. Nó sẽ chỉ làm phức tạp việc thực hiện mà không có lý do chính đáng. Đơn giản luôn quan trọng hơn; và nếu nó khởi động lại thường xuyên, giấc ngủ sẽ giữ cho nó không có bất kỳ tác động xấu nào đến tài nguyên hệ thống của bạn. Dù sao cũng đã có một tin nhắn.
lhunath

2
@orschiro Không có tiêu thụ tài nguyên khi chương trình hoạt động. Nếu nó tồn tại ngay lập tức khi khởi chạy, liên tục, mức tiêu thụ tài nguyên với chế độ ngủ 1 vẫn không đáng kể.
lhunath

7
Có thể tin rằng tôi chỉ nhìn thấy câu trả lời này. Cám ơn rất nhiều!
getWeberForStackExchange

2
@ TomášZato bạn có thể thực hiện vòng lặp trên mà không cần kiểm tra mã thoát của quy trình while true; do myprocess; donenhưng lưu ý rằng hiện tại không có cách nào để dừng quá trình.
lhunath

2
@ SergeyP.akaazure Cách duy nhất để buộc phụ huynh phải giết đứa trẻ xuất cảnh trong bash là để biến đứa trẻ thành một công việc và báo hiệu nó:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath

33

Có một cái nhìn về monit ( http://mmonit.com/monit/ ). Nó xử lý bắt đầu, dừng và khởi động lại tập lệnh của bạn và có thể kiểm tra sức khỏe cộng với khởi động lại nếu cần thiết.

Hoặc làm một kịch bản đơn giản:

while true
do
/your/script
sleep 1
done

4
Monit chính xác là những gì bạn đang tìm kiếm.
Sarke

4
"Trong khi 1" không hoạt động. Bạn cần "while [1]" hoặc "while true" hoặc "while:". Xem unix.stackexchange.com/questions/367108/what-does-fter-mean
Curtis Yallop

8

Cách dễ nhất để làm điều đó là sử dụng đàn trên tập tin. Trong tập lệnh Python bạn sẽ làm

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

Trong shell bạn thực sự có thể kiểm tra nếu nó đang chạy:

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

Nhưng tất nhiên bạn không phải kiểm tra, vì nếu nó đã chạy và bạn khởi động lại nó, nó sẽ thoát với 'other instance already running'

Khi quá trình chết, tất cả các mô tả tập tin của nó được đóng lại và tất cả các khóa được tự động xóa.


điều đó có thể đơn giản hóa nó một chút bằng cách loại bỏ tập lệnh bash. Điều gì xảy ra nếu kịch bản python gặp sự cố? tập tin đã được mở khóa?
Tom

1
Khóa tệp được phát hành ngay khi ứng dụng dừng, bằng cách giết, tự nhiên hoặc bị sập.
Christian Witts

@Tom ... chính xác hơn một chút - khóa không còn hoạt động ngay khi tệp xử lý đóng. Nếu tập lệnh Python không bao giờ đóng xử lý tệp theo ý định và đảm bảo rằng nó không bị đóng tự động thông qua đối tượng tệp được thu gom rác, thì việc đóng có lẽ có nghĩa là tập lệnh đã thoát / bị giết. Điều này làm việc ngay cả cho khởi động lại và như vậy.
Charles Duffy

1
Có nhiều cách tốt hơn để sử dụng flock... trên thực tế, trang người đàn ông thể hiện rõ ràng như thế nào! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"là bash tương đương với Python của bạn và giữ khóa (vì vậy nếu sau đó bạn thực hiện một quy trình, khóa sẽ được giữ cho đến khi quá trình đó thoát ra).
Charles Duffy

Tôi đánh giá thấp bạn vì mã của bạn sai. Sử dụng flocklà cách chính xác, nhưng kịch bản của bạn sai. Lệnh duy nhất bạn cần đặt trong crontab là:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus

6

Bạn nên sử dụng monit, một công cụ unix tiêu chuẩn có thể giám sát những thứ khác nhau trên hệ thống và phản ứng tương ứng.

Từ các tài liệu: http://mmonit.com/monit/documentation/monit.html#pid_testing

kiểm tra quá trình checkqueue.py với pidfile /var/run/checkqueue.pid
       nếu thay đổi pid thì thực hiện "checkqueue_restart.sh"

Bạn cũng có thể cấu hình monit để gửi email cho bạn khi nó thực hiện khởi động lại.


2
Monit là một công cụ tuyệt vời, nhưng nó không phải là tiêu chuẩn theo nghĩa chính thức được chỉ định trong POSIX hoặc SUSV.
Charles Duffy

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

Thật tuyệt, đó là một số mã giả của tôi khá tốt. hai qns: 1) làm cách nào để tạo ra PIDFILE? 2) psgrep là gì? Nó không có trên máy chủ Ubuntu.
Tom

ps grep chỉ là một ứng dụng nhỏ hoạt động tương tự ps ax|grep .... Bạn chỉ có thể cài đặt nó hoặc viết một hàm cho điều đó: function psgrep () {ps ax | grep -v grep | grep -q "$ 1"}
soulmerge

Chỉ cần lưu ý rằng tôi đã không trả lời câu hỏi đầu tiên của bạn.
soulmerge

7
Trên máy chủ thực sự bận rộn, có thể PID sẽ được tái chế trước khi bạn kiểm tra.
vartec

2

Tôi không chắc nó có khả năng di động như thế nào trên các hệ điều hành, nhưng bạn có thể kiểm tra xem hệ thống của bạn có chứa lệnh 'run-one' hay không, tức là "man run-one". Cụ thể, nhóm lệnh này bao gồm 'chạy một lần liên tục', dường như chính xác là những gì cần thiết.

Từ trang nam:

chạy một lần liên tục [ARGS]

Lưu ý: rõ ràng điều này có thể được gọi từ trong tập lệnh của bạn, nhưng nó cũng loại bỏ sự cần thiết phải có một tập lệnh.


Điều này có cung cấp bất kỳ lợi thế hơn câu trả lời được chấp nhận?
tripleee

1
Có, tôi nghĩ nên sử dụng lệnh tích hợp hơn là viết một tập lệnh shell thực hiện điều tương tự sẽ phải được duy trì như một phần của cơ sở mã hệ thống. Ngay cả khi chức năng được yêu cầu như một phần của tập lệnh shell, lệnh trên cũng có thể được sử dụng để nó có liên quan đến câu hỏi shell script.
Daniel Bradley

Đây không phải là "được xây dựng"; nếu nó được cài đặt theo mặc định trên một số bản phân phối, câu trả lời của bạn có lẽ nên chỉ định bản phân phối (và lý tưởng bao gồm một con trỏ để tải xuống ở đâu nếu bạn không phải là một trong số chúng).
tripleee

Có vẻ như đó là một tiện ích Ubuntu; nhưng nó là tùy chọn ngay cả trên Ubuntu. manpages.ubfox.com/manpages/bionic/man1/run-one.1.html
tripleee 27/10/18

Đáng chú ý: các tiện ích run-one thực hiện chính xác tên của chúng nói - bạn chỉ có thể chạy một phiên bản của bất kỳ lệnh nào được chạy với run-one-nnnnn. Các câu trả lời khác ở đây là bất khả tri thực thi hơn - thay vì không quan tâm đến nội dung của lệnh.
David Kohen

1

Tôi đã sử dụng đoạn script sau rất thành công trên nhiều máy chủ:

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

ghi chú:

  • Đó là tìm kiếm một quá trình java, vì vậy tôi có thể sử dụng jps, điều này phù hợp hơn nhiều so với các bản phân phối so với ps
  • $INSTALLATION chứa đủ đường dẫn quy trình mà nó hoàn toàn không rõ ràng
  • Sử dụng giấc ngủ trong khi chờ quá trình chết, tránh tài nguyên ăn cắp :)

Kịch bản này thực sự được sử dụng để tắt một phiên bản tomcat đang chạy mà tôi muốn tắt (và chờ) tại dòng lệnh, vì vậy khởi chạy nó như một tiến trình con đơn giản không phải là một lựa chọn cho tôi.


1
grep | awkvẫn là một antipattern - bạn muốn awk "/$INSTALLATION/ { print \$1 }"để conflate vô dụng grepvào kịch bản AWK, có thể tìm thấy các dòng bằng biểu thức chính quy chính nó rất tốt, cảm ơn bạn rất nhiều.
tripleee

0

Tôi sử dụng điều này cho quá trình npm của tôi

#!/bin/bash
for (( ; ; ))
do
date +"%T"
echo Start Process
cd /toFolder
sudo process
date +"%T"
echo Crash
sleep 1
done
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.