pixel
Kubernetes cluster erőforrásigény

Hogyan csökkentettük felére a Kubernetes cluster erőforrásigényét ingyenes eszközökkel

Egy production Kubernetes cluster hatékony üzemeltetése az egyik olyan feladat, amely elméletben egyszerűnek tűnik, de a gyakorlatban számos meglepetést tartogat. Nemrégiben elvégeztünk egy átfogó erőforrás-optimalizálást egy production K3s clusteren, amely adatbázisokat, megfigyelhetőségi stacket, levelezőszervert, webalkalmazásokat és más workloadokat futtatott. Az eredmény figyelemre méltó: egyetlen új node hozzáadása nélkül, cloud provider váltás nélkül és drága külső FinOps eszközök nélkül a CPU limits commitment értéke 105%-ról 77.7%-ra csökkent, a megfigyelhetőségi névtér memóriahasználata pedig több mint 70%-kal mérséklődött.

Ez a cikk végigvezet a metodológián, a konkrét megállapításokon és azon tanulságokon, amelyek szinte minden Kubernetes környezetben alkalmazhatók.

Miért fontosabb az erőforrás-hangolás, mint gondolnánk

A Kubernetesben két különböző erőforrás-fogalmat érdemes megkülönböztetni, amelyeket az üzemeltetők gyakran összekevernek: a request és a limit értékeket.

A request az, amit a scheduler a pod elhelyezésekor figyelembe vesz. Ha egy node az összes aktív request alapján 4 CPU core kapacitással rendelkezik, egy 2 core-t kérő új pod oda kerül ütemezésre — függetlenül attól, hogy a meglévő podok ténylegesen mit fogyasztanak.

A limit a futásidei plafont határozza meg. Memória esetén a limit túllépése OOMKill-t eredményez. CPU esetén a kernel throttle-özi a konténert, ami látens növekedést okoz összeomlás nélkül.

Ez a különbség két eltérő hibamódot eredményez:

  • A túl magas request értékek azt a látszatot keltik a scheduler számára, hogy a cluster tele van, holott fizikai kapacitás bőven rendelkezésre áll.
  • A túl magas limit értékek esetén a limits commitment metrika meghaladhatja a 100%-ot, ami azt jelenti, hogy ha minden pod egyszerre érné el a limitjét, a cluster túlterhelt lenne. CPU esetén ez throttlinget okoz, ami kiszámíthatatlan lassulásként jelentkezik terhelés alatt.

Egyik helyzet sem veszélyes önmagában, de mindkettő fölösleges kapacitásveszteséget jelent, amelyet egy adatvezérelt megközelítéssel szisztematikusan fel lehet tárni és megszüntetni.

Az induló állapot felmérése

Az első lépés egy kiindulási állapot rögzítése volt a cluster meglévő Prometheus és Grafana stackjével. A kezdeti értékek a következők voltak:

MetrikaKezdeti érték
CPU kihasználtság5,30%
CPU requests commitment47,5%
CPU limits commitment105%
Memória kihasználtság22,6%
Memória requests commitment42,8%
Memória limits commitment66,7%

A 100%-ot meghaladó CPU limits commitment azonnal szembeötlő. Ha a fizikai CPU kihasználtság csupán 5,3%, de a limitek 105%-on állnak, az azt jelenti, hogy a beállított limitek nagyjából 20-szorosát teszik ki a tényleges felhasználásnak. Ez klasszikus tünete az alapértelmezett vagy copy-paste erőforrás-konfigurációknak, amelyeket az üzembe helyezés után soha nem vizsgáltak felül.

1. lépés: A memóriahasználat fő okának azonosítása

A vizsgálat névtér szintű bontással kezdődött. Egy egyszerű kubectl top pods -A --sort-by=memory parancs kimutatta, hogy a megfigyelhetőségi névtér több mint 11 GB memóriát fogyaszt — ez az adatbázis cluster után a második legnagyobb fogyasztó.

A legnagyobb egységes pod egy 8 GB-os Memcached instance volt: a Loki chunks cache.

Ez egy Helm-menedzselt deployment volt, amelynek values fájljában a chunksCache blokk ki volt kommentelve. A Grafana Loki Helm chart alapértelmezett allocatedMemory értéke 8192 MB — azaz 8 GB —, amelyet a Memcached az induláskor azonnal lefoglal, függetlenül a tényleges terheléstől.

A javítás kézenfekvő volt: a values fájlban explicit módon be kellett állítani a cache méretét az alapértelmezett helyett:

chunksCache: 
enabled: true
allocatedMemory: 1024 # alapértelmezett 8192 MB helyett
resources:
requests:
memory: 1152Mi
cpu: 100m
limits:
memory: 1536Mi
cpu: 500m

Kis-közepes production cluster esetén 1 GB chunks cache teljesen elegendő. Az eredmény: egyetlen konfigurációs sor módosításával több mint 7 GB szabadult fel, a lekérdezési teljesítményre mérhető hatás nélkül.

Az ebből levonható tanulság széleskörűen alkalmazható: mindig explicit módon konfiguráljuk a Helm chart cache méreteit. A chart-ok alapértelmezett értékeit nagy léptékű deploymentekhez tervezték, és kisebb clusterek számára rendszerint nem megfelelők.

2. lépés: Az adatbázis erőforrásainak optimalizálása

A második legnagyobb memóriafogyasztó egy 3 csomópontos MariaDB Galera cluster volt, amely összességében körülbelül 22 GB memóriát használt. Az eredeti pod konfiguráció:

resources: 
limits:
cpu: "1000m"
memory: "8Gi"
requests:
cpu: "500m"
memory: "6Gi"

Az InnoDB buffer pool node-onként 4 GB-ra volt beállítva. MySQL/MariaDB memóriahasználat szempontjából itt érdemes először keresni — a buffer pool szándékosan a memóriában tartott adat, és ez dominálja a folyamat memóriakiosztását.

Mielőtt bármilyen változtatást végeztünk, lekérdeztük az adatbázis tényleges állapotát:

-- Tényleges adatbázis méret 
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024 / 1024, 2) AS 'DB Size (GB)' FROM information_schema.tables;
-- Eredmény: 3,72 GB

-- Buffer pool találati arány
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
-- read_requests: 8 445 733 405
-- reads: 129 755
-- Találati arány: 99,9985% ```

A 99,9985%-os találati arány kiváló — szinte minden kiszolgálás memóriából történik. Ugyanakkor 3,72 GB tényleges adatmennyiségnél egy 4 GB-os buffer pool azt jelenti, hogy a teljes adatbázis elfér a cache-ben. A 3 GB-ra való csökkentés ugyanolyan hatékony működést biztosít, miközben érdemi memóriát szabadít fel.

ini innodb_buffer_pool_size=3G # 4G helyett

A CPU oldalon az adatok más képet mutattak:

mariadb-galera-0 30m CPU mariadb-galera-1 47m CPU mariadb-galera-2 39m CPU

500m request és 1000m limit mellett a 30–47m tényleges használat azt mutatja, hogy a requestek messze felülméretezettek. A helyes reakció azonban nem a limit csökkentése volt, hanem a request csökkentése a limit megtartása vagy emelése mellett:

resources: 
requests:
cpu: "100m" # 500m helyett — a tényleges használatot tükrözi
limits:
cpu: "2000m" # emelve — védelmet nyújt a Galera SST burst ellen

A Galera State Snapshot Transfer (SST) folyamata node újraszinkronizálás során rövid ideig jelentősen megemeli a CPU-terhelést. Túl alacsony limit esetén a throttling pont a klaszter helyreállítási folyamatát lassítaná — pontosan akkor, amikor maximális teljesítményre van szükség.

Ez a minta — alacsony request, bőkezű limit — az állapotos workloadok esetén a helyes megközelítés.

3. lépés: Túlméretezett requestek azonosítása cluster-szinten

A nagyobb memóriaproblémák megoldása után a figyelem a CPU limits commitment problémájára terelődött. A megközelítés az volt, hogy összeszámoltuk az összes pod CPU request és limit értékét a teljes clusterben:

kubectl get pods -A -o json | jq -r ' .items[] | .metadata.namespace + "/" + .metadata.name + " → req: " + (.spec.containers[].resources.requests.cpu // "none") ' | sort

Ezt az eredményt a kubectl top pods -A --sort-by=cpu kimenetével összevetve több jellegzetes minta rajzolódott ki:

1. minta: Inaktív alkalmazásszerverek magas statikus requestekkel

Több webalkalmazás pod 500m CPU requestet igényelt, miközben a tényleges felhasználás 15m alatt maradt — ez 33-szoros túlfoglalás. A requestek 10–50m-re csökkentése megfelelő limitek megtartásával számottevő scheduler kapacitást szabadított fel teljesítményveszteség nélkül.

2. minta: DaemonSet komponensek felfújt limitekkel

Öt node-exporter pod (node-onként egy) mindegyike 1000m CPU limittel rendelkezett. A node-exporter egy könnyűsúlyú metrikagyűjtő, amely jellemzően 5–15m CPU-t használ. Az egyes podok limitjét 100m-re csökkentve összességében 4500m limit-foglalás szűnt meg.

3. minta: Sidecar konténerek alapértelmezett konfigurációkkal

Több többkonténeres podban az nginx sidecar konténerek 1000m CPU requestet igényeltek — lényegében ugyanannyit, mint a fő alkalmazáskonténer. A reverse proxyként működő sidecar konténerek a gyakorlatban ritkán fogyasztanak 10–20m-nél többet. Ezek korrigálása több mint 2000m ütemezett requestet szabadított fel.

4. minta: Felesleges cache replikák

Alkalmazásszintű gyorsítótárazásra használt Redis deploymenteknél több replika volt konfigurálva. Egy kizárólag olvasási teljesítménynövelés céljából létező cache esetén, amelyet nem perzisztencia vagy magas rendelkezésre állás indokol, a replikák semmiféle előnnyel nem járnak. A felesleges Redis replikák megszüntetése mind CPU, mind memória szempontjából csökkentette az erőforrásigényt.

4. lépés: Alkalmazásszintű hangolás

Az egyik Nextcloud deployment indulási logjaiból érdekes probléma tárult fel:

WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 8 children, there are 48 idle, and 57 total children

A PHP-FPM pool konfigurációja volt a hibás:

pm = dynamic 
pm.start_servers = 50
pm.min_spare_servers = 50
pm.max_spare_servers = 100
pm.max_children = 1000

50 worker indítása pod startup során, ahol minden worker 30–40 MB memóriát foglal, körülbelül 1,5–2 GB-ot jelent csupán tétlen PHP workerekre, még mielőtt egyetlen kérés is érkezne. A max_children = 1000 érték különösen veszélyes — ha a PHP-FPM valaha megpróbálná teljesíteni ezt, 30–40 GB memóriára lenne szükség, ami messze meghaladja a pod memórialimitjét.

A helyesbített konfiguráció:

pm = dynamic 
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 500

Ez reális párhuzamossági szintet tükröz egy egybérlős Nextcloud instance esetén, miközben a memóriaigény kezelhető marad.

Az eredmények

Az összes optimalizálási lépés elvégzése után a cluster metrikái a következő állapotba kerültek:

MetrikaElőtteUtánaVáltozás
CPU kihasználtság5,30%3,93%-1,37pp
CPU requests commitment47,5%30,3%-17,2pp
CPU limits commitment105%77,7%-27,3pp
Memória kihasználtság22,6%20,4%-2,2pp
Memória requests commitment42,8%38,0%-4,8pp
Memória limits commitment66,7%58,2%-8,5pp

A CPU limits commitment 105%-ról 77,7%-ra való csökkenése a legfontosabb eredmény: a cluster egy elméletileg túlfoglalt állapotból egészséges növekedési tartalékkal rendelkező állapotba került. A 58,2%-os memória limits commitment kényelmes védelmet nyújt az OOMKill kockázattal szemben cluster-szinten.

Kiemelendő, hogy egyetlen workload sem romlott le. Minden alkalmazás zavartalanul működött az optimalizálás teljes folyamata alatt, amelyet gördülő Helm frissítésekkel és inkrementális deployment módosításokkal hajtottunk végre.

Legfontosabb tanulságok

Ez a projekt több olyan alapelvet erősített meg, amelyek általánosan alkalmazhatók a Kubernetes erőforrás-menedzsmentben:

1. Mérjünk, mielőtt hangolunk. A projekt minden egyes változtatása tényleges használati adatokon alapult a kubectl top kimenetéből vagy Prometheus metrikákból. A találgatás — még ha megalapozott is — fölösleges pazarláshoz vagy instabilitáshoz vezet.

2. A request és a limit különböző célokat szolgál. A request az ütemezést befolyásolja; a limit a futásidei viselkedést szabályozza. A requestek optimalizálása a scheduler hatékonyságát és a cluster kapacitását javítja. A limitek optimalizálása csökkenti az elméleti túlfoglalást és a „zajos szomszéd” hatás kockázatát.

3. A Helm chart alapértelmezések nem production alapértelmezések. A chart karbantartók általánosított, nagyobb léptékű deploymentekre szabják az alapértékeket. Mindig vizsgáljuk felül a cache méreteket, replikaszámokat és erőforrás blokkokat minden Helm chart esetén, mielőtt production környezetbe telepítjük.

4. A CPU és a memória eltérően viselkedik terhelés alatt. A memórialimit túllépése OOMKill-t okoz — hirtelen és rendszerint zavaró. A CPU-limit túllépése throttlingot okoz — láthatatlan és latenció-növekedésként jelentkezik. Ez az aszimmetria azt jelenti, hogy a memória limitek konzervatívabb tartalékot igényelnek, mint a CPU limitek.

5. Az állapotos workloadok aszimmetrikus CPU-profilt igényelnek. Az adatbázisoknak és klaszterezett rendszereknek erősen változó CPU-igényük van — alacsony normál üzemben, magas helyreállításkor, replikációs felzárkózáskor vagy tömeges műveletek során. Az alacsony request nagy limittel párosítva az itt alkalmazandó helyes minta.

6. Figyeljük a változásokat a módosítások után. Az InnoDB buffer pool találati arányát, a PHP-FPM worker számokat és a cache találati arányokat érdemes rendszeresen ellenőrizni a hangolás után. A workload növekedése idővel megváltoztathatja az optimális konfigurációt.

Mi következik ezután

Miután a cluster egészséges állapotba került, a természetes következő lépés a figyelmeztetési küszöbök beállítása:

  • Figyelmeztetés, ha a CPU limits commitment meghaladja a 85%-ot — ez jelzi, hogy node bővítést kell tervezni
  • Figyelmeztetés, ha a memória limits commitment meghaladja a 75%-ot — korai figyelmeztetés, mielőtt az OOMKill kockázat valóssá válna
  • Figyelmeztetés, ha bármely névtér tényleges memóriahasználata meghaladja a deklarált request 80%-át — azokat a workloadokat azonosítja, amelyek kinőtték a beállított profiljukat

Ezek a jelzések proaktív kapacitástervezést tesznek lehetővé a reaktív tűzoltás helyett — ami végső soron minden Kubernetes környezet FinOps gyakorlatának célja.

Ha szervezetük Kubernetes workloadokat futtat, és szisztematikusan szeretné csökkenteni a cloud költségeket és javítani a cluster hatékonyságát, egy ilyen strukturált erőforrás-audit kiváló kiindulási pont. A szükséges eszközök — Prometheus, Grafana és kubectl — a legtöbb production clusterben már rendelkezésre állnak. Az érték abban rejlik, hogy tudjuk, mit keressünk, és hogyan értelmezzük, amit találunk.

Ha szakmai segítségre van szüksége Kubernetes optimalizálásban és Fractional DevOps & Cloud FinOps szolgáltatások terén, keressen minket bizalommal.

Ajánlott külső hivatkozások:

Shopping Cart
Scroll to Top