Dziewięć wskazówek dotyczących wydajności Kubernetes

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

Dziewięć wskazówek dotyczących wydajności Kubernetes

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.

resources:
   requests:
     memory: 2Gi
     cpu: 250m
   limits:
     memory: 4Gi
     cpu: 500m

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:

➜  ~ kubectl describe resourcequotas --namespace ops
Name:                   resource-quota
Namespace:              ops
Resource                Used          Hard
--------                ----          ----
limits.cpu              77250m        80
limits.memory           124814367488  150Gi
pods                    31            45
requests.cpu            53850m        80
requests.memory         75613234944   150Gi
services                26            50
services.loadbalancers  0             0
services.nodeports      0             0

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:

Error creating: pods "nginx-proxy-9967d8d78-nh4fs" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=5,requests.cpu=5, used: limits.cpu=77250m,requests.cpu=53850m, limited: limits.cpu=10,requests.cpu=10

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

Dziewięć wskazówek dotyczących wydajności Kubernetes

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

Dziewięć wskazówek dotyczących wydajności Kubernetes

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:

  1. Zmniejszone obciążenie sieci w całym klastrze.

  2. Skrócono czas uruchamiania kontenera.

  3. Mniejszy rozmiar całego rejestru platformy Docker.

4. Użyj pamięci podręcznej DNS

Dziewięć wskazówek dotyczących wydajności Kubernetes

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

Dziewięć wskazówek dotyczących wydajności Kubernetes

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.

Dziewięć wskazówek dotyczących wydajności KubernetesObraz 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.

Na przykład, oto typowe ustawienia pod:

resources:
   requests:
     memory: 250Mi
     cpu: 200m
   limits:
     memory: 500Mi
     cpu: 350m

Silnik rekomendacji określa, że ​​Twoja aplikacja potrzebuje 300m CPU i 500Mi do poprawnego działania. Otrzymasz te ustawienia:

resources:
   requests:
     memory: 500Mi
     cpu: 300m
   limits:
     memory: 1000Mi
     cpu: 525m

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.

Dziewięć wskazówek dotyczących wydajności KubernetesObraz 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

Dziewięć wskazówek dotyczących wydajności Kubernetes

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:

$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule

Warto również zauważyć, że mechanizm skażenia obsługuje trzy główne efekty: NoSchedule, NoExecute и PreferNoSchedule.

  • NoSchedule oznacza, ż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.

Oto jak wyglądałaby specyfikacja poda:

spec:
   tolerations:
     - key: "node-role.kubernetes.io/ingress"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"

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 PriorityClass i opisy pól priorityClassName w 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

Dziewięć wskazówek dotyczących wydajności Kubernetes

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.

Dziewięć wskazówek dotyczących wydajności Kubernetes

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ń:

  1. Mieć dobry sprzęt, w oparciu o rozmiar klastra (można przeczytać tutaj).

  2. 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ć.

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