Najlepsze praktyki dotyczące kontenerów Kubernetes: kontrole stanu

Najlepsze praktyki dotyczące kontenerów Kubernetes: kontrole stanu

TL; DR

  • Aby osiągnąć wysoką obserwowalność kontenerów i mikrousług, logi i podstawowe metryki nie wystarczą.
  • Aby zapewnić szybsze odzyskiwanie i większą odporność, aplikacje powinny stosować zasadę wysokiej obserwowalności (HOP).
  • Na poziomie aplikacji NOP wymaga: odpowiedniego rejestrowania, ścisłego monitorowania, kontroli poprawności i śledzenia wydajności/przejścia.
  • Użyj czeków jako elementu NOR Sonda gotowości и Sonda żywotności Kubernetesa.

Co to jest szablon kontroli stanu?

Projektując aplikację o znaczeniu krytycznym i charakteryzującą się wysoką dostępnością, bardzo ważne jest, aby pomyśleć o takim aspekcie, jak odporność na awarie. Aplikacja jest uważana za odporną na awarie, jeśli szybko odzyskuje sprawność po awarii. Typowa aplikacja chmurowa wykorzystuje architekturę mikrousług – gdzie każdy komponent jest umieszczony w osobnym kontenerze. Aby podczas projektowania klastra mieć pewność, że aplikacja na K8s będzie wysoce dostępna, należy postępować zgodnie z określonymi wzorcami. Wśród nich znajduje się szablon oceny funkcjonowania. Definiuje sposób, w jaki aplikacja komunikuje k8s, że jest zdrowa. To nie tylko informacja o tym, czy pod działa, ale także o tym, w jaki sposób odbiera żądania i odpowiada na nie. Im więcej Kubernetes wie o kondycji podu, tym mądrzejsze decyzje podejmuje dotyczące routingu ruchu i równoważenia obciążenia. Zatem zasada wysokiej obserwowalności pozwala aplikacji reagować na żądania w odpowiednim czasie.

Zasada wysokiej obserwowalności (HOP)

Jedną z nich jest zasada wysokiej obserwowalności zasady projektowania aplikacji kontenerowych. W architekturze mikrousług usługi nie dbają o to, jak ich żądanie zostanie przetworzone (i słusznie), ale liczy się to, w jaki sposób otrzymają odpowiedzi od usług odbierających. Przykładowo, aby uwierzytelnić użytkownika, jeden kontener wysyła do drugiego żądanie HTTP, oczekując odpowiedzi w określonym formacie – to wszystko. PythonJS może również przetworzyć żądanie, a Python Flask może odpowiedzieć. Kontenery przypominają czarne skrzynki z ukrytą między sobą zawartością. Jednak zasada NOP wymaga, aby każda usługa udostępniała wiele punktów końcowych interfejsu API, które wskazują jej kondycję, a także stan gotowości i odporności na błędy. Kubernetes żąda tych wskaźników, aby przemyśleć kolejne kroki w zakresie routingu i równoważenia obciążenia.

Dobrze zaprojektowana aplikacja w chmurze rejestruje swoje główne zdarzenia przy użyciu standardowych strumieni I/O STDERR i STDOUT. Następnie pojawia się usługa pomocnicza, np. filebeat, logstash czy fluentd, dostarczająca logi do scentralizowanego systemu monitorowania (np. Prometheus) i systemu gromadzenia logów (pakiet oprogramowania ELK). Poniższy diagram pokazuje, jak aplikacja w chmurze działa zgodnie ze wzorcem testu kondycji i zasadą wysokiej obserwowalności.

Najlepsze praktyki dotyczące kontenerów Kubernetes: kontrole stanu

Jak zastosować wzorzec kontroli stanu w Kubernetesie?

Po wyjęciu z pudełka k8s monitoruje stan podów za pomocą jednego z kontrolerów (Rozmieszczenia, Zestawy replik, Zestawy demonów, Zestawy stanowe itd itd.). Po odkryciu, że kapsuła z jakiegoś powodu spadła, kontroler próbuje ją zrestartować lub przenieść do innego węzła. Jednakże kapsuła może zgłosić, że jest uruchomiona i działa, ale sama nie działa. Podajmy przykład: Twoja aplikacja używa Apache jako serwera WWW, zainstalowałeś komponent na kilku podach klastra. Ponieważ biblioteka została niepoprawnie skonfigurowana, wszystkie żądania kierowane do aplikacji odpowiadają kodem 500 (wewnętrzny błąd serwera). Sprawdzając dostawę, sprawdzenie statusu kapsułek daje pomyślny wynik, ale klienci myślą inaczej. Tę niepożądaną sytuację opiszemy w następujący sposób:

Najlepsze praktyki dotyczące kontenerów Kubernetes: kontrole stanu

W naszym przykładzie k8s tak kontrola funkcjonalności. W tego typu weryfikacji kubelet na bieżąco sprawdza stan procesu w kontenerze. Gdy zrozumie, że proces się zatrzymał, uruchomi go ponownie. Jeśli błąd można rozwiązać, po prostu ponownie uruchamiając aplikację, a program jest zaprojektowany tak, aby zamykał się w przypadku dowolnego błędu, wówczas wystarczy sprawdzenie stanu procesu, aby postępować zgodnie z NOP i wzorcem testu kondycji. Szkoda tylko, że nie wszystkie błędy są eliminowane przez ponowne uruchomienie. W tym przypadku k8s oferuje 2 głębsze sposoby identyfikowania problemów z kapsułą: Sonda żywotności и Sonda gotowości.

Sonda Żywotności

Podczas Sonda żywotności kubelet wykonuje 3 rodzaje kontroli: nie tylko sprawdza, czy pod działa, ale także czy jest gotowy do przyjmowania żądań i odpowiedniego reagowania na nie:

  • Skonfiguruj żądanie HTTP do modułu. Odpowiedź musi zawierać kod odpowiedzi HTTP z zakresu od 200 do 399. Zatem kody 5xx i 4xx sygnalizują, że pod ma problemy, mimo że proces jest uruchomiony.
  • Aby przetestować pody z usługami innymi niż HTTP (na przykład serwerem pocztowym Postfix), musisz ustanowić połączenie TCP.
  • Wykonaj dowolne polecenie dla poda (wewnętrznie). Sprawdzenie uznaje się za pomyślne, jeśli kod zakończenia polecenia wynosi 0.

Przykład jak to działa. Kolejna definicja poda zawiera aplikację NodeJS, która przy żądaniach HTTP zgłasza błąd 500. Aby mieć pewność, że kontener zostanie zrestartowany po otrzymaniu takiego błędu, używamy parametru livenessProbe:

apiVersion: v1
kind: Pod
metadata:
 name: node500
spec:
 containers:
   - image: magalix/node500
     name: node500
     ports:
       - containerPort: 3000
         protocol: TCP
     livenessProbe:
       httpGet:
         path: /
         port: 3000
       initialDelaySeconds: 5

Nie różni się to od innych definicji podów, ale dodajemy obiekt .spec.containers.livenessProbe. Parametr httpGet akceptuje ścieżkę, na którą wysyłane jest żądanie HTTP GET (w naszym przykładzie jest to /, ale w scenariuszach bojowych może być coś takiego /api/v1/status). Inna sonda livenessProbe akceptuje parametr initialDelaySeconds, który instruuje operację weryfikacji, aby czekała określoną liczbę sekund. Opóźnienie jest potrzebne, ponieważ kontener potrzebuje czasu na uruchomienie, a po ponownym uruchomieniu będzie przez jakiś czas niedostępny.

Aby zastosować to ustawienie do klastra, użyj:

kubectl apply -f pod.yaml

Po kilku sekundach możesz sprawdzić zawartość kapsuły za pomocą następującego polecenia:

kubectl describe pods node500

Na końcu wyniku znajdź o to chodzi.

Jak widać, livenessProbe zainicjował żądanie HTTP GET, kontener wygenerował błąd 500 (tak właśnie został zaprogramowany), a kubelet go zrestartował.

Jeśli zastanawiasz się, jak została zaprogramowana aplikacja NideJS, oto pliki app.js i Dockerfile, które zostały użyte:

aplikacja.js

var http = require('http');

var server = http.createServer(function(req, res) {
    res.writeHead(500, { "Content-type": "text/plain" });
    res.end("We have run into an errorn");
});

server.listen(3000, function() {
    console.log('Server is running at 3000')
})

Dockerfile

FROM node
COPY app.js /
EXPOSE 3000
ENTRYPOINT [ "node","/app.js" ]

Warto o tym pamiętać: livenessProbe zrestartuje kontener tylko w przypadku niepowodzenia. Jeśli ponowne uruchomienie nie naprawi błędu uniemożliwiającego uruchomienie kontenera, kubelet nie będzie mógł podjąć działań w celu rozwiązania problemu.

Sonda gotowości

ReadinessProbe działa podobnie do livenessProbes (żądania GET, komunikacja TCP i wykonywanie poleceń), z wyjątkiem działań związanych z rozwiązywaniem problemów. Kontener, w którym wykryto awarię, nie jest uruchamiany ponownie, ale jest izolowany od ruchu przychodzącego. Wyobraź sobie, że jeden z kontenerów wykonuje wiele obliczeń lub jest pod dużym obciążeniem, co powoduje wydłużenie czasu odpowiedzi. W przypadku livenessProbe wyzwalane jest sprawdzenie dostępności odpowiedzi (poprzez parametr timeoutSeconds check), po czym kubelet restartuje kontener. Po uruchomieniu kontener zaczyna wykonywać zadania wymagające dużej ilości zasobów i zostaje ponownie uruchomiony. Może to mieć kluczowe znaczenie w przypadku aplikacji wymagających szybkości reakcji. Na przykład samochód w drodze czeka na odpowiedź z serwera, odpowiedź jest opóźniona - i samochód ulega wypadkowi.

Napiszmy definicję redinessProbe, która ustawi czas odpowiedzi na żądanie GET na nie więcej niż dwie sekundy, a aplikacja odpowie na żądanie GET po 5 sekundach. Plik pod.yaml powinien wyglądać następująco:

apiVersion: v1
kind: Pod
metadata:
 name: nodedelayed
spec:
 containers:
   - image: afakharany/node_delayed
     name: nodedelayed
     ports:
       - containerPort: 3000
         protocol: TCP
     readinessProbe:
       httpGet:
         path: /
         port: 3000
       timeoutSeconds: 2

Wdróżmy kapsułę za pomocą kubectl:

kubectl apply -f pod.yaml

Poczekajmy kilka sekund, a następnie zobaczmy, jak działał ReadinessProbe:

kubectl describe pods nodedelayed

Na końcu wyniku widać, że niektóre zdarzenia są podobne ten.

Jak widać, kubectl nie uruchomił ponownie kapsuły, gdy czas sprawdzania przekroczył 2 sekundy. Zamiast tego anulował żądanie. Komunikacja przychodząca jest przekierowywana do innych, działających kapsuł.

Zauważ, że teraz, gdy pod jest odciążony, kubectl ponownie kieruje do niego żądania: odpowiedzi na żądania GET nie są już opóźnione.

Dla porównania poniżej zmodyfikowany plik app.js:

var http = require('http');

var server = http.createServer(function(req, res) {
   const sleep = (milliseconds) => {
       return new Promise(resolve => setTimeout(resolve, milliseconds))
   }
   sleep(5000).then(() => {
       res.writeHead(200, { "Content-type": "text/plain" });
       res.end("Hellon");
   })
});

server.listen(3000, function() {
   console.log('Server is running at 3000')
})

TL; DR
Przed pojawieniem się aplikacji w chmurze dzienniki były głównym sposobem monitorowania i sprawdzania kondycji aplikacji. Nie było jednak możliwości podjęcia jakichkolwiek działań naprawczych. Dzienniki są nadal przydatne, należy je gromadzić i przesyłać do systemu gromadzenia dzienników w celu analizy sytuacji awaryjnych i podejmowania decyzji. [Wszystko to dałoby się zrobić bez aplikacji chmurowych wykorzystujących np. monit, ale z k8s stało się to dużo prostsze :) – przyp. red. ]

Dziś korekty trzeba wprowadzać niemal w czasie rzeczywistym, więc aplikacje nie muszą już być czarnymi skrzynkami. Nie, powinny pokazywać punkty końcowe, które umożliwiają systemom monitorującym odpytywanie i zbieranie cennych danych o stanie procesów, aby w razie potrzeby mogły natychmiast zareagować. Nazywa się to wzorcem projektowym testu wydajności i jest zgodny z zasadą wysokiej obserwowalności (HOP).

Kubernetes domyślnie oferuje 2 typy kontroli stanu: ReadinessProbe i livenessProbe. Obydwa korzystają z tych samych typów kontroli (żądania HTTP GET, komunikacja TCP i wykonywanie poleceń). Różnią się decyzjami, które podejmują w odpowiedzi na problemy w strąkach. livenessProbe ponownie uruchamia kontener w nadziei, że błąd się nie powtórzy, a ReadinessProbe izoluje pod od ruchu przychodzącego do czasu usunięcia przyczyny problemu.

Prawidłowy projekt aplikacji powinien obejmować oba typy sprawdzania i zapewniać, że zbierają one wystarczającą ilość danych, szczególnie w przypadku zgłoszenia wyjątku. Powinien także pokazywać niezbędne punkty końcowe API, które dostarczają systemowi monitorowania (Prometheus) ważnych wskaźników kondycji.

Źródło: www.habr.com

Dodaj komentarz