Zazwyczaj konieczne jest zapewnienie dedykowanej puli zasobów dla konkretnej aplikacji, aby działała poprawnie i stabilnie. Co jednak, jeśli na tych samych zasobach działa kilka aplikacji? Jak zapewnić każdemu z nich minimalnie potrzebne zasoby? W jaki sposób można ograniczyć zużycie zasobów? Jak prawidłowo rozdzielić obciążenie pomiędzy węzłami? Jak zapewnić funkcjonowanie mechanizmu poziomego skalowania w przypadku wzrostu obciążenia aplikacji?

Najpierw warto określić, jakie podstawowe typy zasobów istnieją w systemie — są to oczywiście czas procesora i pamięć operacyjna. W manifestach k8s te typy zasobów mierzone są w następujących jednostkach:
- CPU — w rdzeniach
- RAM — w bajtach
Każdy zasób może mieć określone dwa typy wymagań — requests i limits. Requests — opisuje minimalne wymagania dotyczące dostępnych zasobów węzła do uruchomienia kontenera (a w przypadku poda w ogóle), podczas gdy limits ustala sztywną górną granicę zasobów, które są dostępne dla kontenera.
Ważne jest, aby zrozumieć, że nie ma obowiązku określania obu typów w manifeście, a zachowanie będzie następujące:
- Jeśli wyraźnie zdefiniowany jest tylko limit zasobów, to requests dla tego zasobu automatycznie przyjmuje wartość równą limits (można to sprawdzić, wywołując opis jednostki). Oznacza to, że faktycznie praca kontenera będzie ograniczona do tej samej ilości zasobów, które są wymagane do jego uruchomienia.
- Jeśli dla zasobu wyraźnie zdefiniowany jest tylko requests, to nie są ustalane żadne górne ograniczenia dla tego zasobu — to znaczy, że kontener jest ograniczony tylko zasobami samego węzła.
Istnieje również możliwość skonfigurowania zarządzania zasobami nie tylko na poziomie konkretnego kontenera, ale także na poziomie namespace przy użyciu następujących jednostek:
- LimitRange — opisuje politykę ograniczeń na poziomie kontenera/poda w ns i jest potrzebna do opisania domyślnych ograniczeń dla kontenera/poda, a także do zapobiegania tworzeniu zbyt obfitych kontenerów/podów (lub odwrotnie), ograniczania ich liczby oraz określania możliwej różnicy wartości w limits i requests.
- ResourceQuotas — opisuje politykę ograniczeń dla wszystkich kontenerów w ns i jest zazwyczaj stosowany do podziału zasobów między środowiskami (przydatne, gdy środowiska nie są ściśle oddzielone na poziomie węzłów)
Poniżej znajdują się przykłady manifestów, w których ustalane są ograniczenia zasobów:
Na poziomie konkretnego kontenera:
containers: - name: app-nginx image: nginx resources: requests: memory: 1Gi limits: cpu: 200mTzn. w tym przypadku do uruchomienia kontenera z nginx potrzebne jest co najmniej 1G pamięci RAM i 0.2 CPU na węźle, przy czym maksymalnie kontener może zjeść 0.2 CPU i całą dostępną pamięć RAM na węźle.
Na poziomie całego ns:
apiVersion: v1 kind: ResourceQuota metadata: name: nxs-test spec: hard: requests.cpu: 300m requests.memory: 1Gi limits.cpu: 700m limits.memory: 2GiTzn. suma wszystkich requestów kontenerów w domyślnym ns nie może przekraczać 300m dla CPU i 1G dla RAM, a suma wszystkich limitów — 700m dla CPU i 2G dla RAM.
Domyślne ograniczenia dla kontenerów w ns:
apiVersion: v1 kind: LimitRange metadata: name: nxs-limit-per-container spec: limits: - type: Container defaultRequest: cpu: 100m memory: 1Gi default: cpu: 1 memory: 2Gi min: cpu: 50m memory: 500Mi max: cpu: 2 memory: 4GiTzn. w domyślnym namespace dla wszystkich kontenerów domyślnie zostanie ustawiony request na 100m dla CPU i 1G dla RAM, limit — 1 CPU i 2G. Ponadto również ustalone jest ograniczenie na możliwe wartości w request/limit dla CPU (50m < x < 2) i RAM (500M < x < 4G).
Ograniczenia na poziomie podów ns:
apiVersion: v1 kind: LimitRange metadata: name: nxs-limit-pod spec: limits: - type: Pod max: cpu: 4 memory: 1GiTzn. dla każdego podu w domyślnym ns ustalone zostanie ograniczenie na 4 vCPU i 1G.
Teraz chciałbym opowiedzieć o korzyściach, jakie mogą przynieść te ograniczenia.
Mechanizm równoważenia obciążenia między węzłami
Jak wiadomo, za rozmieszczanie podów na węzłach odpowiada komponent k8s, zwany scheduler, który działa według określonego algorytmu. Algorytm ten podczas wyboru optymalnego węzła do uruchomienia przechodzi przez dwa etapy:
- Filtrowanie
- Ranking
Tzn. zgodnie z opisaną polityką początkowo wybierane są węzły, na których możliwe jest uruchomienie poda na podstawie zestawu predicates (w tym sprawdzane jest, czy węzeł ma wystarczającą ilość zasobów do uruchomienia poda — PodFitsResources), a następnie dla każdego z tych węzłów zgodnie z priorities Punkty są przyznawane (w tym większa ilość wolnych zasobów w węźle — tym więcej punktów mu przypisywanych jest — LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) i uruchamiane na węźle z największą liczbą punktów (jeśli kilka węzłów spełnia ten warunek, wybierany jest losowo z nich).
Jednak należy pamiętać, że scheduler, oceniając dostępne zasoby węzła, opiera się na danych, które są przechowywane w etcd — tzn. na sumie requested/limit zasobów każdego podu uruchomionego na tym węźle, a nie na faktycznym zużyciu zasobów. Te informacje można uzyskać z wyniku polecenia kubectl describe node $NODE, na przykład:
# kubectl describe nodes nxs-k8s-s1
..
Non-terminated Pods: (9 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
ingress-nginx nginx-ingress-controller-754b85bf44-qkt2t 0 (0%) 0 (0%) 0 (0%) 0 (0%) 233d
kube-system kube-flannel-26bl4 150m (0%) 300m (1%) 64M (0%) 500M (1%) 233d
kube-system kube-proxy-exporter-cb629 0 (0%) 0 (0%) 0 (0%) 0 (0%) 233d
kube-system kube-proxy-x9fsc 0 (0%) 0 (0%) 0 (0%) 0 (0%) 233d
kube-system nginx-proxy-k8s-worker-s1 25m (0%) 300m (1%) 32M (0%) 512M (1%) 233d
nxs-monitoring alertmanager-main-1 100m (0%) 100m (0%) 425Mi (1%) 25Mi (0%) 233d
nxs-logging filebeat-lmsmp 100m (0%) 0 (0%) 100Mi (0%) 200Mi (0%) 233d
nxs-monitoring node-exporter-v4gdq 112m (0%) 122m (0%) 200Mi (0%) 220Mi (0%) 233d
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 487m (3%) 822m (5%)
memory 15856217600 (2%) 749976320 (3%)
ephemeral-storage 0 (0%) 0 (0%)Tutaj widzimy wszystkie pody uruchomione na konkretnym węźle, a także zasoby, które są wymagane przez każdy z podów. Oto jak wyglądają logi scheduler'a podczas uruchamiania podu cronjob-cron-events-1573793820-xt6q9 (te informacje pojawią się w logu scheduler'a przy ustawieniu 10. poziomu logowania w argumentach polecenia uruchomienia —v=10):
log
I1115 07:57:21.637791 1 scheduling_queue.go:908] Zaczynam próby zaplanowania podu nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.637804 1 scheduler.go:453] Próbuję zaplanować pod: nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.638285 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s5 jest dozwolone, węzeł uruchamia tylko 16 z 110 podów.
I1115 07:57:21.638300 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s6 jest dozwolone, węzeł uruchamia tylko 20 z 110 podów.
I1115 07:57:21.638322 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s3 jest dozwolone, węzeł uruchamia tylko 20 z 110 podów.
I1115 07:57:21.638322 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s4 jest dozwolone, węzeł uruchamia tylko 17 z 110 podów.
I1115 07:57:21.638334 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s10 jest dozwolone, węzeł uruchamia tylko 16 z 110 podów.
I1115 07:57:21.638365 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s12 jest dozwolone, węzeł uruchamia tylko 9 z 110 podów.
I1115 07:57:21.638334 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s11 jest dozwolone, węzeł uruchamia tylko 11 z 110 podów.
I1115 07:57:21.638385 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s1 jest dozwolone, węzeł uruchamia tylko 19 z 110 podów.
I1115 07:57:21.638402 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s2 jest dozwolone, węzeł uruchamia tylko 21 z 110 podów.
I1115 07:57:21.638383 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s9 jest dozwolone, węzeł uruchamia tylko 16 z 110 podów.
I1115 07:57:21.638335 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s8 jest dozwolone, węzeł uruchamia tylko 18 z 110 podów.
I1115 07:57:21.638408 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s13 jest dozwolone, węzeł uruchamia tylko 8 z 110 podów.
I1115 07:57:21.638478 1 predicates.go:1369] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s10 jest dozwolone, spełnione warunki anty-afinity.
I1115 07:57:21.638505 1 predicates.go:1369] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s8 jest dozwolone, spełnione warunki anty-afinity.
I1115 07:57:21.638577 1 predicates.go:1369] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s9 jest dozwolone, spełnione warunki anty-afinity.
I1115 07:57:21.638583 1 predicates.go:829] Zaplanowanie podu nxs-stage/cronjob-cron-events-1573793820-xt6q9 na węźle nxs-k8s-s7 jest dozwolone, węzeł uruchamia tylko 25 z 110 podów.
I1115 07:57:21.638932 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: BalancedResourceAllocation, pojemność 39900 milikerneli 66620178432 bajtów pamięci, całkowite żądanie 2343 milikerneli 9640186880 bajtów pamięci, wynik 9
I1115 07:57:21.638946 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: LeastResourceAllocation, pojemność 39900 milikerneli 66620178432 bajtów pamięci, całkowite żądanie 2343 milikerneli 9640186880 bajtów pamięci, wynik 8
I1115 07:57:21.638961 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: BalancedResourceAllocation, pojemność 39900 milikerneli 66620170240 bajtów pamięci, całkowite żądanie 4107 milikerneli 11307422720 bajtów pamięci, wynik 9
I1115 07:57:21.638971 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: BalancedResourceAllocation, pojemność 39900 milikerneli 66620178432 bajtów pamięci, całkowite żądanie 5847 milikerneli 24333637120 bajtów pamięci, wynik 7
I1115 07:57:21.638975 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: LeastResourceAllocation, pojemność 39900 milikerneli 66620170240 bajtów pamięci, całkowite żądanie 4107 milikerneli 11307422720 bajtów pamięci, wynik 8
I1115 07:57:21.638990 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: LeastResourceAllocation, pojemność 39900 milikerneli 66620178432 bajtów pamięci, całkowite żądanie 5847 milikerneli 24333637120 bajtów pamięci, wynik 7
I1115 07:57:21.639022 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-nxs-stage -> nxs-k8s-s10: TaintTolerationPriority, Wynik: (10)
I1115 07:57:21.639030 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-nxs-stage -> nxs-k8s-s8: TaintTolerationPriority, Wynik: (10)
I1115 07:57:21.639034 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-nxs-stage -> nxs-k8s-s9: TaintTolerationPriority, Wynik: (10)
I1115 07:57:21.639041 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-nxs-stage -> nxs-k8s-s10: NodeAffinityPriority, Wynik: (0)
I1115 07:57:21.639053 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-nxs-stage -> nxs-k8s-s8: NodeAffinityPriority, Wynik: (0)
I1115 07:57:21.639059 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-nxs-stage -> nxs-k8s-s9: NodeAffinityPriority, Wynik: (0)
I1115 07:57:21.639061 1 interpod_affinity.go:237] cronjob-cron-events-1573793820 -> nxs-k8s-s10: InterPodAffinityPriority, Wynik: (0)
I1115 07:57:21.639063 1 selector_spreading.go:146] cronjob-cron-events-1573793820 -> nxs-k8s-s10: SelectorSpreadPriority, Wynik: (10)
I1115 07:57:21.639073 1 interpod_affinity.go:237] cronjob-cron-events-1573793820 -> nxs-k8s-s8: InterPodAffinityPriority, Wynik: (0)
I1115 07:57:21.639077 1 selector_spreading.go:146] cronjob-cron-events-1573793820 -> nxs-k8s-s8: SelectorSpreadPriority, Wynik: (10)
I1115 07:57:21.639085 1 interpod_affinity.go:237] cronjob-cron-events-1573793820 -> nxs-k8s-s9: InterPodAffinityPriority, Wynik: (0)
I1115 07:57:21.639088 1 selector_spreading.go:146] cronjob-cron-events-1573793820 -> nxs-k8s-s9: SelectorSpreadPriority, Wynik: (10)
I1115 07:57:21.639103 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-nxs-stage -> nxs-k8s-s10: SelectorSpreadPriority, Wynik: (10)
I1115 07:57:21.639109 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-nxs-stage -> nxs-k8s-s8: SelectorSpreadPriority, Wynik: (10)
I1115 07:57:21.639114 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-nxs-stage -> nxs-k8s-s9: SelectorSpreadPriority, Wynik: (10)
I1115 07:57:21.639127 1 generic_scheduler.go:781] Host nxs-k8s-s10 -> Wynik 100037
I1115 07:57:21.639150 1 generic_scheduler.go:781] Host nxs-k8s-s8 -> Wynik 100034
I1115 07:57:21.639154 1 generic_scheduler.go:781] Host nxs-k8s-s9 -> Wynik 100037
I1115 07:57:21.639267 1 scheduler_binder.go:269] AssumePodVolumes dla podu "nxs-stage/cronjob-cron-events-1573793820-xt6q9", węzeł "nxs-k8s-s10"
I1115 07:57:21.639286 1 scheduler_binder.go:279] AssumePodVolumes dla podu "nxs-stage/cronjob-cron-events-1573793820-xt6q9", węzeł "nxs-k8s-s10": wszystkie PVC są powiązane i nic do zrobienia
I1115 07:57:21.639333 1 factory.go:733] Próbuję powiązać cronjob-cron-events-1573793820-xt6q9 z nxs-k8s-s10Tutaj widzimy, że scheduler początkowo filtruje i formuje listę z 3 węzłów, na których możliwe jest uruchomienie (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Następnie dokonuje obliczenia punktów według kilku parametrów (w tym BalancedResourceAllocation, LeastResourceAllocation) dla każdego z tych węzłów w celu określenia najbardziej odpowiedniego węzła. Ostatecznie plan jest przypisany do węzła z największą liczbą punktów (tutaj od razu dwa węzły mają tę samą liczbę punktów 100037, dlatego wybierany jest losowo jeden z nich — nxs-k8s-s10).
Wnioski: Jeśli na węźle działają pody, dla których nie określono ograniczeń, to dla k8s (z punktu widzenia zużycia zasobów) będzie to równoznaczne z tym, jakby na tym węźle takie pody w ogóle nie istniały. Dlatego, jeśli macie, powiedzmy, pod z wymagającym procesem (na przykład, wowza) i dla niego nie określono ograniczeń, może wystąpić sytuacja, w której dany pod zjada wszystkie zasoby węzła, a mimo to dla k8s ten węzeł będzie uważany za niezapracowany i otrzyma taką samą liczbę punktów w rankingu (w punktach z oceną dostępnych zasobów), jak węzeł, na którym nie ma aktywnych podów, co ostatecznie może prowadzić do nierównomiernego rozkładu obciążenia między węzłami.
Wydalenie poda
Jak wiadomo — każdemu podowi przypisywana jest jedna z 3 klas QoS:
- guaranuted — jest przypisywana wtedy, gdy dla każdego kontenera w podzie dla pamięci i CPU określono request i limit, przy czym te wartości muszą być równe
- burstable — przynajmniej jeden kontener w podzie ma request i limit, przy tym request < limit
- best effort — gdy żaden kontener w podzie nie jest ograniczony pod względem zasobów
Przy tym, gdy na węźle występuje niedobór zasobów (dysku, pamięci), kubelet zaczyna oceniać i wydalać pody według określonego algorytmu, który uwzględnia priorytet poda i jego klasę QoS. Na przykład, jeśli chodzi o RAM, to na podstawie klasy QoS przyznawane są punkty według następującej zasady:
- Guaranteed: -998
- BestEffort: 1000
- Burstable: min(max(2, 1000 — (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
T.j. przy takim samym priorytecie kubelet w pierwszej kolejności wydali z węzła pody z klasą QoS best effort.
Wnioski: Jeśli chcesz zmniejszyć prawdopodobieństwo wydalenia potrzebnego poda z węzła w przypadku niedoboru zasobów na nim, to oprócz priorytetu należy również zadbać o określenie request/limit dla niego.
Mechanizm poziomego automatycznego skalowania aplikacji (HPA)
Kiedy pojawia się potrzeba automatycznego zwiększania i zmniejszania liczby podów w zależności od wykorzystania zasobów (systemowych – CPU/RAM lub użytkowych – rps), w jej rozwiązaniu może pomóc taka jednostka k8s jak HPA (Horizontal Pod Autoscaler). Algorytm działa następująco:
- Określane są aktualne wartości obserwowanego zasobu (currentMetricValue)
- Określane są pożądane wartości dla zasobu (desiredMetricValue), które dla zasobów systemowych ustawia się za pomocą request
- Określa się bieżącą liczbę replik (currentReplicas)
- Na podstawie następującego wzoru oblicza się pożądaną liczbę replik (desiredReplicas)
desiredReplicas = [ currentReplicas * ( currentMetricValue / desiredMetricValue )]
Przy tym skalowanie nie nastąpi, gdy współczynnik (currentMetricValue / desiredMetricValue) jest bliski 1 (przy czym dopuszczalny błąd możemy ustawić sami, domyślnie wynosi 0,1).
Rozważmy działanie hpa na przykładzie aplikacji app-test (opisanej jako Deployment), w której należy zmieniać liczbę replik w zależności od zużycia CPU:
Manifest aplikacji
kind: Deployment apiVersion: apps/v1beta2 metadata: name: app-test spec: selector: matchLabels: app: app-test replicas: 2 template: metadata: labels: app: app-test spec: containers: - name: nginx image: registry.nixys.ru/generic-images/nginx imagePullPolicy: Always resources: requests: cpu: 60m ports: - name: http containerPort: 80 - name: nginx-exporter image: nginx/nginx-prometheus-exporter resources: requests: cpu: 30m ports: - name: nginx-exporter containerPort: 9113 args: - -nginx.scrape-uri - http://127.0.0.1:80/nginx-statusTo znaczy, widzimy, że pod z aplikacją uruchamiany jest początkowo w dwóch egzemplarzach, z których każdy zawiera dwa kontenery nginx i nginx-exporter, dla których ustawiono requests dla CPU.
Manifest HPA
apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: app-test-hpa spec: maxReplicas: 10 minReplicas: 2 scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: app-test metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 30To znaczy, że stworzyliśmy hpa, który będzie monitorować Deployment app-test i regulować liczbę podów z aplikacją na podstawie wskaźnika CPU (oczekujemy, że pod powinien zużywać 30% z zarezerwowanego mu CPU), przy czym liczba replik znajduje się w przedziale 2-10.
Teraz rozważmy mechanizm działania hpa, jeśli na jeden z podów zostanie nałożony ładunek:
# kubectl top pod NAME CPU(cores) MEMORY(bytes) app-test-78559f8f44-pgs58 101m 243Mi app-test-78559f8f44-cj4jz 4m 240Mi
W związku z tym mamy następujące:
- Wartość docelowa (desiredMetricValue) — zgodnie z ustawieniami hpa wynosi 30%
- Bieżąca wartość (currentMetricValue) — w celu obliczeń controller-manager oblicza średnie zużycie zasobów w %, tzn. w uproszczeniu robi to w następujący sposób:
- Pobiera absolutne wartości metryk poda z serwera metryk, tzn. 101m i 4m
- Oblicza średnią absolutną wartość, tzn. (101m + 4m) / 2 = 53m
- Pobiera absolutną wartość dla żądanego zużycia zasobów (w tym celu sumuje requesty wszystkich kontenerów) 60m + 30m = 90m
- Oblicza średni procent zużycia CPU w odniesieniu do requestu poda, tzn. 53m / 90m * 100% = 59%
Teraz mamy wszystko, co potrzebne, aby określić, czy należy zmienić liczbę replik, w tym celu obliczamy współczynnik:
ratio = 59% / 30% = 1.96
Tzn. liczba replik powinna zostać zwiększona o około 2 razy i wynosić [2 * 1.96] = 4.
Wynik: Jak można zauważyć, aby ten mechanizm działał, konieczne jest również posiadanie requestów dla wszystkich kontenerów w obserwowanym podzie.
Mechanizm poziomego automatycznego skalowania węzłów (Cluster Autoscaler)
Aby zniwelować negatywne skutki dla systemu w przypadku wzrostu obciążenia, samo posiadanie skonfigurowanego hpa może być niewystarczające. Na przykład, zgodnie z ustawieniami w hpa, manager kontrolera podejmuje decyzję, że liczba replik musi zostać podwojona, jednak na węzłach brakuje dostępnych zasobów do uruchomienia takiej liczby podów (tzn. węzeł nie może dostarczyć żądanych zasobów dla poda requests) i te pody przechodzą w stan Pending.
W takim przypadku, jeśli dostawca posiada odpowiedni IaaS/PaaS (np. GKE/GCE, AKS, EKS itd.), może nam pomóc takie narzędzie jak Node Autoscaler. Pozwala on ustawić maksymalną i minimalną liczbę węzłów w klastrze oraz automatycznie regulować bieżącą liczbę węzłów (poprzez wywołanie API dostawcy chmury w celu zamówienia/usunięcia węzła), gdy zauważa się brak zasobów w klastrze i pody nie mogą być zaplanowane (znajdują się w stanie Pending).
Wynik: Aby móc skorzystać z automatycznego skalowania węzłów, należy określić requesty w kontenerach podów, aby k8s mógł prawidłowo ocenić obciążenie węzłów i odpowiednio informować, że do uruchomienia kolejnego poda brakuje zasobów w klastrze.
Podsumowanie
Warto zauważyć, że ustawienie ograniczeń zasobów kontenera nie jest warunkiem koniecznym do pomyślnego uruchomienia aplikacji, jednak nadal lepiej to zrobić z następujących powodów:
- Aby zwiększyć dokładność działania harmonogramu w zakresie równoważenia obciążenia między węzłami k8s
- Aby zmniejszyć prawdopodobieństwo wystąpienia zdarzenia „wypierania poda”
- Aby zrealizować poziome autoskalowanie podów aplikacji (HPA)
- Aby zrealizować poziome autoskalowanie węzłów (Cluster Autoscaling) u dostawców chmurowych
Przeczytaj również inne artykuły na naszym blogu:
Źródło: habr.com
