Reiniciando o serviço systemd em falha de dependência

26

Qual é a abordagem correta para lidar com a reinicialização de um serviço, caso uma de suas dependências falhe na inicialização (mas seja bem-sucedida após a nova tentativa).

Aqui está uma reprodução artificial para tornar o problema mais claro.

a.service (simula falha na primeira tentativa e sucesso na segunda tentativa)

[Unit]
Description=A

[Service]
ExecStartPre=/bin/sh -x -c "[ -f /tmp/success ] || (touch /tmp/success && sleep 10)"
ExecStart=/bin/true
TimeoutStartSec=5
Restart=on-failure
RestartSec=5
RemainAfterExit=yes

b.service (sucede trivialmente após o início de A)

[Unit]
Description=B
After=a.service
Requires=a.service

[Service]
ExecStart=/bin/true
RemainAfterExit=yes
Restart=on-failure
RestartSec=5

Vamos começar b:

# systemctl start b
A dependency job for b.service failed. See 'journalctl -xe' for details.

Registros:

Jun 30 21:34:54 debug systemd[1]: Starting A...
Jun 30 21:34:54 debug sh[1308]: + '[' -f /tmp/success ']'
Jun 30 21:34:54 debug sh[1308]: + touch /tmp/success
Jun 30 21:34:54 debug sh[1308]: + sleep 10
Jun 30 21:34:59 debug systemd[1]: a.service start-pre operation timed out. Terminating.
Jun 30 21:34:59 debug systemd[1]: Failed to start A.
Jun 30 21:34:59 debug systemd[1]: Dependency failed for B.
Jun 30 21:34:59 debug systemd[1]: Job b.service/start failed with result 'dependency'.
Jun 30 21:34:59 debug systemd[1]: Unit a.service entered failed state.
Jun 30 21:34:59 debug systemd[1]: a.service failed.
Jun 30 21:35:04 debug systemd[1]: a.service holdoff time over, scheduling restart.
Jun 30 21:35:04 debug systemd[1]: Starting A...
Jun 30 21:35:04 debug systemd[1]: Started A.
Jun 30 21:35:04 debug sh[1314]: + '[' -f /tmp/success ']'

A foi iniciado com êxito, mas B é deixado em um estado com falha e não tentará novamente.

EDITAR

Adicionei o seguinte aos dois serviços e agora B inicia com êxito quando A é iniciado, mas não consigo explicar o porquê.

[Install]
WantedBy=multi-user.target

Por que isso afetaria o relacionamento entre A e B?

EDIT2

Acima "correção" não funciona no systemd 220.

logs de depuração systemd 219

systemd219 systemd[1]: Trying to enqueue job b.service/start/replace
systemd219 systemd[1]: Installed new job b.service/start as 3454
systemd219 systemd[1]: Installed new job a.service/start as 3455
systemd219 systemd[1]: Enqueued job b.service/start as 3454
systemd219 systemd[1]: About to execute: /bin/sh -x -c '[ -f /tmp/success ] || (touch oldcoreos
systemd219 systemd[1]: Forked /bin/sh as 1502
systemd219 systemd[1]: a.service changed dead -> start-pre
systemd219 systemd[1]: Starting A...
systemd219 systemd[1502]: Executing: /bin/sh -x -c '[ -f /tmp/success ] || (touch /tmpoldcoreos
systemd219 sh[1502]: + '[' -f /tmp/success ']'
systemd219 sh[1502]: + touch /tmp/success
systemd219 sh[1502]: + sleep 10
systemd219 systemd[1]: a.service start-pre operation timed out. Terminating.
systemd219 systemd[1]: a.service changed start-pre -> final-sigterm
systemd219 systemd[1]: Child 1502 belongs to a.service
systemd219 systemd[1]: a.service: control process exited, code=killed status=15
systemd219 systemd[1]: a.service got final SIGCHLD for state final-sigterm
systemd219 systemd[1]: a.service changed final-sigterm -> failed
systemd219 systemd[1]: Job a.service/start finished, result=failed
systemd219 systemd[1]: Failed to start A.
systemd219 systemd[1]: Job b.service/start finished, result=dependency
systemd219 systemd[1]: Dependency failed for B.
systemd219 systemd[1]: Job b.service/start failed with result 'dependency'.
systemd219 systemd[1]: Unit a.service entered failed state.
systemd219 systemd[1]: a.service failed.
systemd219 systemd[1]: a.service changed failed -> auto-restart
systemd219 systemd[1]: a.service: cgroup is empty
systemd219 systemd[1]: a.service: cgroup is empty
systemd219 systemd[1]: a.service holdoff time over, scheduling restart.
systemd219 systemd[1]: Trying to enqueue job a.service/restart/fail
systemd219 systemd[1]: Installed new job a.service/restart as 3718
systemd219 systemd[1]: Installed new job b.service/restart as 3803
systemd219 systemd[1]: Enqueued job a.service/restart as 3718
systemd219 systemd[1]: a.service scheduled restart job.
systemd219 systemd[1]: Job b.service/restart finished, result=done
systemd219 systemd[1]: Converting job b.service/restart -> b.service/start
systemd219 systemd[1]: a.service changed auto-restart -> dead
systemd219 systemd[1]: Job a.service/restart finished, result=done
systemd219 systemd[1]: Converting job a.service/restart -> a.service/start
systemd219 systemd[1]: About to execute: /bin/sh -x -c '[ -f /tmp/success ] || (touch oldcoreos
systemd219 systemd[1]: Forked /bin/sh as 1558
systemd219 systemd[1]: a.service changed dead -> start-pre
systemd219 systemd[1]: Starting A...
systemd219 systemd[1]: Child 1558 belongs to a.service
systemd219 systemd[1]: a.service: control process exited, code=exited status=0
systemd219 systemd[1]: a.service got final SIGCHLD for state start-pre
systemd219 systemd[1]: About to execute: /bin/true
systemd219 systemd[1]: Forked /bin/true as 1561
systemd219 systemd[1]: a.service changed start-pre -> running
systemd219 systemd[1]: Job a.service/start finished, result=done
systemd219 systemd[1]: Started A.
systemd219 systemd[1]: Child 1561 belongs to a.service
systemd219 systemd[1]: a.service: main process exited, code=exited, status=0/SUCCESS
systemd219 systemd[1]: a.service changed running -> exited
systemd219 systemd[1]: a.service: cgroup is empty
systemd219 systemd[1]: About to execute: /bin/true
systemd219 systemd[1]: Forked /bin/true as 1563
systemd219 systemd[1]: b.service changed dead -> running
systemd219 systemd[1]: Job b.service/start finished, result=done
systemd219 systemd[1]: Started B.
systemd219 systemd[1]: Starting B...
systemd219 systemd[1]: Child 1563 belongs to b.service
systemd219 systemd[1]: b.service: main process exited, code=exited, status=0/SUCCESS
systemd219 systemd[1]: b.service changed running -> exited
systemd219 systemd[1]: b.service: cgroup is empty
systemd219 sh[1558]: + '[' -f /tmp/success ']'

logs de depuração systemd 220

systemd220 systemd[1]: b.service: Trying to enqueue job b.service/start/replace
systemd220 systemd[1]: a.service: Installed new job a.service/start as 4846
systemd220 systemd[1]: b.service: Installed new job b.service/start as 4761
systemd220 systemd[1]: b.service: Enqueued job b.service/start as 4761
systemd220 systemd[1]: a.service: About to execute: /bin/sh -x -c '[ -f /tmp/success ] || (touch /tmp/success && sleep 10)'
systemd220 systemd[1]: a.service: Forked /bin/sh as 2032
systemd220 systemd[1]: a.service: Changed dead -> start-pre
systemd220 systemd[1]: Starting A...
systemd220 systemd[2032]: a.service: Executing: /bin/sh -x -c '[ -f /tmp/success ] || (touch /tmp/success && sleep 10)'
systemd220 sh[2032]: + '[' -f /tmp/success ']'
systemd220 sh[2032]: + touch /tmp/success
systemd220 sh[2032]: + sleep 10
systemd220 systemd[1]: a.service: Start-pre operation timed out. Terminating.
systemd220 systemd[1]: a.service: Changed start-pre -> final-sigterm
systemd220 systemd[1]: a.service: Child 2032 belongs to a.service
systemd220 systemd[1]: a.service: Control process exited, code=killed status=15
systemd220 systemd[1]: a.service: Got final SIGCHLD for state final-sigterm.
systemd220 systemd[1]: a.service: Changed final-sigterm -> failed
systemd220 systemd[1]: a.service: Job a.service/start finished, result=failed
systemd220 systemd[1]: Failed to start A.
systemd220 systemd[1]: b.service: Job b.service/start finished, result=dependency
systemd220 systemd[1]: Dependency failed for B.
systemd220 systemd[1]: b.service: Job b.service/start failed with result 'dependency'.
systemd220 systemd[1]: a.service: Unit entered failed state.
systemd220 systemd[1]: a.service: Failed with result 'timeout'.
systemd220 systemd[1]: a.service: Changed failed -> auto-restart
systemd220 systemd[1]: a.service: cgroup is empty
systemd220 systemd[1]: a.service: Failed to send unit change signal for a.service: Transport endpoint is not connected
systemd220 systemd[1]: a.service: Service hold-off time over, scheduling restart.
systemd220 systemd[1]: a.service: Trying to enqueue job a.service/restart/fail
systemd220 systemd[1]: a.service: Installed new job a.service/restart as 5190
systemd220 systemd[1]: a.service: Enqueued job a.service/restart as 5190
systemd220 systemd[1]: a.service: Scheduled restart job.
systemd220 systemd[1]: a.service: Changed auto-restart -> dead
systemd220 systemd[1]: a.service: Job a.service/restart finished, result=done
systemd220 systemd[1]: a.service: Converting job a.service/restart -> a.service/start
systemd220 systemd[1]: a.service: About to execute: /bin/sh -x -c '[ -f /tmp/success ] || (touch /tmp/success && sleep 10)'
systemd220 systemd[1]: a.service: Forked /bin/sh as 2132
systemd220 systemd[1]: a.service: Changed dead -> start-pre
systemd220 systemd[1]: Starting A...
systemd220 systemd[1]: a.service: Child 2132 belongs to a.service
systemd220 systemd[1]: a.service: Control process exited, code=exited status=0
systemd220 systemd[1]: a.service: Got final SIGCHLD for state start-pre.
systemd220 systemd[1]: a.service: About to execute: /bin/true
systemd220 systemd[1]: a.service: Forked /bin/true as 2136
systemd220 systemd[1]: a.service: Changed start-pre -> running
systemd220 systemd[1]: a.service: Job a.service/start finished, result=done
systemd220 systemd[1]: Started A.
systemd220 systemd[1]: a.service: Child 2136 belongs to a.service
systemd220 systemd[1]: a.service: Main process exited, code=exited, status=0/SUCCESS
systemd220 systemd[1]: a.service: Changed running -> exited
systemd220 systemd[1]: a.service: cgroup is empty
systemd220 systemd[1]: a.service: cgroup is empty
systemd220 systemd[1]: a.service: cgroup is empty
systemd220 systemd[1]: a.service: cgroup is empty
systemd220 sh[2132]: + '[' -f /tmp/success ']'
Vadim
fonte
1
Há um problema do systemd upstream que rastreia isso: github.com/systemd/systemd/issues/1312
JKnight 30/16

Respostas:

31

Tentarei resumir minhas descobertas para esse problema, caso alguém se depare com isso, pois as informações sobre esse assunto são escassas.

  • Restart=on-failure aplica-se apenas a falhas de processo (não se aplica a falhas devido a falhas de dependência)
  • O fato de as unidades com falha dependentes serem reiniciadas sob certas condições quando uma dependência é reiniciada com êxito era um erro no systemd <220: http://lists.freedesktop.org/archives/systemd-devel/2015-July/033513.html
  • Se houver uma pequena chance de uma dependência falhar no início e você se preocupa com a resiliência, não use Before/ Aftere execute uma verificação em algum artefato que a dependência produz

por exemplo

ExecStartPre=/usr/bin/test -f /some/thing
Restart=on-failure
RestartSec=5s

Você poderia até usar systemctl is-active <dependecy>.

Muito hacky, mas não encontrei nenhuma opção melhor.

Na minha opinião, não ter como lidar com falhas de dependência é uma falha no systemd.

Vadim
fonte
Sim, não para não mencionar ter uma nova tentativa para pontos de montagem que Leonard poetring não querem implementar: github.com/systemd/systemd/issues/4468
Hvisage
0

Parece que o tipo de coisa que pode ser roteirizada e colocada em um cronjob com bastante facilidade. A lógica básica seria algo como isto

  1. verifique se os serviços aeb e as dependências estão em execução / em um estado válido. Você saberá a melhor maneira de verificar se tudo está funcionando corretamente
  2. Se tudo estiver funcionando corretamente, não faça nada ou faça logon de que tudo está funcionando. O registro tem a vantagem de permitir que você procure a entrada anterior.
  3. Se algo estiver com defeito, reinicie os serviços e retorne ao início do script em que ocorre a verificação do status do serviço e da dependência. O salto deve ocorrer apenas se você estiver confiante no reinício dos serviços e as dependências tiverem uma alta probabilidade de funcionar, caso contrário, existe o potencial para um loop.
  4. Deixe o cron executar o script novamente daqui a pouco

Depois que o script é definido, o cron é um bom lugar para testá-lo, se o cron for ineficiente, o script seria um bom ponto de partida para tentar gravar um serviço de sistema de baixo nível que possa verificar o status de alguns outros serviços e reiniciá-los conforme necessário. Dependendo da quantidade de esforço que você deseja investir, o script provavelmente pode até ser configurado para enviá-lo por e-mail com base nos resultados (a menos que os serviços em questão sejam os serviços de rede).

Matt
fonte
Este cronjob de coisas devem ser, em vez feito no gerenciador de processo / serviço, caso contrário, você estará voltando para métodos SVR4, que Systemd tentativas de não fazer ...
Hvisage
0

Aftere Beforedefina apenas a ordem em que os serviços serão iniciados, seus arquivos de serviço dizem "Se A e B serão iniciados, A deverá ser iniciado antes de B".

Requires significa que, se esse serviço for iniciado, esse serviço deverá ser iniciado primeiro, no seu exemplo "Se B for iniciado e A não estiver em execução, inicie A"

Quando você adiciona o WantedBy=multi-user.targetque está dizendo ao sistema que os serviços devem ser iniciados quando o sistema está sendo inicializado multi-user.target, presumivelmente isso significa que, uma vez adicionado, você estava deixando o sistema iniciar os serviços em vez de iniciá-los manualmente?

Não sei por que isso não funciona na versão 220, pode valer a pena tentar 222. Vou desenterrar uma VM e experimentar seus serviços quando tiver uma chance.

Michael Shaw
fonte
1
Eu perguntei no systemd-devel, o fato de estar funcionando no 219 era um bug. O comportamento pretendido é que as dependências com falha NÃO são reiniciadas.
Vadim
0

Passei dias nisso, tentando fazê-lo funcionar da maneira "systemd", mas desisti de frustração e escrevi um script de invólucro para gerenciar dependências e falhas. Cada serviço filho é um serviço normal do sistema, sem "Requer" ou "PartOf" ou qualquer gancho para outros serviços.

Meu arquivo de serviço de nível superior fica assim:

[Service]
Type=simple
Environment=REQUIRES=foo.service bar.service
ExecStartPre=/usr/bin/systemctl start $REQUIRES
ExecStart=@PREFIX@/bin/top-service.sh $REQUIRES
ExecStop=/usr/bin/systemctl      stop $REQUIRES

Por enquanto, tudo bem. O top.servicearquivo controla foo.servicee bar.service. A partida topcomeça fooe bar, e a parada toppara fooe bar. O ingrediente final é o meu top-service.shscript que monitora os serviços quanto a falhas:

#!/bin/bash

# This monitors REQUIRES services. If any service stops, all of the services are stopped and this script ends.

REQUIRES="$@"

if [ "$REQUIRES" == "" ]
then
  echo "ERROR: no services listed"
  exit 1
fi

echo "INFO: watching services: ${REQUIRES}"

end=0
while [[ $end == 0 ]]
do
  s=$(systemctl is-active ${REQUIRES} )
  if echo $s | egrep '^(active ?)+$' > /dev/null
  then
    # $s has embedded newlines, but echo $s seems to get rid of them, while echo "$s" keeps them.
    # echo INFO: All active, $s
    end=0
  else
    echo "WARN: ${REQUIRES}"
    echo WARN: $s
  fi

  if [[ $s == *"failed"* ]] || [[ $s == *"unknown"* ]]
  then
    echo "WARN: At least one service is failed or unknown, ending service"
    end=1
  else
    sleep 1
  fi
done

echo "INFO: done watching services, stopping: ${REQUIRES}"
systemctl stop ${REQUIRES}
echo "INFO: stopped: ${REQUIRES}"
exit 1
Mark Lakata
fonte
REQUIRES="$@"é um código inato - você está recolhendo uma matriz em uma string, descartando os limites originais entre os itens, então o argumento criado por, ie. set -- "argument one" "argument two"torna-se idêntico a set -- "argument" "one" "argument" "two". requires=( "$@" )manteria os dados originais, sendo assim expansível com segurança como systemctl is-active "${requires[@]}".
Charles Duffy
-1

Não responda a isso. Mas alguém pode precisar disso (porque, como esta página aparece na pesquisa):

deveria estar

[Service]
 Restart=always
 RestartSec=3

https://jonarcher.info/2015/08/ensure-systemd-services-restart-on-failure/

Shimon Doodkin
fonte
Por favor, leia a pergunta com mais cuidado. Não se trata de reiniciar um único serviço não íntegro, mas de como o systemd se comporta quando um serviço réu falha.
Vadim