Nagyjából két éve üzemeltetünk olyan klasztereket, ahol ML training jobok futnak a sima service-ek mellett. A legfájóbb pont mindig az ütemezés volt. Egy elosztott trainingből pár pod elindult, a többi meg Pendingben ragadt, közben a GPU-k csak vitték a pénzt.

A Kubernetes 1.35 múlt héten jött ki, ezért a hétvégén rászántam az időt, és végigteszteltem stagingen. Több újdonság is olyan, amit tényleg vártam.

Gang Scheduling, Végre

A legnagyobb változás a workload-aware scheduling, benne a gang scheduling támogatással. Ez még alpha, tehát productionbe most még nem raknám be, de az irány nagyon jó: egy podcsoport vagy együtt ütemeződik be, vagy sehogy.

Korábban mindenféle kerülőutat használtunk. Volcano, Coscheduling plugin, saját script, ami figyelte a részleges indulást és takarított utána. Működött, csak törékeny volt.

Az új API így néz ki:

apiVersion: scheduling.k8s.io/v1alpha1
kind: Workload
metadata:
  name: training-run-042
spec:
  podSets:
  - name: workers
    count: 4
    template:
      spec:
        containers:
        - name: trainer
          image: my-registry/llm-trainer:v3.2
          resources:
            limits:
              nvidia.com/gpu: 1
  schedulingPolicy:
    gangScheduling:
      mode: Strict

A Strict mód azt jelenti, hogy mind a négy pod egyszerre megy, vagy egyik sem. Nincs olyan helyzet, hogy három pod fut, a negyedik vár, és közben feleslegesen ég a GPU-idő.

Ezt egy 4 node-os GPU klaszteren teszteltem, node-onként 2 A100-zal. A 4 GPU-t kérő workload szépen egyszerre indult el. Utána kértem 6 GPU-t egy olyan klaszteren, ahol csak 5 volt szabad. A workload Waiting státuszban maradt, nem indult részlegesen. Pont erre volt szükség.

kubectl get workloads
NAME                STATUS    AGE
training-run-042    Running   2m
training-run-043    Waiting   45s

kubectl describe workload training-run-043
# ...
# Message: Insufficient nvidia.com/gpu: requested 6, available 5

Fontos: a WorkloadAwareScheduling feature gate alapból ki van kapcsolva. A kube-schedulerben és a kube-apiserverben is be kell kapcsolni:

# kubeadm configban vagy static pod manifestekben:
--feature-gates=WorkloadAwareScheduling=true

In-Place Pod Resize Most Már Stabil

Ez a funkció 1.27 óta érik, mostanra lett GA. Futó podon tudsz CPU- és memórialimitet módosítani úgy, hogy a konténer nem indul újra.

Inference service-eknél ez óriási előny. Ha megugrik a terhelés, feljebb húzod a CPU-t, és minden megy tovább. Nincs cold start, nincs újracsatlakozási hullám, nincs új modellbetöltés.

kubectl patch pod inference-server-abc123 --subresource resize \
  --type merge -p '{"spec":{"containers":[{"name":"server","resources":{"limits":{"cpu":"4"}}}]}}'

Egy ONNX inference podot próbáltam 2-ről 4 CPU magra emelni. A pod végig futott, a latency pár másodperc alatt javult. Visszafelé is működött. Memóriánál van egy fontos peremfeltétel: ha az aktuális memóriahasználat már magasabb, mint az új limit, a resize elutasításra kerül. A podot nem lövi le, ami jó hír.

kubectl get pod inference-server-abc123 -o jsonpath='{.status.resize}'
# "InProgress" -> "Completed"

Ami meglepett: a HPA már tud építeni erre. Először vertikálisan méretez, és csak utána skáláz horizontálisan. A VPA integráció várhatóan 1.36-ban jön, de már most is hasznos custom metrikákkal.

A KYAML Lett az Alapértelmezett kubectl Kimenet

Ez könnyen okozhat meglepetést. A kubectl mostantól KYAML-t ad vissza alapból, nem sima YAML-t. A KYAML szigorúbb formátum, és több tipikus YAML-csapdát kiszűr, például amikor a NO szöveg boolean false-ként értelmeződik.

Ha vannak scripted, amik kubectl kimenetet parse-olnak, frissítés előtt mindenképp tesztelj. A legtöbb rendben lesz, de az élő rendszerekben pont a sarkos esetek fájnak. Ideiglenesen vissza tudod kapcsolni:

export KUBECTL_KYAML=false

Nálunk a CI pipeline futtatásakor két hibát találtam:

  1. Egy script a ConfigMap yes és no értékeire épített, de most idézőjeles formában jönnek, ezért eltört egy string összehasonlítás.
  2. Néhány többsoros string megjelenítése kicsit eltérő flow style-t használ.

Mindkettő gyorsan javítható volt, de production közben bosszantó lett volna kibogozni.

A DRA Tovább Erősödik

A Dynamic Resource Allocation nem új 1.35-ben, de láthatóan érik. A device claim-ek kiszámíthatóbbak lettek, és a scheduling hint-ek jobban együtt mozognak a gang schedulinggel.

A GPU-s workloadjainknál azt láttam, hogy gyorsabban oldódnak fel a DRA claim-ek. 1.34-en néha 10-15 másodperc is eltelt a pod ütemezése és a GPU allokáció között. 1.35-ön ez jellemzően három másodperc alatt marad. Még nem bontottam szét, hogy konkrét javításról van-e szó, vagy több kisebb scheduler fejlesztés hatása adódik össze.

Mit Változtattam a Klasztereinkben

A tesztek után ezeket vezetem be:

  1. Bekapcsolom a gang schedulinget stagingen az elosztott training jobokhoz, és legalább egy hónapig figyelem.
  2. Az inference podokat átállítom in-place resize-ra a jelenlegi töröld-és-hozd-létre-újra folyamat helyett.
  3. A CI scripteket felkészítem KYAML kompatibilitásra a production upgrade előtt.
  4. A DRA konfig marad, de monitorozom az allokációs időket.

A Nagy Kép

Jól látszik, hogy a Kubernetes egyre inkább az AI infrastruktúra alaprétege akar lenni. A gang scheduling, az in-place resize és a DRA együtt már tényleg használható csomagot ad komoly ML workloadokra is.

Egy éve még gondolkodás nélkül azt mondtam volna, hogy multi-node GPU traininghez inkább Slurm. Most már nem ilyen egyértelmű. A Kubernetes 1.35 a gyakori esetek nagy részét elég jól kezeli ahhoz, hogy az egyplatformos üzemeltetés előnye erősen számítson.

A fő kockázat továbbra is az, hogy a gang scheduling alpha. Inference fókuszú klasztereknél viszont az in-place resize stabilitása önmagában is erős érv a frissítés mellett.

Ha upgrade-et tervezel, a CI/CD scripteket és a KYAML kompatibilitást nézd meg először. Az opt-in scheduling feature-ök kevésbé fognak váratlan meglepetést okozni.