Como escrevo um script bash para reiniciar um processo se ele morrer?

226

Eu tenho um script python que verificará uma fila e executará uma ação em cada item:

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

Como escrevo um script bash que verifica se está sendo executado e, se não estiver, inicie-o. Aproximadamente o seguinte pseudo-código (ou talvez deva fazer algo assim ps | grep?):

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

run checkqueue.py
write processid to processidfile

Vou chamar isso de um crontab:

# crontab
*/5 * * * * /path/to/keepalivescript.sh
Tom
fonte
4
Apenas para adicionar isso para 2017. Use supervisord. O crontab não pretende fazer esse tipo de tarefa. Um script bash é terrível para emitir o erro real. stackoverflow.com/questions/9301494/…
mootmoot 7/17/17 /
Que tal usar inittab e respawn em vez de outras soluções que não são do sistema? Veja superuser.com/a/507835/116705
Lars Nordin

Respostas:

635

Evite arquivos PID, crons ou qualquer outra coisa que tente avaliar processos que não são filhos deles.

Há uma boa razão pela qual, no UNIX, você só pode esperar seus filhos. Qualquer método (ps parsing, pgrep, armazenando um PID, ...) que tente solucionar o problema é defeituoso e possui buracos escancarados. Apenas diga não .

Em vez disso, você precisa do processo que monitora seu processo para ser o pai do processo. O que isto significa? Isso significa que apenas o processo que inicia o processo pode esperar com segurança que ele termine. No bash, isso é absolutamente trivial.

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

A parte acima do código do bash é executada myserverem um untilloop. A primeira linha inicia myservere aguarda o término. Quando termina, untilverifica seu status de saída. Se o status de saída for 0, significa que ele terminou normalmente (o que significa que você solicitou o desligamento de alguma forma e o fez com êxito). Nesse caso, não queremos reiniciá-lo (apenas pedimos para desligar!). Se o status de saída não for 0, untilexecutará o corpo do loop, que emite uma mensagem de erro no STDERR e reinicia o loop (de volta à linha 1) após 1 segundo .

Por que esperamos um segundo? Porque se algo estiver errado com a sequência de inicialização myservere ela travar imediatamente, você terá um loop muito intenso de reinicializações e travamentos constantes em suas mãos. O sleep 1tira a tensão disso.

Agora tudo o que você precisa fazer é iniciar esse script bash (de forma assíncrona, provavelmente), e ele será monitorado myservere reiniciado conforme necessário. Se você deseja iniciar o monitor na inicialização (fazendo o servidor "sobreviver" à reinicialização), é possível agendá-lo no cron (1) do usuário com uma @rebootregra. Abra suas regras cron com crontab:

crontab -e

Em seguida, adicione uma regra para iniciar o script do seu monitor:

@reboot /usr/local/bin/myservermonitor

Alternativamente; veja inittab (5) e / etc / inittab. Você pode adicionar uma linha para myserveriniciar em um determinado nível de inicialização e ser reaparecida automaticamente.


Editar.

Deixe-me adicionar algumas informações sobre por que não usar arquivos PID. Enquanto eles são muito populares; eles também são muito falhos e não há razão para que você não faça da maneira correta.

Considere isto:

  1. Reciclagem de PID (acabando com o processo errado):

    • /etc/init.d/foo start: inicie foo, escreva fooo PID para/var/run/foo.pid
    • Um tempo depois: foomorre de alguma forma.
    • Um pouco mais tarde: qualquer processo aleatório que inicie (chame bar) leva um PID aleatório, imagine-o usando fooo antigo PID.
    • Você percebe que se foofoi: /etc/init.d/foo/restart/var/run/foo.pid, checa para ver se ainda está vivo, acha bar, acha que está foo, mata, inicia um novo foo.
  2. Os arquivos PID ficam obsoletos. Você precisa de lógica excessivamente complicada (ou devo dizer, não trivial) para verificar se o arquivo PID está obsoleto e se essa lógica está novamente vulnerável 1..

  3. E se você nem tiver acesso de gravação ou estiver em um ambiente somente leitura?

  4. É supercomplicação inútil; veja como é simples o meu exemplo acima. Não há necessidade de complicar isso.

Veja também: Os arquivos PID ainda são defeituosos ao fazê-lo 'certo'?

A propósito; ainda pior do que os arquivos PID está analisando ps! Nunca faça isso.

  1. psé muito portável. Enquanto você o encontra em quase todos os sistemas UNIX; seus argumentos variam muito se você deseja saída fora do padrão. E a saída padrão é APENAS para consumo humano, não para análise por script!
  2. A análise psleva a muitos falsos positivos. Pegue o ps aux | grep PIDexemplo e agora imagine alguém iniciando um processo com um número em algum lugar, como argumento que é o mesmo que o PID com o qual você olhou seu daemon! Imagine duas pessoas iniciando uma sessão do X e você esperando que o X mate a sua. É apenas todo tipo de coisa ruim.

Se você não deseja gerenciar o processo sozinho; existem alguns sistemas perfeitamente bons por aí que atuam como monitor para seus processos. Veja runit , por exemplo.

lhunath
fonte
1
@Chas. Ownes: Eu não acho que seja necessário. Isso apenas complicaria a implementação sem uma boa razão. A simplicidade é sempre mais importante; e, se reiniciar com frequência, o sono impedirá que ele tenha um impacto ruim nos recursos do sistema. Já existe uma mensagem de qualquer maneira.
31710 lhunath
2
@orschiro Não há consumo de recursos quando o programa se comporta. Se ele existir imediatamente no lançamento, continuamente, o consumo de recursos com um sono 1 ainda será totalmente desprezível.
Lhunath 29/11
7
Acredito que estou apenas vendo esta resposta. Muito obrigado!
getWeberForStackExchange
2
@ TomášZato, você pode fazer o loop acima sem testar o código de saída do processo, while true; do myprocess; donemas observe que agora não há como parar o processo.
lhunath
2
@ SergeyP.akaazure A única maneira de forçar o pai a matar a criança na saída em bash é transformar a criança em um emprego e sinalizá-lo:trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
lhunath
33

Dê uma olhada no monit ( http://mmonit.com/monit/ ). Ele lida com iniciar, parar e reiniciar seu script e pode fazer verificações de saúde e reiniciar, se necessário.

Ou faça um script simples:

while true
do
/your/script
sleep 1
done
Bernd
fonte
4
Monit é exatamente o que você está procurando.
Sarke 11/09/15
4
"while 1" não funciona. Você precisa "while [1]" ou "while true" ou "while:". Veja unix.stackexchange.com/questions/367108/what-does- while
Curtis Yallop
8

A maneira mais fácil de fazer isso é usando o rebanho em arquivo. No script Python você faria

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

No shell, você pode realmente testar se está em execução:

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

Mas é claro que você não precisa testar, porque se ele já estiver em execução e você o reiniciar, ele sairá com 'other instance already running'

Quando o processo morre, todos os seus descritores de arquivo são fechados e todos os bloqueios são removidos automaticamente.

vartec
fonte
isso poderia simplificá-lo um pouco, removendo o script bash. o que acontece se o script python falhar? o arquivo está desbloqueado?
Tom
1
O bloqueio de arquivo é liberado assim que o aplicativo para, matando, naturalmente ou travando.
Christian Witts
@ Tom ... para ser um pouco mais preciso - o bloqueio não está mais ativo assim que o identificador do arquivo é fechado. Se o script Python nunca fecha o identificador do arquivo por intenção e garante que ele não seja fechado automaticamente através do objeto de arquivo que está sendo coletado pelo lixo, então provavelmente será necessário fechar o script. Isso funciona mesmo para reinicializações e afins.
Charles Duffy
1
Existem maneiras muito melhores de usar flock... de fato, a página de manual demonstra explicitamente como! exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"é o equivalente do bash ao seu Python e deixa o bloqueio retido (portanto, se você executar um processo, o bloqueio permanecerá retido até que o processo termine).
Charles Duffy
Eu diminuí seu voto porque seu código está errado. Usar flocké a maneira correta, mas seus scripts estão errados. O único comando que você precisa definir no crontab é:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus
6

Você deve usar o monit, uma ferramenta unix padrão que pode monitorar coisas diferentes no sistema e reagir de acordo.

Nos documentos: http://mmonit.com/monit/documentation/monit.html#pid_testing

verifique o processo checkqueue.py com pidfile /var/run/checkqueue.pid
       se alterado pid, então exec "checkqueue_restart.sh"

Você também pode configurar o monit para lhe enviar um email quando ele reiniciar.

clofresh
fonte
2
Monit é uma ótima ferramenta, mas é não padrão no sentido formal de ser especificado em qualquer POSIX ou SUSV.
Charles Duffy
5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi
soulmerge
fonte
legal, isso está desenvolvendo muito bem meu pseudo código. dois qns: 1) como faço para gerar PIDFILE? 2) o que é psgrep? não está no servidor ubuntu.
Tom
O ps grep é apenas um aplicativo pequeno que faz o mesmo que ps ax|grep .... Você pode simplesmente instalá-lo ou escrever uma função para isso: psgrep function () {ps ax | grep -v grep | grep -q "$ 1"}
soulmerge
Só notei que eu não tinha respondido sua primeira pergunta.
#
7
Em um servidor muito ocupado, é possível que o PID seja reciclado antes da verificação.
vartec 30/03/09
2

Não sei ao certo como é portátil em todos os sistemas operacionais, mas você pode verificar se o seu sistema contém o comando 'run-one', ou seja, "man run-one". Especificamente, esse conjunto de comandos inclui 'executar um constantemente', que parece ser exatamente o que é necessário.

Na página do manual:

executar um-constantemente COMANDO [ARGS]

Nota: obviamente isso pode ser chamado de dentro do seu script, mas também elimina a necessidade de ter um script.

Daniel Bradley
fonte
Isso oferece alguma vantagem sobre a resposta aceita?
Tripleee 26/10/18
1
Sim, acho que é preferível usar um comando interno do que escrever um script de shell que faça a mesma coisa que precisará ser mantida como parte da base de código do sistema. Mesmo que a funcionalidade seja necessária como parte de um script de shell, o comando acima também pode ser usado, por isso é relevante para uma pergunta de script de shell.
27518 Daniel Bradley
Isso não é "incorporado"; se estiver instalado por padrão em alguma distribuição, sua resposta provavelmente deverá especificar a distribuição (e, idealmente, incluir um ponteiro para onde fazer o download, se o seu não for um deles).
tripleee
Parece que é um utilitário Ubuntu; mas é opcional mesmo no Ubuntu. manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
tripleee 27/10/18
Vale ressaltar: os utilitários run-one fazem exatamente o que o nome diz - você pode executar apenas uma instância de qualquer comando executado com run-one-nnnnn. Outras respostas aqui são mais agnósticas executáveis ​​- elas não se importam com o conteúdo do comando.
David Kohen
1

Eu usei o seguinte script com grande sucesso em vários servidores:

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

notas:

  • Ele está procurando um processo java, para que eu possa usar jps, isso é muito mais consistente entre distribuições do que ps
  • $INSTALLATION contém o suficiente do caminho do processo, é totalmente inequívoco
  • Durma enquanto aguarda a morte do processo, evite consumir recursos :)

Na verdade, esse script é usado para desligar uma instância em execução do tomcat, que eu quero desligar (e esperar) na linha de comando; portanto, iniciá-lo como um processo filho simplesmente não é uma opção para mim.

Kevin Wright
fonte
1
grep | awkainda é um antipadrão - você deseja awk "/$INSTALLATION/ { print \$1 }"combinar o inútil grepcom o script Awk, que pode encontrar linhas pela própria expressão regular muito bem, muito obrigado.
Tripleee
0

Eu uso isso para o meu processo npm

#!/bin/bash
for (( ; ; ))
do
date +"%T"
echo Start Process
cd /toFolder
sudo process
date +"%T"
echo Crash
sleep 1
done
BitDEVil2K16
fonte