Ponowne uruchamianie usystematyzowanej usługi w przypadku awarii zależności

26

Jakie jest właściwe podejście do obsługi ponownego uruchomienia usługi w przypadku, gdy jedna z jej zależności zawiedzie podczas uruchamiania (ale powiedzie się po ponownej próbie).

Oto wymyślone repro, aby wyjaśnić problem.

a. usługa (symuluje niepowodzenie przy pierwszej próbie i sukces przy drugiej próbie)

[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. usługa (trywialnie się udaje po uruchomieniu A)

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

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

Zacznijmy b:

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

Dzienniki:

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

Pomyślnie uruchomiono A, ale B jest w stanie awarii i nie będzie próbował ponownie.

EDYTOWAĆ

Dodałem następujące do obu usług i teraz B pomyślnie uruchamia się, gdy zaczyna A, ale nie mogę wyjaśnić, dlaczego.

[Install]
WantedBy=multi-user.target

Dlaczego miałoby to wpłynąć na relacje między A i B?

EDYCJA 2

Powyższy „fix” nie działa w systemie 220.

systemd 219 dzienników debugowania

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

systemd 220 dzienników debugowania

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
źródło
1
Występuje problem systemowy śledzący to: github.com/systemd/systemd/issues/1312
JKnight

Odpowiedzi:

31

Spróbuję podsumować moje ustalenia dotyczące tego problemu, na wypadek, gdyby ktoś się z tym spotkał, ponieważ informacje na ten temat są skąpe.

  • Restart=on-failure dotyczy tylko awarii procesu (nie dotyczy awarii spowodowanej awariami zależności)
  • Fakt, że zależne jednostki, które uległy awarii, zostaną zrestartowane pod pewnymi warunkami po pomyślnym ponownym uruchomieniu zależności, był błędem w systemd <220: http://lists.freedesktop.org/archives/systemd-devel/2015-July/033513.html
  • Jeśli istnieje choć niewielka szansa, że ​​zależność może zawieść przy starcie, a zależy Ci na odporności, nie używaj Before/, Aftera zamiast tego sprawdź artefakt, który powoduje zależność

na przykład

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

Możesz nawet użyć systemctl is-active <dependecy>.

Bardzo zuchwały, ale nie znalazłem lepszych opcji.

Moim zdaniem brak możliwości radzenia sobie z awariami zależności jest wadą systemd.

Vadim
źródło
Tak, nie wspominając o tym, że nie ponawiam próby zamontowania punktów, których Leonard poetring nie chce wdrożyć: github.com/systemd/systemd/issues/4468
Hvisage
0

Wydaje się, że jest to coś, co można łatwo napisać w skrypcie i zrobić z nim cronjob. Podstawowa logika wyglądałaby mniej więcej tak

  1. sprawdź, czy zarówno usługa aib, jak i zależności działają / są w poprawnym stanie. Poznasz najlepszy sposób sprawdzenia, czy wszystko działa poprawnie
  2. Jeśli wszystko działa poprawnie, nie rób nic lub rejestruj, że wszystko działa. Zaletą rejestrowania jest wyszukiwanie poprzedniego wpisu dziennika.
  3. Jeśli coś jest zepsute, uruchom ponownie usługi i wróć do początku skryptu, w którym następuje sprawdzenie stanu usługi i zależności. Przeskok powinien nastąpić tylko wtedy, gdy masz pewność co do ponownego uruchomienia usług, a zależności będą miały wysokie prawdopodobieństwo działania, w przeciwnym razie istnieje możliwość wystąpienia pętli.
  4. Pozwól cronowi ponownie uruchomić skrypt za chwilę

Po ustawieniu skryptu cron jest dobrym miejscem do przetestowania go, jeśli cron jest nieefektywny, skrypt byłby dobrym miejscem do rozpoczęcia próby napisania usługi systemowej niskiego poziomu, która może sprawdzić status niektórych innych usług i zrestartować je w razie potrzeby. W zależności od wysiłku, jaki chcesz zainwestować, skrypt może nawet zostać skonfigurowany do wysyłania Ci wiadomości e-mail na podstawie wyników (chyba że usługi w pytaniach to usługi sieciowe).

Matt
źródło
Ta kolizja rzeczy powinna być raczej wykonywana w menedżerze procesów / usług, w przeciwnym razie wrócisz do metod SVR4, których system usiłował nie robić ...
Hvisage
0

Afteri Beforeustaw tylko kolejność uruchamiania usług, pliki usług mówią „Jeśli A i B zostaną uruchomione, to A musi zostać uruchomione przed B”.

Requires oznacza, że ​​jeśli ta usługa ma zostać uruchomiona, usługa ta musi zostać uruchomiona jako pierwsza, w twoim przykładzie „Jeśli B jest uruchomione, a A nie działa, uruchom A”

Kiedy dodajesz WantedBy=multi-user.target, mówisz teraz systemowi, że usługi muszą zostać uruchomione podczas inicjalizacji systemu multi-user.target, prawdopodobnie oznacza to, że po dodaniu pozwoliłeś systemowi uruchomić usługi zamiast uruchamiać je ręcznie?

Nie jestem pewien, dlaczego to nie działa w wersji 220, być może warto wypróbować 222. Wykopię maszynę wirtualną i wypróbuję twoje usługi, kiedy będę miał szansę.

Michael Shaw
źródło
1
Zapytałem na systemd-devel, że fakt, że działał w 219, był błędem. Zamierzonym zachowaniem jest to, że nieudane zależności NIE zostaną ponownie uruchomione.
Vadim,
0

Spędziłem nad tym kilka dni, starając się, aby działało to w sposób „systemowy”, ale zrezygnowałem z frustracji i napisałem skrypt do zarządzania zależnościami i awariami. Każda usługa podrzędna jest normalną usługą systemową, bez „Wymaga” lub „PartOf” ani żadnych połączeń z innymi usługami.

Mój plik usługi najwyższego poziomu wygląda następująco:

[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

Jak na razie dobrze. Te top.servicekontrole plików foo.servicei bar.service. Uruchamianie toprozpoczyna się fooi bar, a zatrzymywanie topzatrzymuje się fooi bar. Ostatnim składnikiem jest mój top-service.shskrypt, który monitoruje usługi pod kątem awarii:

#!/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
źródło
REQUIRES="$@"jest z natury błędnym kodem - zwijasz tablicę w ciąg, odrzucając oryginalne granice między elementami, więc argument utworzony przez, tj. set -- "argument one" "argument two"staje się identyczny z set -- "argument" "one" "argument" "two". requires=( "$@" )zachowałoby oryginalne dane, dzięki czemu można je bezpiecznie rozszerzać jako systemctl is-active "${requires[@]}".
Charles Duffy
-1

Nie odpowiadam na to. Ale ktoś może tego potrzebować (ponieważ jak ta strona pojawia się w wyszukiwaniu):

powinno być

[Service]
 Restart=always
 RestartSec=3

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

Shimon Doodkin
źródło
Przeczytaj uważniej pytanie. Nie chodzi o ponowne uruchomienie pojedynczej niezdrowej usługi, ale o to, jak zachowuje się systemd w przypadku awarii usługi pozwanej.
Vadim,