Proby życia w Kubernetes mogą być niebezpieczne

Przyp. tłum.: Inżynier w firmie Zalando — Henning Jacobs — wielokrotnie zauważał problemy użytkowników Kubernetes zrozumieniem celu liveness (i readiness) probes oraz ich właściwym stosowaniem. Dlatego zebrał swoje myśli w tę zwięzłą notatkę, która z czasem stanie się częścią dokumentacji K8s.

Proby życia w Kubernetes mogą być niebezpieczne

Sprawdzenia stanu, znane w Kubernetes jako liveness probes (czyli dosłownie «testy żywotności» — przyp. tłum.), mogą być dość niebezpieczne. Rekomenduję unikanie ich, jeśli to możliwe: wyjątkiem są tylko przypadki, kiedy są one naprawdę niezbędne i w pełni rozumiecie specyfikę oraz konsekwencje ich użycia. W tym artykule mowa będzie o liveness i readiness sprawdzeniach oraz omówimy, kiedy znajduje się i nie należy ich stosować.

Mój kolega Sandor niedawno podzielił się na Twitterze najczęstszymi błędami, które napotyka, w tym związanymi z użyciem readiness/liveness probes:

Proby życia w Kubernetes mogą być niebezpieczne

Niewłaściwie skonfigurowana livenessProbe może pogorszyć sytuację przy dużym obciążeniu (lawinowe wyłączenie + potencjalnie długi czas uruchamiania kontenera/aplikacji) i prowadzić do innych negatywnych skutków, takich jak awarie zależności (zobacz także mój niedawny artykuł na temat ograniczenia liczby zapytań w zestawie K3s+ACME). Jeszcze gorzej, gdy liveness probe jest połączony z kontrolą stanu zależności (health check), w roli której występuje zewnętrzna baza danych: jedna awaria Bazy Danych spowoduje ponowne uruchomienie wszystkich twoich kontenerów.!

Ogólny przekaz «Nie używaj liveness probes» w tym przypadku mało pomaga, więc przyjrzyjmy się, do czego służą readiness i liveness sprawdzenia.

Uwaga: większa część poniższego testu została pierwotnie włączona do wewnętrznej dokumentacji dla programistów Zalando.

Sprawdzenia Readiness i Liveness

Kubernetes oferuje dwa ważne mechanizmy, zwane liveness probes i readiness probes. Regularnie wykonują jakieś działanie — na przykład, wysyłają zapytanie HTTP, otwierają połączenie TCP lub wykonują polecenie w kontenerze — aby potwierdzić, że aplikacja działa prawidłowo.

Kubernetes używa readiness probes, aby zrozumieć, kiedy kontener jest gotowy do przyjmowania ruchu. Pod uznawany jest za gotowy do pracy, jeśli wszystkie jego kontenery są gotowe. Jednym z zastosowań tego mechanizmu jest kontrolowanie, które pod’y są używane jako backendy dla usług Kubernetes (a szczególnie Ingress’a).

Proby żywotności pomagają Kubernetes zrozumieć, kiedy nadszedł czas na ponowne uruchomienie kontenera. Na przykład tego rodzaju weryfikacja pozwala przechwycić deadlock, gdy aplikacja "utknie" w jednym miejscu. Ponowne uruchomienie kontenera w takim stanie pomaga przesunąć aplikację z martwego punktu, mimo błędów, ale może też prowadzić do kaskadowych awarii (zobacz poniżej).

Jeśli spróbujesz wdrożyć aktualizację aplikacji, która nie przechodzi weryfikacji żywotności/readiness, jej wdrożenie zostanie wstrzymane, ponieważ Kubernetes będzie czekał na status Gotowy wszystkich podów.

Przykład

Oto przykład probe readiness, która sprawdza ścieżkę /health przez HTTP z domyślnymi ustawieniami (interval: 10 sekund, timeout: 1 sekunda, success threshold: 1, failure threshold: 3):

# часть общего описания deployment'а/стека
podTemplate:
  spec:
    containers:
    - name: my-container
      # ...
      readinessProbe:
        httpGet:
          path: /health
          port: 8080

Zalecenia

  1. Dla mikrousług z końcówką HTTP (REST itp.) zawsze definiuj probe readiness, która sprawdza, czy aplikacja (pod) jest gotowa do odbioru ruchu.
  2. Upewnij się, że probe readiness obejmuje gotowość rzeczywistego portu serwera WWW:
    • używając portów dla potrzeb administracyjnych, zwanych „admin” lub „management” (na przykład 9090), dla readinessProbe, upewnij się, że końcówka zwraca OK tylko wtedy, gdy podstawowy port HTTP (np. 8080) jest gotowy przyjmować ruch*;

      * Znam co najmniej jeden przypadek w Zalando, w którym tak się nie stało, co oznacza, że readinessProbe sprawdziła port „management”, ale serwer nie rozpoczął działania z powodu problemów z ładowaniem pamięci podręcznej.

    • przypięcie probe readiness do osobnego portu może spowodować, że obciążenie na głównym porcie nie będzie odzwierciedlane w health checku (to znaczy, że pula wątków na serwerze jest pełna, ale health check wciąż pokazuje, że wszystko jest OK).
  3. Upewnij się, że probe readiness obejmuje inicjalizację/migrację bazy danych;
    • najprostszym sposobem, aby to osiągnąć, jest zwrócenie się do serwera HTTP tylko po zakończeniu inicjalizacji (np. migracji DB z Flyway itp.); to znaczy, zamiast zmieniać status health checka, po prostu nie uruchamiaj serwera WWW przed zakończeniem migracji DB*.

      * Można także uruchamiać migracje DB z kontenerów init poza podem. Wciąż jestem zwolennikiem aplikacji samodzielnych (self-contained), czyli takich, w których kontener aplikacji wie, jak doprowadzić DB do odpowiedniego stanu bez zewnętrznej koordynacji.

  4. Użyj httpGet dla weryfikacji readiness przez typowe końcówki health checków (na przykład, /health).
  5. Poznaj parametry weryfikacji ustawione domyślnie (interval: 10s, timeout: 1s, successThreshold: 1, failureThreshold: 3):
    • domyślne parametry oznaczają, że pod stanie się niegotowy za około 30 sekund (3 nieudane kontrole dostępności).
  6. Użyj osobnego portu dla „admin” lub „zarządzania”, jeśli stos technologiczny (na przykład Java/Spring) na to pozwala, aby oddzielić zarządzanie „zdrowiem” i metrykami od zwykłego ruchu:
    • ale nie zapomnij o punkcie 2.
  7. W razie potrzeby probe gotowości można używać do rozgrzewania/ładowania pamięci podręcznej i zwracać kod stanu 503, dopóki kontener się nie „rozgrzeje”:
    • zachęcam również do zapoznania się z nową kontrolą startupProbe, wprowadzonej w wersji 1.16 (pisaliśmy o niej po rosyjsku tutaj — przyp. tłum.).

Ostrzeżenia

  1. Nie polegaj na zewnętrznych zależnościach (takich jak bazy danych) podczas przeprowadzania testów gotowości/żywotności — może to prowadzić do awarii kaskadowych:
    • jako przykład weźmy stateful-service REST z 10 podami, które zależą od jednej bazy danych Postgres: gdy kontrola opiera się na działającym połączeniu z bazą danych, wszystkie 10 podów może się wyłączyć, jeśli wystąpi opóźnienie w sieci/po stronie bazy danych — zazwyczaj kończy się to gorzej, niż mogłoby;
    • zauważ, że Spring Data domyślnie sprawdza połączenie z bazą danych*;

      * Taki jest domyślny sposób działania Spring Data Redis (przynajmniej tak było, gdy sprawdzałem po raz ostatni), co doprowadziło do „katastrofalnej” awarii: gdy Redis na krótki czas stał się niedostępny, wszystkie pode „wykopy” dosłownie.

    • „zewnętrzny” w tym sensie może także oznaczać inne pody tej samej aplikacji, co oznacza, że w idealnym przypadku kontrola nie powinna zależeć od stanu innych podów w tym samym klastrze, aby zapobiec kaskadowym awariom:
      • wyniki mogą się różnić dla aplikacji z rozproszonym stanem (na przykład pamięć podręczna w podach).
  2. Nie używaj liveness probe dla podów (wyjątkiem są przypadki, gdy są one naprawdę potrzebne i jesteś w pełni świadomy specyfiki i konsekwencji ich zastosowania):
    • liveness probe może przyczynić się do odzyskiwania „zawieszonych” kontenerów, ale ponieważ masz pełną kontrolę nad swoją aplikacją, takie rzeczy jak „zawieszone” procesy i deadlocki w idealnym przypadku nie powinny się zdarzać: lepszą alternatywą jest celowe wyłączenie aplikacji i jej przywrócenie do poprzedniego stabilnego stanu;
    • Nieudana liveness probe spowoduje restart kontenera, co potencjalnie pogorszy skutki błędów związanych z uruchomieniem: restart kontenera prowadzi do przestoju (przynajmniej na czas uruchomienia aplikacji, powiedzmy, ponad 30 sekund), wywołując nowe błędy, zwiększając obciążenie innych kontenerów i zwiększając prawdopodobieństwo ich awarii, itd.;
    • Testy liveness w połączeniu z zewnętrzną zależnością to najgorsza możliwa kombinacja, grożąca kaskadowymi awariami: niewielkie opóźnienie po stronie bazy danych spowoduje restart wszystkich twoich kontenerów!
  3. Parametry testów liveness i readiness powinny być różne:
    • można używać liveness probe z tym samym health checkiem, ale z wyższym progiem aktywacji (failureThreshold), na przykład przypisując status niegotowy po 3 próbach i uznając, że liveness probe nie powiodło się po 10 próbach;
  4. Nie używaj testów exec, ponieważ wiążą się z nimi znane problemy, prowadzące do powstawania zombie procesów:

Podsumowanie

  • Używaj readiness probes, aby określić, kiedy pod jest gotowy do przyjęcia ruchu.
  • Używaj liveness probes tylko wtedy, gdy są naprawdę potrzebne.
  • Niewłaściwe użycie readiness/liveness probes może prowadzić do obniżenia dostępności i kaskadowych awarii.

Proby życia w Kubernetes mogą być niebezpieczne

Dodatkowe materiały na ten temat

Aktualizacja nr 1 z 2019-09-29

O init-kontenerach do migracji bazy danych: dodano przypis.

EJ przypomniał mi o PDB: jednym z problemów testów liveness jest brak koordynacji między podami. W Kubernetes istnieją Pod Disruption Budgets (PDB) w celu ograniczenia liczby równoległych awarii, które może doświadczyć aplikacja, jednak testy nie uwzględniają PDB. W idealnym przypadku możemy nakazać K8s: „Zrestartuj jeden pod, jeśli jego test nie powiedzie się, ale nie restartuj wszystkich, aby nie pogorszyć sytuacji”.

Bryan doskonale to ujął: „Użyj liveness probing, gdy wiesz, że najlepsze, co można zrobić, to „zabić” aplikację” (znowu, nie przesadzajmy).

Proby życia w Kubernetes mogą być niebezpieczne

Aktualizacja nr 2 z 2019-09-29

Odnośnie do czytania dokumentacji przed użyciem: stworzyłem odpowiednie zgłoszenie (feature request) dotyczące dodania dokumentacji o liveness probes.

P.S. od tłumacza

Przeczytaj także na naszym blogu:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster