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:
- Egy script a ConfigMap
yesésnoértékeire épített, de most idézőjeles formában jönnek, ezért eltört egy string összehasonlítás. - 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:
- Bekapcsolom a gang schedulinget stagingen az elosztott training jobokhoz, és legalább egy hónapig figyelem.
- Az inference podokat átállítom in-place resize-ra a jelenlegi töröld-és-hozd-létre-újra folyamat helyett.
- A CI scripteket felkészítem KYAML kompatibilitásra a production upgrade előtt.
- 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.