Limity procesora i agresywne throttling w Kubernetesie

Notatka. przeł.: Ta otwierająca oczy historia Omio — europejskiego agregatora podróży — zabiera czytelników od podstawowej teorii do fascynujących praktycznych zawiłości konfiguracji Kubernetes. Znajomość takich przypadków pomaga nie tylko poszerzyć horyzonty, ale także zapobiec nietrywialnym problemom.

Limity procesora i agresywne throttling w Kubernetesie

Czy kiedykolwiek zdarzyło Ci się, że aplikacja utknęła w miejscu, przestała odpowiadać na kontrole stanu i nie mogłeś dowiedzieć się dlaczego? Jedno z możliwych wyjaśnień jest związane z limitami przydziału zasobów procesora. O tym właśnie porozmawiamy w tym artykule.

TL; DR:
Zdecydowanie zalecamy wyłączenie limitów procesora w Kubernetes (lub wyłączenie przydziałów CFS w Kubelet), jeśli używasz wersji jądra Linuksa z błędem przydziału CFS. W rdzeniu tam jest poważny i dobrze znane błąd prowadzący do nadmiernego dławienia i opóźnień
.

W Omio całą infrastrukturą zarządza Kubernetes. Wszystkie nasze stanowe i bezstanowe obciążenia działają wyłącznie na platformie Kubernetes (używamy Google Kubernetes Engine). W ciągu ostatnich sześciu miesięcy zaczęliśmy obserwować przypadkowe spowolnienia. Aplikacje zawieszają się lub przestają odpowiadać na kontrole stanu, tracą połączenie z siecią itp. To zachowanie zastanawiało nas przez długi czas, aż w końcu postanowiliśmy poważnie podejść do problemu.

Podsumowanie artykułu:

  • Kilka słów o kontenerach i Kubernetesie;
  • Jak realizowane są żądania i limity procesora;
  • Jak działa limit procesora w środowiskach wielordzeniowych;
  • Jak śledzić dławienie procesora;
  • Rozwiązanie problemu i niuanse.

Kilka słów o kontenerach i Kubernetesie

Kubernetes to w istocie nowoczesny standard w świecie infrastruktury. Jego głównym zadaniem jest orkiestracja kontenerów.

pojemniki

W przeszłości musieliśmy tworzyć artefakty, takie jak pliki JAR/WAR Java, jaja Pythona lub pliki wykonywalne, aby uruchamiać je na serwerach. Aby jednak zadziałały, trzeba było wykonać dodatkową pracę: zainstalować środowisko uruchomieniowe (Java/Python), umieścić niezbędne pliki w odpowiednich miejscach, zapewnić kompatybilność z konkretną wersją systemu operacyjnego itp. Innymi słowy, należy zwrócić szczególną uwagę na zarządzanie konfiguracją (co często było źródłem sporów między programistami a administratorami systemu).

Kontenery zmieniły wszystko. Teraz artefakt jest obrazem kontenera. Można go przedstawić jako rodzaj rozszerzonego pliku wykonywalnego, zawierającego nie tylko program, ale także pełnoprawne środowisko wykonawcze (Java/Python/...), a także niezbędne pliki/pakiety, preinstalowane i gotowe do uruchomić. Kontenery można wdrażać i uruchamiać na różnych serwerach bez wykonywania dodatkowych czynności.

Ponadto kontenery działają we własnym środowisku piaskownicy. Mają własną wirtualną kartę sieciową, własny system plików z ograniczonym dostępem, własną hierarchię procesów, własne ograniczenia dotyczące procesora i pamięci itp. Wszystko to jest realizowane dzięki specjalnemu podsystemowi jądra Linuksa - przestrzeniom nazw.

Kubernetes

Jak wspomniano wcześniej, Kubernetes jest koordynatorem kontenerów. Działa to w ten sposób: dajesz mu pulę maszyn, a potem mówisz: „Hej, Kubernetes, uruchommy dziesięć instancji mojego kontenera z 2 procesorami i 3 GB pamięci każda i niech działają!” Resztą zajmie się Kubernetes. Znajdzie wolną pojemność, uruchomi kontenery i w razie potrzeby zrestartuje je, wprowadzi aktualizację przy zmianie wersji itp. Zasadniczo Kubernetes pozwala wyodrębnić komponent sprzętowy i stworzyć szeroką gamę systemów odpowiednich do wdrażania i uruchamiania aplikacji.

Limity procesora i agresywne throttling w Kubernetesie
Kubernetes z punktu widzenia laika

Czym są żądania i limity w Kubernetes

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

Analogię można przeprowadzić z mieszkaniem komunalnym. Przejmujemy przestronny teren (maszyny/agregaty) i wynajmujemy kilku najemcom (kontenery). Kubernetes działa jako pośrednik w handlu nieruchomościami. Powstaje pytanie, jak uchronić najemców przed wzajemnymi konfliktami? A co jeśli, powiedzmy, któryś z nich zdecyduje się pożyczyć łazienkę na pół dnia?

Tutaj w grę wchodzą prośby i ograniczenia. procesor PROŚBA potrzebne wyłącznie do celów planistycznych. Jest to coś w rodzaju „listy życzeń” kontenera i służy do wyboru najbardziej odpowiedniego węzła. W tym samym czasie procesor Limit można porównać do umowy najmu – gdy tylko wybierzemy jednostkę do kontenera, następuje tzw Nie mogę wykraczać poza ustalone granice. I tu pojawia się problem...

Jak żądania i limity są implementowane w Kubernetes

Kubernetes wykorzystuje mechanizm dławienia (pomijanie cykli zegara) wbudowany w jądro w celu implementacji limitów procesora. Jeśli aplikacja przekracza limit, włączane jest ograniczanie przepustowości (tzn. otrzymuje mniej cykli procesora). Żądania i limity pamięci są zorganizowane inaczej, dzięki czemu są łatwiejsze do wykrycia. Aby to zrobić, po prostu sprawdź status ostatniego ponownego uruchomienia kapsuły: czy jest to „OOMKilled”. Ograniczanie procesora nie jest takie proste, ponieważ K8s udostępnia metryki tylko na podstawie użycia, a nie grup cgroup.

Żądanie procesora

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

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

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

K8s używa pliku cpu.share przydzielać zasoby procesora. W naszym przypadku grupa główna otrzymuje 4096 udziałów w zasobach 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 zarejestrowanych w cpu.share, a oni z kolei robią to samo ze swoimi potomkami itp. W typowym węźle Kubernetes główna grupa cgroup 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 - kubepods — stworzony przez Kubernetes w celu dystrybucji zasobów pomiędzy podami.

Powyższy diagram pokazuje, że pierwsza i druga podgrupa otrzymały każdą z nich 1024 udziałów, z przydzieloną podgrupą kuberpod 4096 Akcje Jak to możliwe: w końcu grupa root ma dostęp tylko do 4096 udziałów, a suma udziałów jej zstępnych znacznie przekracza tę liczbę (6144)? Chodzi o to, że wartość ma logiczny sens, więc program planujący Linuksa (CFS) używa jej do proporcjonalnego przydzielania zasobów procesora. W naszym przypadku pierwsze dwie grupy otrzymują 680 realne akcje (16,6% z 4096), a resztę otrzymuje kubepod 2736 Akcje W przypadku przestoju dwie pierwsze grupy nie będą korzystać z przydzielonych zasobów.

Na szczęście harmonogram posiada mechanizm pozwalający uniknąć marnowania niewykorzystanych zasobów procesora. Przenosi „bezczynną” pojemność do globalnej puli, z której jest dystrybuowana do grup potrzebujących dodatkowej mocy procesora (transfer odbywa się partiami, aby uniknąć strat zaokrągleń). Podobną metodę stosuje się wobec wszystkich potomków potomków.

Mechanizm ten zapewnia sprawiedliwy podział mocy procesora i sprawia, że ​​żaden proces nie „kradnie” zasobów innym.

Limit procesora

Pomimo tego, że konfiguracje limitów i żądań w K8 wyglądają podobnie, ich implementacja jest diametralnie inna: to najbardziej mylące i najmniej udokumentowana część.

K8s angażuje się Mechanizm kwotowy CFS wdrożyć limity. Ich ustawienia są określone w plikach cfs_period_us и cfs_quota_us w katalogu cgroup (tam też znajduje się plik cpu.share).

Niepodobny cpu.share, na której opiera się kontyngent okres czasu, a nie od dostępnej mocy procesora. cfs_period_us określa czas trwania okresu (epoki) - zawsze jest to 100000 μs (100 ms). Istnieje możliwość zmiany tej wartości w K8s, ale na razie jest ona dostępna tylko w wersji alfa. Program planujący wykorzystuje epokę do ponownego uruchomienia wykorzystanych przydziałów. Drugi plik cfs_quota_us, określa dostępny czas (limit) w każdej epoce. Należy pamiętać, że jest on również podawany w mikrosekundach. Limit może przekraczać długość epoki; innymi słowy, może być większy niż 100 ms.

Przyjrzyjmy się dwóm scenariuszom na maszynach 16-rdzeniowych (najpopularniejszym typie komputera, jaki mamy w Omio):

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

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

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

I tu zaczyna się zabawa. Jak wspomniano powyżej, dostępny limit wynosi 200 ms. Jeśli pracujesz równolegle dziesięć wątków na maszynie 12-rdzeniowej (patrz ilustracja do scenariusza 2), podczas gdy wszystkie inne pody są bezczynne, limit zostanie wyczerpany w ciągu zaledwie 20 ms (ponieważ 10 * 20 ms = 200 ms), a wszystkie wątki tego poda zawieszą się » (przepustnica) przez następne 80 ms. Wspomniany już błąd harmonogramu, przez co dochodzi do nadmiernego throttlingu i kontener nie jest w stanie nawet wypełnić istniejącego limitu.

Jak ocenić dławienie w podach?

Po prostu zaloguj się do kapsuły i uruchom cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — całkowitą liczbę okresów harmonogramu;
  • nr_throttled — liczba okresów dławionych w kompozycji nr_periods;
  • throttled_time — łączny czas dławienia w nanosekundach.

Limity procesora i agresywne throttling w Kubernetesie

Co się naprawdę dzieje?

W rezultacie uzyskujemy wysoki poziom dławienia we wszystkich aplikacjach. Czasami on wchodzi półtora raza silniejszy niż obliczono!

Prowadzi to do różnych błędów – niepowodzeń sprawdzania gotowości, zawieszania się kontenerów, przerw w połączeniu sieciowym, przekroczeń limitów czasu w ramach zgłoszeń serwisowych. Ostatecznie skutkuje to zwiększonymi opóźnieniami i wyższymi wskaźnikami 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 naprawiono błąd. Liczba błędów (HTTP 5xx) w naszych usługach od razu znacząco spadła:

Błędy HTTP 5xx

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

Czas reakcji p95

Limity procesora i agresywne throttling w Kubernetesie
Opóźnienie krytycznego żądania usługi, 95. percentyl

Koszty operacyjne

Limity procesora i agresywne throttling w Kubernetesie
Liczba godzin spędzonych na instancji

Co to jest haczyk?

Jak stwierdzono na początku artykułu:

Można przeprowadzić analogię z mieszkaniem komunalnym... Kubernetes pełni funkcję pośrednika w handlu nieruchomościami. Jak jednak uchronić najemców przed konfliktami między sobą? A co jeśli, powiedzmy, któryś z nich zdecyduje się pożyczyć łazienkę na pół dnia?

Oto haczyk. Jeden nieostrożny kontener może pochłonąć wszystkie dostępne zasoby procesora na maszynie. Jeśli masz inteligentny stos aplikacji (np. JVM, Go, Node VM są odpowiednio skonfigurowane), to nie stanowi to problemu: możesz pracować w takich warunkach przez długi czas. Ale jeśli aplikacje są słabo zoptymalizowane lub w ogóle nie są zoptymalizowane (FROM java:latest), sytuacja może wymknąć się spod kontroli. W Omio zautomatyzowaliśmy podstawowe pliki Dockerfile z odpowiednimi ustawieniami domyślnymi dla głównego stosu językowego, więc ten problem nie istniał.

Zalecamy monitorowanie wskaźników UŻYWAĆ (wykorzystanie, nasycenie i błędy), opóźnienia API i wskaźniki błędów. Upewnij się, że wyniki spełniają oczekiwania.

referencje

To jest nasza historia. Poniższe materiały znacznie pomogły zrozumieć, co się dzieje:

Raporty o błędach Kubernetes:

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

PS od tłumacza

Przeczytaj także na naszym blogu:

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

Dodaj komentarz