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 ']'

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.


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


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.


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

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.