Limity procesora i agresywne throttling w Kubernetesie

Notatka. przeł.:Ta pouczająca historia Omio, europejskiego agregatora podróży, przeprowadza czytelników od podstaw teorii do fascynujących praktycznych zawiłości konfiguracji Kubernetes. Poznanie takich przypadków pomaga nie tylko poszerzyć horyzonty, ale także zapobiec poważnym problemom.

Limity procesora i agresywne throttling w Kubernetesie

Czy zdarzyło Ci się kiedyś, że aplikacja zawiesiła się, przestała reagować na kontrole stanu, a Ty nie potrafisz ustalić przyczyny? Jedno z możliwych wyjaśnień wiąże się z limitem zasobów procesora. O tym właśnie będzie ten artykuł.

TL; DR:
Zdecydowanie zalecamy wyłączenie limitów procesora w Kubernetes (lub wyłączenie kwot CFS w Kubelet), jeśli używasz wersji jądra Linux z błędem limitu CFS. W jądrze tam jest poważny i dobrze znane błąd powodujący nadmierne ograniczanie przepustowości i opóźnienia
.

W Omio całą infrastrukturą zarządza Kubernetes. Wszystkie nasze obciążenia stanowe i bezstanowe działają wyłącznie na platformie Kubernetes (używamy Google Kubernetes Engine). W ciągu ostatnich sześciu miesięcy zaczęliśmy obserwować losowe spowolnienia. Aplikacje zawieszają się lub przestają reagować na kontrole stanu, tracą połączenie sieciowe itp. To zachowanie zastanawiało nas od dłuższego czasu i w końcu postanowiliśmy zająć się tym problemem bezpośrednio.

Podsumowanie artykułu:

  • Kilka słów o kontenerach i Kubernetesie;
  • W jaki sposób wdrażane są żądania i limity procesora;
  • Jak działa limit procesora w środowiskach wielordzeniowych;
  • Jak śledzić ograniczanie procesora;
  • Rozwiązanie problemu i niuanse.

Kilka słów o kontenerach i Kubernetes

Kubernetes jest w zasadzie nowoczesnym standardem w świecie infrastruktury. Jego głównym zadaniem jest orkiestracja kontenerów.

pojemniki

W przeszłości musieliśmy tworzyć artefakty, takie jak pliki Java JAR/WAR, Python Eggs lub pliki wykonywalne, aby uruchamiać je na serwerach. Aby jednak działały, konieczne było wykonanie dodatkowych czynności: zainstalowanie środowiska wykonawczego (Java/Python), umieszczenie niezbędnych plików w odpowiednich miejscach, zapewnienie zgodności z konkretną wersją systemu operacyjnego itd. Innymi słowy, należało starannie rozważyć kwestię zarządzania konfiguracją (co często powodowało tarcia między programistami a administratorami systemu).

Kontenery zmieniły wszystko. Teraz obraz kontenera zachowuje się jak artefakt. Można go postrzegać jako pewien rozszerzony plik wykonywalny zawierający nie tylko program, ale także pełnoprawne środowisko wykonawcze (Java/Python/…), a także niezbędne pliki/pakiety, wstępnie zainstalowane i gotowe do uruchomienia. Kontenery można wdrażać i uruchamiać na różnych serwerach bez konieczności wykonywania dodatkowych czynności.

Co więcej, kontenery działają we własnym środowisku sandbox. Mają własną wirtualną kartę sieciową, własny system plików z ograniczonym dostępem, własną hierarchię procesów, własne limity procesora i pamięci itd. Wszystko to jest realizowane dzięki specjalnemu podsystemowi jądra. Linux — przestrzenie nazw.

Kubernetes

Jak wspomniano wcześniej, Kubernetes jest narzędziem do koordynacji kontenerów. Działa to w ten sposób, że dajesz pulę maszyn, a następnie mówisz: „Hej Kubernetes, uruchom dziesięć instancji mojego kontenera z dwoma procesorami i 2 GB pamięci każda i utrzymaj je w działaniu!” Kubernetes zajmie się resztą. Znajdzie wolną przestrzeń, uruchomi kontenery i w razie potrzeby uruchomi je ponownie, wdroży aktualizacje przy zmianie wersji itd. Zasadniczo Kubernetes oddziela sprzęt od reszty i sprawia, że ​​wszelkiego rodzaju systemy są odpowiednie do wdrażania i uruchamiania aplikacji.

Limity procesora i agresywne throttling w Kubernetesie
Kubernetes z perspektywy przeciętnego człowieka

Czym są żądania i limity w Kubernetes

Ok, omówiliśmy kontenery i Kubernetes. Wiemy również, że na jednej maszynie może znajdować się wiele kontenerów.

Można to porównać do mieszkania komunalnego. Przestronna przestrzeń (maszyny/urządzenia) jest zajmowana i wynajmowana kilku najemcom (kontenery). Kubernetes działa jako pośrednik w obrocie nieruchomościami. Nasuwa się pytanie: jak zapobiec konfliktom między lokatorami? A co jeśli ktoś z nich postanowi przejąć łazienkę na pół dnia?

W tym miejscu pojawiają się prośby i ograniczenia. Procesor PROŚBA potrzebne wyłącznie do celów planowania. Jest to coś w rodzaju „listy życzeń” dla kontenera, która służy do wyboru najodpowiedniejszego węzła. W tym samym czasie procesor Ograniczenia można porównać do umowy najmu – gdy tylko wybierzemy jednostkę kontenerową, Nie mogę wykraczać poza ustalone granice. I tu właśnie pojawia się problem...

Jak żądania i limity są implementowane w Kubernetes

Kubernetes korzysta z wbudowanego mechanizmu ograniczania przepustowości jądra w celu implementacji limitów procesora. Jeśli aplikacja przekroczy limit, włącza się ograniczanie przepustowości (czyli otrzymuje mniej cykli procesora). Żądania i limity pamięci są zorganizowane inaczej, dzięki czemu łatwiej je wykryć. Aby to zrobić, wystarczy sprawdzić status ostatniego ponownego uruchomienia modułu, aby zobaczyć, czy ma wartość „OOMKilled”. Ograniczanie wykorzystania procesora nie jest tak proste, jak się wydaje, ponieważ K8s udostępnia jedynie metryki według wykorzystania, a nie według grup cgroups.

Żądanie CPU

Limity procesora i agresywne throttling w Kubernetesie
Jak realizowane jest żądanie procesora

Dla uproszczenia przyjrzyjmy się procesowi na przykładzie komputera z procesorem 4-rdzeniowym.

K8s wykorzystuje mechanizm grupy kontrolnej (cgroups) do zarządzania alokacją zasobów (pamięci i procesora). Dostępny jest model hierarchiczny: dziecko dziedziczy granice grupy nadrzędnej. Szczegóły dystrybucji są przechowywane w wirtualnym systemie plików (/sys/fs/cgroup). W przypadku procesora jest to /sys/fs/cgroup/cpu,cpuacct/*.

K8s używa pliku cpu.share w celu dystrybucji zasobów procesora. W naszym przypadku grupa kontrolna główna otrzymuje 4096 udziałów procesora - 100% dostępnej mocy procesora (1 rdzeń = 1024; jest to wartość stała). Grupa główna rozdziela zasoby proporcjonalnie w zależności od udziałów potomków określonych w cpu.sharei oni z kolei robią to samo ze swoimi potomkami, i tak dalej. W typowym węźle Kubernetes grupa kontrolna główna ma troje dzieci: system.slice, user.slice и kubepods. Pierwsze dwie podgrupy służą do dystrybucji zasobów pomiędzy krytycznymi obciążeniami systemu i programami użytkownika poza K8. Ostatni jest kubepods — stworzony przez Kubernetes w celu dystrybucji zasobów pomiędzy kontenerami.

Powyższy diagram pokazuje, że pierwsza i druga podgrupa otrzymały 1024 udziałów, podczas gdy podgrupa kuberpod jest przydzielona 4096 udziałów. Jak to możliwe? Przecież grupa główna ma dostęp tylko do 4096 udziałów, a suma udziałów jego spadkobierców znacznie przekracza tę liczbę (6144) Chodzi o to, żeby wartość miała logiczne znaczenie, więc planista Linux (CFS) używa go do proporcjonalnego rozłożenia zasobów procesora. W naszym przypadku pierwsze dwie grupy otrzymują 680 prawdziwe udziały (16,6% z 4096), a kubepod otrzymuje resztę 2736 udziałów. W przypadku przestoju pierwsze dwie grupy nie wykorzystają przydzielonych zasobów.

Na szczęście harmonogram ma mechanizm zapobiegający marnowaniu nieużywanych zasobów procesora. Przenosi „bezczynną” moc obliczeniową do globalnej puli, z której jest ona dystrybuowana do grup potrzebujących dodatkowej mocy obliczeniowej procesora (przenoszenie odbywa się partiami, aby uniknąć strat z powodu zaokrągleń). Podobną metodę stosuje się do wszystkich potomków potomków.

Mechanizm ten zapewnia sprawiedliwy podział mocy procesora i daje pewność, że żaden proces nie „kradnie” zasobów innym.

Limit procesora

Mimo że konfiguracje limitów i żądań K8s wyglądają podobnie, ich implementacja jest radykalnie różna: najbardziej mylący i część najmniej udokumentowana.

K8s jest włączony Mechanizm kwotowy CFS w celu wprowadzenia ograniczeń. Ich ustawienia są określone w plikach. cfs_period_us и cfs_quota_us w katalogu cgroup (plik znajduje się również w tym miejscu) cpu.share).

Niepodobny cpu.share, kwota jest oparta na okres czasu, a nie od dostępnej mocy procesora. cfs_period_us ustala długość okresu (epoki) – zawsze wynosi ona 100000 µs (100 ms). W K8s istnieje opcja zmiany tej wartości, ale na razie jest ona dostępna tylko w wersji alfa. Harmonogram wykorzystuje epokę do ponownego wykorzystania wykorzystanych kwot. Drugi plik, cfs_quota_us, określa dostępny czas (kwotę) w każdej epoce. Należy pamiętać, że wartość ta jest również podawana w mikrosekundach. Kwota może przekroczyć czas trwania epoki; innymi słowy może to być więcej niż 100 ms.

Przyjrzyjmy się dwóm scenariuszom na maszynach 16-rdzeniowych (najpopularniejszy typ maszyn, jakie mamy w Omio):

Limity procesora i agresywne throttling w Kubernetesie
Scenariusz 1: 2 wątki i limit 200 ms. Bez dławienia

Limity procesora i agresywne throttling w Kubernetesie
Scenariusz 2: 10 wątków i limit 200 ms. Ograniczanie rozpoczyna się po 20 ms, dostęp do zasobów procesora zostaje wznowiony po kolejnych 80 ms

Załóżmy, że ustawisz limit procesora na 2 jądra; Kubernetes przetłumaczy tę wartość na 200 ms. Oznacza to, że kontener może wykorzystywać maksymalnie 200 ms czasu procesora bez ograniczania przepustowości.

I tu zaczyna się najciekawsza część. Jak wspomniano powyżej, dostępny limit wynosi 200 ms. Jeśli masz pracę równoległą dziesięć wątków na maszynie 12-rdzeniowej (patrz ilustracja scenariusza 2), podczas gdy wszystkie inne kontenery są bezczynne, kwota zostanie wyczerpana w ciągu zaledwie 20 ms (ponieważ 10 * 20 ms = 200 ms), a wszystkie wątki tego kontenera zostaną „zablokowane” (przepustnica) przez następne 80 ms. Sytuację pogarsza już wspomniany błąd harmonogramu, w wyniku czego następuje nadmierne ograniczanie przepustowości i kontener nie jest w stanie wyprodukować dostępnej kwoty.

Jak ocenić dławienie w kontenerach?

Wystarczy zalogować się do modułu i uruchomić cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — całkowita liczba okresów harmonogramowania;
  • nr_throttled — liczba okresów dławionych w składzie nr_periods;
  • throttled_time — całkowity czas dławienia w nanosekundach.

Limity procesora i agresywne throttling w Kubernetesie

Co się naprawdę dzieje?

W rezultacie we wszystkich aplikacjach występuje duże ograniczenie przepustowości. Czasami jest w półtora raza silniejszy niż oczekiwano!

Prowadzi to do różnych błędów – niepowodzeń kontroli gotowości, zawieszania się kontenerów, zrywania połączeń sieciowych, przekroczenia limitu czasu w zgłoszeniach serwisowych. Ostatecznie skutkuje to większymi opóźnieniami i wyższym wskaźnikiem błędów.

Decyzja i konsekwencje

Tutaj wszystko jest proste. Zrezygnowaliśmy z limitów procesora i rozpoczęliśmy aktualizację jądra systemu operacyjnego w klastrach do najnowszej wersji, w której błąd został naprawiony. Liczba błędów (HTTP 5xx) w naszych usługach natychmiast znacząco spadła:

Błędy HTTP 5xx

Limity procesora i agresywne throttling w Kubernetesie
Błędy HTTP 5xx z jednej krytycznej usługi

czas reakcji p95

Limity procesora i agresywne throttling w Kubernetesie
Opóźnienie w przypadku krytycznych żądań obsługi, 95. percentyl

Koszty operacyjne

Limity procesora i agresywne throttling w Kubernetesie
Liczba godzin poświęconych na kopiowanie

Co to jest haczyk?

Jak powiedziano na początku artykułu:

Można to porównać do mieszkania komunalnego... Kubernetes działa jak pośrednik w obrocie nieruchomościami. Ale jak zapobiec konfliktom między lokatorami? A co jeśli ktoś z nich postanowi przejąć łazienkę na pół dnia?

Ale tu jest haczyk. Jeden nieuczciwy kontener może wykorzystać wszystkie dostępne zasoby procesora maszyny. Jeśli dysponujesz przyzwoitym stosem aplikacji (np. poprawnie skonfigurowaną maszyną wirtualną Java, środowiskiem Go, maszyną wirtualną Node), to nie stanowi to problemu: możesz pracować w takich warunkach przez długi czas. Ale jeśli aplikacje są słabo zoptymalizowane lub wcale nie zoptymalizowane (FROM java:latest), sytuacja może wymknąć się spod kontroli. W Omio zautomatyzowaliśmy podstawowe pliki Dockerfile z rozsądnymi domyślnymi ustawieniami dla głównego stosu językowego, więc ten problem nie istniał.

Zalecamy monitorowanie wskaźników UŻYWAĆ (użycie, nasycenie i błędy), opóźnienie API i współczynnik błędów. Upewnij się, że wyniki spełniają Twoje oczekiwania.

referencje

To jest nasza historia. Poniższe materiały okazały się bardzo pomocne w zrozumieniu tego, co się działo:

Raporty o błędach Kubernetes:

Czy w swojej praktyce spotkałeś się z podobnymi problemami lub masz doświadczenie związane z ograniczaniem przepustowości w środowiskach produkcyjnych konteneryzowanych? Podziel się swoją historią w komentarzach!

PS od tłumacza

Przeczytaj także na naszym blogu:

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

Kup niezawodny hosting dla stron z ochroną DDoS, serwery VPS VDS 🔥 Kup niezawodny hosting stron internetowych z ochroną DDoS, serwery VPS VDS | ProHoster