Dziewięć wskazówek dotyczących wydajności Kubernetes
Cześć wszystkim! Nazywam się Oleg Sidorenkov, pracuję w DomClick jako lider zespołu ds. infrastruktury. Używamy Cube w sprzedaży od ponad trzech lat iw tym czasie przeżyliśmy z nim wiele różnych ciekawych chwil. Dzisiaj powiem ci, jak przy odpowiednim podejściu możesz wycisnąć jeszcze więcej wydajności z waniliowego Kubernetes dla twojego klastra. Gotowy do startu start!
Wszyscy dobrze wiecie, że Kubernetes to skalowalny system typu open source do orkiestracji kontenerów; cóż, czyli 5 plików binarnych, które czynią cuda, zarządzając cyklem życia Twoich mikroserwisów w środowisku serwerowym. Ponadto jest to dość elastyczne narzędzie, które można złożyć jak konstruktor Lego w celu maksymalnego dostosowania do różnych zadań.
I wszystko wydaje się być w porządku: wrzuć serwery do klastra, jak drewno opałowe do paleniska i nie znam żalu. Ale jeśli jesteś za środowiskiem, pomyślisz: „Jak mogę utrzymać ogień w piecu i żałować lasu?”. Innymi słowy, jak znaleźć sposoby na poprawę infrastruktury i obniżenie kosztów.
1. Śledź zasoby zespołu i aplikacji
Jedną z najbardziej banalnych, ale skutecznych metod jest wprowadzenie żądań/limitów. Oddziel aplikacje według przestrzeni nazw, a przestrzenie nazw według zespołów deweloperskich. Ustaw aplikację przed wdrożeniem wartości zużycia czasu procesora, pamięci, pamięci efemerycznej.
Z doświadczenia doszliśmy do wniosku: nie warto zwiększać żądań z limitów więcej niż dwa razy. Rozmiar klastra jest obliczany na podstawie żądań, a jeśli ustawisz aplikację na różnicę w zasobach, na przykład 5-10 razy, to wyobraź sobie, co stanie się z twoim węzłem, gdy zostanie wypełniony podami i nagle otrzyma obciążenie. Nic dobrego. Minimalnie ograniczaj, a maksymalnie pożegnaj się z pracownikiem i uzyskaj cykliczne obciążenie pozostałych węzłów po tym, jak strąki zaczną się poruszać.
W dodatku z pomocą limitranges możesz ustawić wartości zasobów dla kontenera na starcie - minimalną, maksymalną i domyślną:
➜ ~ kubectl describe limitranges --namespace ops
Name: limit-range
Namespace: ops
Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio
---- -------- --- --- --------------- ------------- -----------------------
Container cpu 50m 10 100m 100m 2
Container ephemeral-storage 12Mi 8Gi 128Mi 4Gi -
Container memory 64Mi 40Gi 128Mi 128Mi 2
Pamiętaj o ograniczeniu zasobów przestrzeni nazw, aby jedno polecenie nie mogło zająć wszystkich zasobów klastra:
Jak widać z opisu resourcequotas, jeśli komenda ops chce wdrożyć pody, które zużyją kolejne 10 procesorów, wówczas program planujący nie pozwoli na to i zgłosi błąd:
Aby rozwiązać podobny problem, możesz napisać narzędzie, na przykład jako to, który może przechowywać i zatwierdzać stan zasobów dowodzenia.
2. Wybierz najlepsze miejsce do przechowywania plików
W tym miejscu chciałbym poruszyć temat woluminów trwałych oraz podsystemu dyskowego węzłów roboczych Kubernetes. Mam nadzieję, że nikt nie używa „Cube” na HDD w produkcji, ale czasami nawet zwykły dysk SSD to już za mało. Spotkaliśmy się z takim problemem, że logi zabijały dysk operacjami I/O, a tutaj nie ma zbyt wielu rozwiązań:
Użyj wysokowydajnych dysków SSD lub przełącz się na NVMe (jeśli zarządzasz własnym sprzętem).
Zmniejsz poziom logowania.
Wykonuj „inteligentne” równoważenie strąków, które gwałcą dysk (podAntiAffinity).
Powyższy zrzut ekranu pokazuje, co dzieje się w nginx-ingress-controller z dyskiem, gdy logowanie access_logs jest włączone (~12k logów/s). Taki stan oczywiście może doprowadzić do degradacji wszystkich aplikacji na tym węźle.
Jeśli chodzi o PV, niestety nie próbowałem wszystkiego. gatunki Trwałe woluminy. Skorzystaj z najlepszej opcji, która Ci odpowiada. Historycznie zdarzało się w naszym kraju, że niewielka część usług potrzebuje woluminów RWX i dawno temu zaczęto wykorzystywać do tego zadania pamięć NFS. Tanio i... dość. Oczywiście zjedliśmy z nim gówno - bądź zdrowy, ale nauczyliśmy się go stroić i głowa już nie boli. A jeśli to możliwe, przełącz się na obiektową pamięć masową S3.
3. Twórz zoptymalizowane obrazy
Najlepiej używać obrazów zoptymalizowanych pod kątem kontenerów, aby Kubernetes mógł je szybciej pobierać i wykonywać wydajniej.
Optymalizacja oznacza, że obrazy:
zawierać tylko jedną aplikację lub wykonywać tylko jedną funkcję;
mały rozmiar, ponieważ duże obrazy są gorzej przesyłane przez sieć;
mieć punkty końcowe dotyczące kondycji i gotowości, których Kubernetes może użyć do podjęcia działań w przypadku przestoju;
korzystaj z systemów operacyjnych przyjaznych dla kontenerów (takich jak Alpine czy CoreOS), które są bardziej odporne na błędy konfiguracji;
używaj kompilacji wieloetapowych, aby można było wdrażać tylko skompilowane aplikacje, a nie towarzyszące im źródła.
Istnieje wiele narzędzi i usług, które pozwalają sprawdzać i optymalizować obrazy w locie. Ważne jest, aby zawsze były aktualne i bezpieczne. W rezultacie otrzymujesz:
Zmniejszone obciążenie sieci w całym klastrze.
Skrócono czas uruchamiania kontenera.
Mniejszy rozmiar całego rejestru platformy Docker.
4. Użyj pamięci podręcznej DNS
Jeśli mówimy o dużych obciążeniach, to bez dostrojenia systemu DNS klastra życie jest dość kiepskie. Dawno, dawno temu programiści Kubernetes wspierali swoje rozwiązanie kube-dns. Został również wdrożony w naszym kraju, ale to oprogramowanie nie dostroiło się szczególnie i nie zapewniło wymaganej wydajności, chociaż wydaje się, że zadanie jest proste. Potem pojawił się coredns, na który przeszliśmy i nie znaliśmy żalu, później stał się domyślną usługą DNS w K8s. W pewnym momencie podrosło nam do 40 tys rps do systemu DNS i to rozwiązanie też nie wystarczało. Ale szczęśliwym trafem pojawił się Nodelocaldns, czyli lokalna pamięć podręczna węzła, czyli Lokalna pamięć podręczna DNS węzła.
Dlaczego go używamy? W jądrze Linuksa występuje błąd, który przy wielokrotnym dostępie przez conntrack NAT przez UDP prowadzi do sytuacji wyścigu przy zapisywaniu do tabel conntrack, a część ruchu przez NAT jest tracona (każda podróż przez usługę to NAT). Nodelocaldns rozwiązuje ten problem, pozbywając się NAT i aktualizując łączność TCP do upstream DNS, a także lokalnie buforując zapytania DNS upstream (w tym krótką 5-sekundową negatywną pamięć podręczną).
5. Automatycznie skaluj strąki w poziomie iw pionie
Czy możesz śmiało powiedzieć, że wszystkie Twoje mikroserwisy są gotowe na dwu-, trzykrotny wzrost obciążenia? Jak prawidłowo alokować zasoby do swoich aplikacji? Utrzymywanie kilku podów działających ponad obciążenie może być zbędne, a utrzymywanie ich jeden po drugim grozi przestojami spowodowanymi nagłym wzrostem ruchu w usłudze. Złoty środek pomaga osiągnąć zaklęcie pomnożenia takich usług jak Automatyczny skaler podów poziomych и Pionowe automatyczne skalowanie podów.
VPA umożliwia automatyczne podnoszenie żądań/limitów kontenerów w podeście na podstawie rzeczywistego wykorzystania. Jak może się przydać? Jeśli masz pody, których z jakiegoś powodu nie można skalować w poziomie (co nie jest całkowicie niezawodne), możesz spróbować zaufać VPA w zakresie zmiany zasobów. Jego funkcją jest system rekomendacji oparty na historycznych i aktualnych danych z serwera metryk, więc jeśli nie chcesz automatycznie zmieniać żądań/limitów, możesz po prostu monitorować zalecane zasoby dla swoich kontenerów i optymalizować ustawienia, aby oszczędzać procesor i pamięć w klastrze.
Obraz zaczerpnięty z https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231
Harmonogram w Kubernetes zawsze opiera się na żądaniach. Bez względu na to, jaką wartość tam wpiszesz, program planujący na jej podstawie wyszuka odpowiedni węzeł. Wartość limitów jest potrzebna kubletowi, aby wiedzieć, kiedy zdławić lub zabić kapsułę. A ponieważ jedynym ważnym parametrem jest wartość żądania, VPA będzie z nim współpracować. Za każdym razem, gdy skalujesz swoją aplikację w pionie, określasz, jakie powinny być żądania. A co wtedy stanie się z granicami? Ten parametr będzie również skalowany proporcjonalnie.
Jak wspomniano powyżej, jest to skalowanie proporcjonalne w oparciu o stosunek żądań do limitów w manifeście:
Procesor: 200m → 300m: stosunek 1:1.75;
Pamięć: 250Mi → 500Mi: stosunek 1:2.
Jeśli chodzi o HPA, wtedy mechanizm działania jest bardziej przejrzysty. Progi są ustawiane dla metryk, takich jak procesor i pamięć, a jeśli średnia wszystkich replik przekroczy próg, aplikacja skaluje się o +1 pod, aż wartość spadnie poniżej progu lub do osiągnięcia maksymalnej liczby replik.
Obraz zaczerpnięty z https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231
Oprócz zwykłych metryk, takich jak procesor i pamięć, możesz ustawić progi dla niestandardowych metryk Prometheus i pracować z nimi, jeśli uważasz, że jest to najdokładniejszy sposób określenia, kiedy skalować aplikację. Gdy aplikacja ustabilizuje się poniżej określonego progu metryki, HPA rozpocznie skalowanie zasobników do minimalnej liczby replik lub do momentu, gdy obciążenie osiągnie określony próg.
6. Nie zapomnij o koligacji węzłów i koligacji podów
Nie wszystkie węzły działają na tym samym sprzęcie i nie wszystkie zasobniki muszą uruchamiać aplikacje intensywnie korzystające z mocy obliczeniowej. Kubernetes pozwala określić specjalizację używanych węzłów i podów Koligacja węzła и Powinowactwo podów.
Jeśli masz węzły, które są odpowiednie dla operacji intensywnie korzystających z obliczeń, to w celu uzyskania maksymalnej wydajności lepiej jest powiązać aplikacje z odpowiednimi węzłami. Aby to zrobić, użyj nodeSelector z etykietą węzła.
Załóżmy, że masz dwa węzły: jeden z CPUType=HIGHFREQ i dużą liczbę szybkich rdzeni, inny z MemoryType=HIGHMEMORY więcej pamięci i szybsze działanie. Najprostszym sposobem jest przypisanie wdrożenia pod do węzła HIGHFREQdodając do sekcji spec taki selektor:
…
nodeSelector:
CPUType: HIGHFREQ
Bardziej kosztownym i specyficznym sposobem na to jest użycie nodeAffinity w terenie affinity Sekcja spec. Istnieją dwie opcje:
requiredDuringSchedulingIgnoredDuringExecution: twarde ustawienie (harmonogram będzie wdrażał pody tylko w określonych węzłach (i nigdzie indziej));
preferredDuringSchedulingIgnoredDuringExecution: ustawienie miękkie (harmonogram spróbuje wdrożyć w określonych węzłach, a jeśli się nie powiedzie, spróbuje wdrożyć w następnym dostępnym węźle).
Możesz określić określoną składnię do zarządzania etykietami węzłów, na przykład In, NotIn, Exists, DoesNotExist, Gt lub Lt. Pamiętaj jednak, że złożone metody na długich listach etykiet spowalniają podejmowanie decyzji w sytuacjach krytycznych. Innymi słowy, nie komplikuj.
Jak wspomniano powyżej, Kubernetes umożliwia ustawienie powiązania bieżących podów. Oznacza to, że niektóre zasobniki mogą współpracować z innymi zasobnikami w tej samej strefie dostępności (dotyczy chmur) lub węzłów.
В podAffinity marginesy affinity Sekcja spec dostępne są te same pola co w przypadku nodeAffinity: requiredDuringSchedulingIgnoredDuringExecutionи preferredDuringSchedulingIgnoredDuringExecution. Jedyna różnica polega na tym matchExpressions powiąże strąki z węzłem, w którym już działa strąk z tą etykietą.
Więcej Kubernetes oferuje pole podAntiAffinity, który w przeciwieństwie do tego nie wiąże poda z węzłem z określonymi strąkami.
O wyrażeniach nodeAffinity Można udzielić tej samej rady: staraj się, aby zasady były proste i logiczne, nie próbuj przeciążać specyfikacji poda złożonym zestawem reguł. Bardzo łatwo jest utworzyć regułę, która nie pasuje do warunków klastra, powodując dodatkowe obciążenie programu planującego i obniżając ogólną wydajność.
7. Skażenia i tolerancje
Istnieje inny sposób zarządzania harmonogramem. Jeśli masz duży klaster z setkami węzłów i tysiącami mikrousług, bardzo trudno jest zapobiec hostowaniu niektórych zasobników przez określone węzły.
Pomaga w tym mechanizm skaz - zasady zakazujące. Na przykład można uniemożliwić niektórym węzłom uruchamianie zasobników w określonych scenariuszach. Aby zastosować skazę do określonego węzła, użyj opcji taint w kubectlu. Określ klucz i wartość, a następnie skazę NoSchedule lub NoExecute:
Warto również zauważyć, że mechanizm skażenia obsługuje trzy główne efekty: NoSchedule, NoExecute и PreferNoSchedule.
NoScheduleoznacza, że dopóki nie pojawi się odpowiedni wpis w specyfikacji poda tolerations, nie można go wdrożyć w węźle (w tym przykładzie node10).
PreferNoSchedule - wersja uproszczona NoSchedule. W takim przypadku program planujący spróbuje nie przydzielać zasobników, które nie mają pasującego wpisu. tolerations na węzeł, ale nie jest to sztywny limit. Jeśli w klastrze nie ma żadnych zasobów, w tym węźle rozpoczną się wdrażanie zasobników.
NoExecute- ten efekt powoduje natychmiastową ewakuację kapsuł, które nie mają pasującego wpisu tolerations.
Co ciekawe, to zachowanie można cofnąć za pomocą mechanizmu tolerancji. Jest to wygodne, gdy istnieje „zakazany” węzeł i trzeba umieścić na nim tylko usługi infrastrukturalne. Jak to zrobić? Zezwalaj tylko na te strąki, dla których istnieje odpowiednia tolerancja.
Nie oznacza to, że podczas kolejnego ponownego wdrożenia pod trafi dokładnie w ten węzeł, nie jest to mechanizm Node Affinity i nodeSelector. Ale łącząc kilka funkcji, możesz osiągnąć bardzo elastyczną konfigurację harmonogramu.
8. Ustaw priorytet rozmieszczenia podów
Tylko dlatego, że skonfigurowałeś powiązania pod-węzły, nie oznacza, że wszystkie pody powinny być traktowane z tym samym priorytetem. Na przykład możesz chcieć wdrożyć niektóre Pody przed innymi.
Kubernetes oferuje różne sposoby ustawiania priorytetu kapsuł i wywłaszczania. Oprawa składa się z kilku części: obiekt PriorityClassi opisy pól priorityClassNamew specyfikacji pod. Rozważ przykład:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 99999
globalDefault: false
description: "This priority class should be used for very important pods only"
Tworzymy PriorityClass, nadaj mu nazwę, opis i wartość.Wyższy value, tym wyższy priorytet. Wartość może być dowolną 32-bitową liczbą całkowitą mniejszą lub równą 1 000 000 000. Wyższe wartości są zarezerwowane dla podów systemowych o znaczeniu krytycznym, których zwykle nie można wywłaszczyć.Eksmisja nastąpi tylko wtedy, gdy kapsuła o wysokim priorytecie nie będzie miała gdzie się obrócić, wtedy niektóre z kapsuł z określonego węzła zostaną ewakuowane. Jeśli ten mechanizm jest dla Ciebie zbyt sztywny, możesz dodać opcję preemptionPolicy: Never, a wtedy nie będzie wywłaszczania, kapsuła będzie pierwsza w kolejce i będzie czekać, aż program planujący znajdzie dla niej wolne zasoby.
Następnie tworzymy pod, w którym określamy nazwę priorityClassName:
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
role: myrole
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
protocol: TCP
priorityClassName: high-priority
Możesz utworzyć tyle klas priorytetów, ile chcesz, chociaż zaleca się, aby nie dać się ponieść emocjom (powiedzmy, ogranicz się do niskiego, średniego i wysokiego priorytetu).
W ten sposób, jeśli to konieczne, możesz zwiększyć efektywność wdrażania krytycznych usług, takich jak nginx-ingress-controller, coredns itp.
9. Zoptymalizuj swój klaster ETCD
ETCD można nazwać mózgiem całego klastra. Bardzo ważne jest utrzymanie działania tej bazy danych na wysokim poziomie, ponieważ od tego zależy szybkość operacji w „Kostce”. Dość standardowym, a jednocześnie dobrym rozwiązaniem byłoby utrzymywanie klastra ETCD na węzłach nadrzędnych, aby mieć minimalne opóźnienie do kube-apiserver. Jeśli nie jest to możliwe, umieść ETCD jak najbliżej, z dobrą przepustowością między uczestnikami. Zwróć też uwagę, ile węzłów z ETCD może wypaść bez szkody dla klastra.
Należy pamiętać, że nadmierny wzrost liczby uczestników klastra może zwiększyć odporność na awarie kosztem wydajności, wszystko powinno być z umiarem.
Jeśli mówimy o konfiguracji usługi, istnieje kilka zaleceń:
Mieć dobry sprzęt, w oparciu o rozmiar klastra (można przeczytać tutaj).
Dostosuj kilka parametrów, jeśli rozłożyłeś klaster między parę kontrolerów domeny lub sieć, a dyski pozostawiają wiele do życzenia (możesz przeczytać tutaj).
wniosek
W tym artykule opisano punkty, których nasz zespół stara się przestrzegać. To nie jest opis krok po kroku działań, ale opcje, które mogą być przydatne do optymalizacji narzutu klastra. Oczywiste jest, że każdy klaster jest wyjątkowy na swój sposób, a rozwiązania dostrajające mogą się znacznie różnić, dlatego interesujące byłoby uzyskanie od Ciebie opinii: jak monitorujesz swój klaster Kubernetes, jak poprawiasz jego wydajność. Podziel się swoimi doświadczeniami w komentarzach, będzie ciekawie je poznać.