Zarządzanie CPU w Kubernetes – request i limit 4 kwietnia 2026 | 9 min czytania

Zarządzanie CPU w Kubernetes – request i limit

Kubernetes jest z nami już od 11 lat, a zarządzanie zasobami to jedna z jego najbardziej podstawowych funkcji. Mimo to wciąż pozostaje jednym z najczęstszych problemów, które obserwujemy w projektach. Wiele osób nie wie, jak poprawnie korzystać z mechanizmów zarządzania zasobami albo używa ich w niewłaściwy sposób. To nadal jeden z najbardziej dyskutowanych tematów w świecie Kubernetes. W tym artykule chciałbym wyjaśnić, jak działa CPU request i CPU limit. W tym poście skupimy się na CPU, a w kolejnej części omówimy zarządzanie pamięcią.

Jak Kubernetes zarządza zasobami?

Odpowiedź brzmi: cgroups.

Cgroups w wersji v1 są dostępne od jądra Linux 2.6.24 (styczeń 2008). To fundament technologii konteneryzacji takich jak Docker, Podman, LXC. Obecnie większość systemów używa cgroups v2, które zostały uznane za stabilne w kernelu 4.5 w 2016 roku - i do tej wersji będziemy się dalej odnosić. „Grupy kontrolne, zwykle nazywane cgroups, to funkcja jądra Linux umożliwiająca organizowanie procesów w grupy hierarchiczne, których wykorzystanie różnych typów zasobów można następnie ograniczać i monitorować." - https://man7.org/linux/man-pages/man7/cgroups.7.html W praktyce oznacza to, że wszystkie procesy w kontenerze mogą zostać umieszczone w jednej grupie cgroup, której ustawiamy parametry kontrolerów, takich jak CPU czy memory. Kubelet, który odpowiada za tworzenie kontenerów, wykorzystuje m.in. kontrolery:

  • cpu
  • cpuset
  • memory
  • hugetlb
  • pids

W kontekście ustawianie requestów i limitów CPU interesuje nas cpu controller.

Zgodnie z dokumentacją https://www.kernel.org/doc/Documentation/admin-guide/cgroup-v2.rst “Kontrolery „CPU” regulują rozkład cykli procesora. Ten kontroler implementuje modele wag i bezwzględnych limitów przepustowości dla standardowej polityki harmonogramowania (normal scheduling policy) oraz model bezwzględnej alokacji przepustowości dla polityki harmonogramowania w czasie rzeczywistym.” Skupimy się na standardowym trybie planowania (normal scheduling), ponieważ tak domyślnie działa Kubernetes. Po wprowadzeniu pewnych zmian możliwe jest uruchamianie aplikacji w czasie rzeczywistym na platformie Kubernetes, jednak nie jest to domyślna platforma dla tego typu obciążeń i nie jest jeszcze obsługiwana w wersji 2 cgroups.

CPU Request – model wag (weight)

CPU request w Kubernetes odpowiada modelowi wag w cgroups. To ilość CPU, którą gwarantujemy sobie pod obciążeniem. Jeśli ustawimy w Deployment:

resources:
  requests:
    cpu: 500m

Kubernetes przelicza to na wartość shares (w cgroups v1) lub weight (w cgroups v2). Funkcją MilliCPUToShares()

// MilliCPUToShares converts the milliCPU to CFS shares.
func MilliCPUToShares(milliCPU int64) uint64 {
	if milliCPU == 0 {
		// Docker converts zero milliCPU to unset, which maps to kernel default
		// for unset: 1024. Return 2 here to really match kernel default for
		// zero milliCPU.
		return MinShares
	}
	// Conceptually (milliCPU / milliCPUToCPU) * sharesPerCPU, but factored to improve rounding.
	shares := (milliCPU * SharesPerCPU) / MilliCPUToCPU
	if shares < MinShares {
		return MinShares
	}
	if shares > MaxShares {
		return MaxShares
	}
	return uint64(shares)
}

W uproszczeniu:

shares = milliCPU * 1024 / 1000

Dla naszego ustawionego requestu o wartości 500m działaniem będzie: 500 * 1024 / 1000 = 512

Ta wartość trafia do części cgroups, która jest odpowiedzialna za alokację czasu procesora. W cgroups v1 jest to parametr cpu.shares, a w cgroups v2 jest to cpu.weight. Oznacza to, że nasz pod będzie miał wagę 512 w stosunku do innych podów, które mają swoje własne wartości shares/weight.

Następnie jest używana przez scheduler Linux. W wielu dystrybujach z wersją jądra powyżej 6.6 takich jako Amazon Linux 2023, Ubuntu 22.04.5+, 24.04+, Red Hat Enterprise Linux 10 jest już nowsza wersja schedulera EEVDF. Scheduler w systemie Linux to część jądra, która obsługuje alokację zasobów procesora. Nawet jeśli czasami w kodzie Kubernetesa widzisz nazwę metryki „CFS”, która odnosi się do starszego schedulera, który został włączony do jądra Linux w październiku 2007 roku w wersji 2.6.23, Twój node nadal korzysta z schedulera dostarczonego przez system Linux.

Jak działa podział CPU?

Załóżmy że mamy 3 pody i dla każdego z nich została utworzona cgroupa z następującymi wartościami:

cgroupmilliCPU parametr
G1150
G2100
G350

Łączna suma weight wynosi 300. Scheduler Linux będzie alokował czas procesora proporcjonalnie do tych wag. Oznacza to, że jeśli wszystkie trzy cgroupy są aktywne i konkurują o czas procesora. Możemy również przeliczyć shares = milliCPU * 1024 / 1000, aby zobaczyć, jak to się przekłada na procenty:

cgroupmilliCPU parametrAlokacja czasu CPUshares
G1150~50% (150/300)~50% (153/306)
G2100~33% (100/300)~33% (102/306)
G350~16%(50/300)~16%(51/306)

Ustawiamy, ile czasu każda z grup cgroup może uzyskać przy pełnym wykorzystaniu procesora. Dotyczy to tylko sytuacji, gdy procesor jest w pełni zajęty. Jeśli Pod A w G1 potrzebuje tylko 25 cykli, pozostałe cykle zostaną rozdzielone zgodnie z wagami innych cgroup. Request to minimalny gwarantowany udział w warunkach pełnego obciążenia, a nie limity. Zatem jeśli inne kontenery nie działają, może on wykorzystać znacznie więcej czasu. Jak widać z tej tabeli, ustawienie CPU > 1 dla danego poda nie oznacza, że otrzyma on więcej niż jeden rdzeń. Oznacza to jedynie, że otrzyma znacznie więcej czasu procesora gdy pody będą konkurować o zasoby.

Co gdy suma requestów przekracza 1 CPU?

Ustawienie CPU > 1 dla danego poda nie oznacza, że będzie on miał więcej niż jeden rdzeń. Oznacza to jedynie, że będzie miał znacznie więcej czasu procesora. Załóżmy, że mamy 3 pody z requestami ponad 1000m i ich suma ti 2800m:

cgroupmilliCPU parametrAlokacja czasu CPUshares
G11600~57% (1600/2800)~57% (1600/2800)
G2600~21,5% (600/2800)~21,5% (600/2800)
G3600~21,5% (600/2800)~21,5% (600/2800)

Podział nadal jest proporcjonalny: Scheduler nie interesuje się tym, że suma przekracza 1 rdzeń. Działa wyłącznie na relacjach wag. 100m to zawsze ta sama waga — ale na większej maszynie ta sama waga przekłada się na większą realną moc obliczeniową.

CPU Limit – model bandwidth (quota)

Teraz, gdy wiemy już, czym jest request, możemy przyjrzeć się bliżej limitowi procesora. W naszym kontrolerze CPU mamy też inny model dystrybucji czasu CPU, który nazywa się „absolute bandwidth limit model”. Ponownie musimy przekonwertować nasze jednostki Kubernetes z ustawień poda na ustawienia cgroups. Funkcja używana do tego celu to MilliCPUToQuota .

// MilliCPUToQuota converts milliCPU to CFS quota and period values.
// Input parameters and resulting value is number of microseconds.
func MilliCPUToQuota(milliCPU int64, period int64) (quota int64) {
	// CFS quota is measured in two values:
	//  - cfs_period_us=100ms (the amount of time to measure usage across given by period)
	//  - cfs_quota=20ms (the amount of cpu time allowed to be used across a period)
	// so in the above example, you are limited to 20% of a single CPU
	// for multi-cpu environments, you just scale equivalent amounts
	// see https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt for details

	if milliCPU == 0 {
		return
	}

	if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CPUCFSQuotaPeriod) {
		period = QuotaPeriod
	}

	// we then convert your milliCPU to a value normalized over a period
	quota = (milliCPU * period) / MilliCPUToCPU

	// quota needs to be a minimum of 1ms.
	if quota < MinQuotaPeriod {
		quota = MinQuotaPeriod
	}
	return
}

Domyślnie wartość cfs_period_us wynosi 100 ms. Zatem nasz limit (quota) wynosi:

quota = (milliCPU * okres) / MilliCPUToCPU

Na przykład:

(500m * 100) / 1000 = 50 ms

Wiemy, że nasz pod może działać maksymalnie 50 ms w ciągu 100 ms mierzonego czasu. Po wykorzystaniu tego czasu cgroup throttluje poda do następnego okresu.

Co się więc stanie, jeżeli nasze ustawienia będą wyglądać następująco: shares = 512 quota = 50ms per 100ms (hard cap)

Jeżeli spróbuje zająć więcej niż 50% czasu procesora w tym okresie to:

  1. Działa
  2. Wykorzystuje quote
  3. Throttling
  4. Czeka na kolejny okres
  5. Powrót do punktu 1

To powoduje throttling procesora.

Czy CPU limit ma sens?

Jak zawsze „to zależy”. Wiele aplikacji może być bezczynnych przez większość czasu, czekając na żądanie przetworzenia czegoś, a następnie działać przez krótki okres czasu. Na przykład API czeka 200 ms, wykonuje 20 ms pracy procesora, a następnie ponownie czeka na odpowiedź. Przy ustawieniu limitu na 10 ms będziemy potrzebować dwóch okresów, aby ukończyć tę samą ilość pracy, a inna część systemu będzie musiała poczekać. Przy ustawieniu limitu zatrzymujemy jego zdolność do szybkiego przetwarzania danych, nawet jeśli nasz procesor ma jeszcze wystarczającą moc, aby to zrobić bez współdzielenia cykli z innymi aplikacjami. Przy żądaniu nadal zapewniamy, że nasza aplikacja otrzyma odpowiednią ilość czasu podczas obciążenia procesora, a główną ideą stojącą za przetwarzaniem w chmurze, Kubernetesem, automatycznym skalowaniem i Karpenterem jest to, że wykorzystujemy nasze zasoby tak efektywnie, jak to możliwe. Większość aplikacji działa w sposób burstowy:

  • czeka na request
  • wykonuje krótki fragment pracy
  • znów czeka

Przykład: API czeka 200ms, wykonuje 20ms pracy CPU i znów czeka. Jeśli limit ustawimy na 10ms, wykonanie tej samej operacji zajmie dwa okresy zamiast jednego, mimo że na nodzie może być dostępne wolne CPU. Limit ogranicza możliwość szybkiego przetwarzania danych, nawet gdy nie ma realnej walki o zasoby. Request zapewnia uczciwy podział pod obciążeniem, a jednocześnie pozwala wykorzystać wolne zasoby. To jest sedno cloud computingu, autoscalingu i efektywnego wykorzystania infrastruktury.

Kiedy CPU limit ma sens?

  • środowiska multi-tenant
  • nieufne workloady
  • ochrona przed runaway procesami
  • kontrola kosztów
  • dostawcy platform PaaS

W takich przypadkach limit daje izolację i przewidywalność. Jeśli jednak jesteś właścicielem klastra i masz kontrolę nad workloadami i poprawny monitoring - w wielu przypadkach CPU limit nie jest konieczny. Limit ten stanowi dodatkową warstwę bezpieczeństwa chroniącą resztę obciążenia klastra w przypadku jego nieprawidłowego działania lub popełnienia błędu podczas profilowania aplikacji.

Ten artykuł nie obejmuje:

  • Narzutu związanego ze środowiskiem wykonawczym kontenera (modelowanym za pomocą klasy RuntimeClass).
  • QoS dla kontenerów.

Oczywiście zdarzają się sytuacje, gdy jesteśmy dostawcami lub zarządzamy klastrami i chcemy mieć pewność, że niektórzy programiści nie mają aplikacji powodujących bardzo wysokie skoki obciążenia procesora, chcemy mieć pewność, że nikt nie wdroży nieskończonej pętli, koparki kryptowalut lub chcemy kontrolować koszty, ale jest to kwestia, którą każda organizacja musi podjąć w oparciu o swój przypadek użycia. W środowiskach wielodostępnych lub niezaufanych limity procesora zapewniają bezpieczeństwo i izolację. Jeśli jesteś właścicielem klastra i Twój zespół jest odpowiedzialny za zarządzanie tym klastrem, moim zdaniem nie jest to konieczne. Lub sytuacje takie jak:

  • Uniemożliwienie testowania obciążenia odizolowanej usługi z wykorzystaniem całej maszyny w środowisku testowym, gdy usługa będzie miała tylko część maszyny w środowisku produkcyjnym
  • Ustalenie sztywnego budżetu, który ogranicza zasoby dostępne dla usługi ze względów harmonogramowania, bezpieczeństwa, rozliczeniowych lub księgowych
  • Jeśli zdecydujesz się na użycie zarządzanej usługi do uruchomienia kontenerów, takiej jak ECS, usługi te wymagają ustawienia limitów procesora (i pamięci).
  • Ten wymóg pomaga dostawcom w alokacji zasobów i działa również na Twoją korzyść. Płacisz za alokację zasobów, a nieograniczone wykorzystanie oznacza nieograniczone koszty.

Decyzje dotyczące zarządzania zasobami, takie jak ustawienie CPU limitów, wydają się proste — ale ich wpływ na latencję, skalowalność i koszty może być znaczący.

Jeśli chcesz zweryfikować architekturę Kubernetes, wyeliminować problemy z ograniczaniem przepustowości lub poprawić wydajność klastra, nasz zespół oferuje kompleksowe doradztwo techniczne oparte na rzeczywistym doświadczeniu.