Karmada v1.19 Multi-Component Scheduling - Ένα training job, τρία clusters, τέσσερις σιωπηλές αποτυχίες
// Το Karmada v1.19 μπορεί να βάλει ολόκληρο PyTorchJob, master και workers, σε ένα member cluster. Τέσσερα kind clusters με ψεύτικες GPU δείχνουν τις τέσσερις προϋποθέσεις που χρειάζεται και τι γίνεται όταν λείπει η καθεμία: το job τρέχει τρεις φορές, ο scheduler δεν βρίσκει χώρο με 16 ελεύθερες GPU, και ένα job καταλήγει εκεί όπου οι workers του δεν θα ξεκινήσουν ποτέ.
Έχεις τρία GPU clusters και ένα control plane μπροστά τους. Κάποιος υποβάλλει ένα distributed training job: ένας master, δύο workers. Θέλεις ολόκληρο το job σε ένα cluster, γιατί NCCL ανάμεσα σε regions δεν είναι κάτι που το κάνεις επίτηδες. Γράφεις το προφανές policy: μοίρασε τα replicas και στρίμωξέ τα σε όσο το δυνατόν λιγότερα clusters.
Το job τρέχει τρεις φορές. Μία σε κάθε cluster. Στο control plane όλα τα status είναι πράσινα.
Αυτή ήταν η πρώτη μου δοκιμή του Karmada v1.19 με ένα PyTorchJob. Το Karmada έγινε graduated project του CNCF τον Σεπτέμβριο του 2026, και το βασικό νέο του v1.19 είναι ακριβώς αυτό το use case. Το feature δουλεύει. Θέλει όμως τέσσερα πράγματα να ισχύουν ταυτόχρονα, και όταν λείπει κάποιο, δεν σου το λέει τίποτα.
Προέλευση: οι αναφορές σε κώδικα είναι από το
karmada-io/karmadaστο tagv1.19.0. Κάθε output βγήκε από τέσσερα kind clusters (kindest/node:v1.37.0) σε laptop: ένα Karmada host και τρία members με ψεύτικο capacitynvidia.com/gpu.
TL;DR#
- Το
MultiplePodTemplatesSchedulingείναι alpha και κλειστό by default. Μπαίνει σταkarmada-controller-manager,karmada-schedulerκαιkarmada-webhook. - Gate κλειστό: ένα
PyTorchJobδεν έχει πληροφορία για replicas, το Karmada το αντιμετωπίζει σαν ConfigMap και το αντιγράφει σε κάθε cluster. - Gate ανοιχτό, χωρίς spread constraint: πάλι αντιγράφεται σε κάθε cluster. Το νέο path τρέχει μόνο με
spreadByField: cluster,minGroups: 1,maxGroups: 1. - Με το constraint, κάθε GPU job αποτυγχάνει με
no enough resource. Τα default resource models ξέρουν μόνο CPU και memory. Κλείσε τοCustomizedClusterResourceModelingστον scheduler. - Ο general estimator αθροίζει τα σύνολα του cluster. Ένα job μπορεί να καταλήξει εκεί όπου κανένα node δεν χωράει τους workers του, να δηλώνει
ScheduledκαιFullyApplied, και να μείνειPendingγια πάντα. Στήσε τον ακριβή scheduler-estimator.
Τι έφερε στην πράξη το v1.19#
Το proposal για multiple pod templates μαθαίνει στον interpreter, στον scheduler και στους estimators τι είναι ένα workload με περισσότερα από ένα pod template. Ο interpreter επιστρέφει ένα component ανά template, ο scheduler τα τοποθετεί όλα μαζί, σαν ένα πράγμα, σε ένα cluster, και το v1.19 καταγράφει το αποτέλεσμα ανά component στο ResourceBinding, στο spec.clusters[*].components.
Built-in interpreters υπάρχουν για τα PyTorchJob, TFJob και MPIJob του Kubeflow, τα RayCluster και RayJob, το Job του Volcano, το FlinkDeployment και το SparkApplication. Αυτοί του Kubeflow αφορούν τα kubeflow.org/v1 APIs του training-operator. Το TrainJob του Kubeflow Trainer v2 δεν έχει built-in interpreter.
Γιατί όχι MultiKueue;#
Το MultiKueue λύνει το ίδιο πρόβλημα από την πλευρά του queueing. Δεν το έτρεξα, η δεξιά στήλη είναι από το documentation του.
| Κριτήριο | Karmada v1.19 | MultiKueue |
|---|---|---|
| Πώς επιλέγεται το cluster | Scheduler + estimators, πριν δημιουργηθεί οτιδήποτε | Αντιγράφεται στους workers, κερδίζει όποιος κάνει πρώτος admit |
| Ωριμότητα του job path | Alpha, κλειστό by default | Beta από το Kueue v0.9, ανοιχτό by default |
| Εύρος | Οποιοδήποτε resource, ίδια policies με τα Deployments σου | Jobs που διαχειρίζεται το Kueue |
Με δυο λόγια: αν τρέχεις ήδη Karmada για services, το v1.19 είναι το πρώτο release όπου το training στο ίδιο control plane είναι ρεαλιστικό. Αν θέλεις μόνο batch dispatch, το MultiKueue σε πάει εκεί με ένα beta feature.
Τι ΔΕΝ είναι#
- Δεν είναι gang scheduling μέσα στο member. Αυτό είναι δουλειά του kube-scheduler, δες το Kubernetes 1.37 Gang Scheduling
- Δεν σπάει το job. Ένα multi-component job δεν απλώνεται ποτέ σε πολλά clusters
- Δεν κάνει rescheduling. Το v1.19 απλώς «laying the foundation» γι’ αυτό. Ένα job που μπήκε σε λάθος μέρος μένει εκεί
Το demo#
flowchart TB
subgraph Host["kind: karmada-host"]
API["karmada-apiserver"]
SCH["karmada-scheduler"]
EST["scheduler-estimator x3"]
end
subgraph A["member-a"]
A1["1 node, 2 GPU"]
end
subgraph B["member-b"]
B1["3 nodes, 2 GPU each"]
end
subgraph C["member-c"]
C1["1 node, 8 GPU"]
end
API --> SCH
SCH --> A & B & C
EST -. per-node view .-> A & B & C
Οι GPU είναι ένα patch στο status του node. Το Karmada αθροίζει το allocatable των nodes στο resource summary κάθε cluster, και ο kube-scheduler κάθε member το επιβάλλει ανά node:
$ kubectl --kubeconfig .karmada/karmada-apiserver.config get clusters -o custom-columns=...
NAME ENDPOINT GPU CPU
member-a https://member-a-control-plane:6443 2 12
member-b https://member-b-control-plane:6443 6 48
member-c https://member-c-control-plane:6443 8 12
Κάθε member τρέχει τον Kubeflow training-operator v1.9.4, οπότε τα jobs γίνονται κανονικά pods που τρέχουν pause. Το train-small είναι ένας master μόνο με CPU και δύο workers με 1 GPU ο καθένας. Το train-big είναι το ίδιο, με 3 GPU ανά worker:
Worker:
replicas: 2
template:
spec:
containers:
- name: pytorch
image: registry.k8s.io/pause:3.10.1
resources:
requests: {cpu: 50m, memory: 32Mi, nvidia.com/gpu: "3"}
limits: {cpu: 100m, memory: 64Mi, nvidia.com/gpu: "3"}
Αποτυχία 1: gate κλειστό, το PyTorchJob είναι ένα ConfigMap#
Το policy που γράφουν οι περισσότεροι για ένα training job:
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: training
namespace: ml-team
spec:
resourceSelectors:
- apiVersion: kubeflow.org/v1
kind: PyTorchJob
placement:
clusterAffinity:
clusterNames: [member-a, member-b, member-c]
replicaScheduling:
replicaSchedulingType: Divided
replicaDivisionPreference: Aggregated
Σε φρέσκο karmadactl init:
replicas: null
components: null
Binding has been scheduled successfully. Result: {member-a:0, member-b:0, member-c:0}
-- member-a
pod/train-small-master-0 1/1 Running
pod/train-small-worker-0 1/1 Running
pod/train-small-worker-1 1/1 Running
-- member-b (same three pods)
-- member-c (same three pods)
Μηδέν replicas ανατέθηκαν, και το job έγινε apply παντού. Ο built-in interpreter ορίζει componentResource αλλά όχι replicaResource. Με το gate κλειστό, το Karmada πέφτει πίσω στο replica hook (pkg/detector/detector.go:1513), δεν βρίσκει τίποτα, και ο scheduler θεωρεί non-workload ένα binding χωρίς replicas και χωρίς components (pkg/scheduler/core/util.go:66-69). Τα non-workloads πάνε σε κάθε cluster. Τα Divided και Aggregated δεν τα ρωτάει κανείς.
Αποτέλεσμα: με το gate κλειστό, ένα Karmada policy για Kubeflow v1 jobs είναι broadcast.
Αποτυχία 2: gate ανοιχτό, πάλι τρία αντίγραφα#
Ανοίγεις το gate και στα τρία components και τρέχεις το ίδιο policy:
NAME REPLICAS COMPONENTS CLUSTERS
train-small-pytorchjob <none> master,worker member-b,member-a,member-c
Τα ίδια εννέα pods. Το Karmada τώρα ξέρει τα components, και δεν τα χρησιμοποιεί κανείς. Το multi-component path κρύβεται πίσω από έναν έλεγχο (pkg/scheduler/core/estimation.go:43-65):
for i := range spec.Placement.SpreadConstraints {
if spec.Placement.SpreadConstraints[i].SpreadByField == policyv1alpha1.SpreadByFieldCluster &&
spec.Placement.SpreadConstraints[i].MinGroups == 1 &&
spec.Placement.SpreadConstraints[i].MaxGroups == 1 {
return true
}
}
return false
Χωρίς ακριβώς αυτό το constraint, ένα multi-component workload γίνεται propagate «to all candidate clusters» (pkg/scheduler/core/common.go:72).
Αποτέλεσμα: το replicaDivisionPreference: Aggregated διαβάζεται σαν «ένα cluster, αν γίνεται». Για multi-component workloads δεν σημαίνει τίποτα.
Αποτυχία 3: 16 ελεύθερες GPU, και χώρος πουθενά#
Προσθέτεις το constraint:
spreadConstraints:
- spreadByField: cluster
minGroups: 1
maxGroups: 1
==> Scheduled:
False failed to select clusters: no enough resource when selecting 1 clusters
Ζητήθηκαν δύο GPU, ελεύθερες είναι δεκαέξι. Το ίδιο job χωρίς το GPU request γίνεται schedule αμέσως, σε ένα cluster, με το αποτέλεσμα ανά component καταγεγραμμένο. Άρα το path δουλεύει, και κάτι μέσα του δεν ξέρει να μετράει GPU.
Αυτό το κάτι είναι το cluster resource modeling: beta, ανοιχτό by default από το v1.4. Ο general estimator προσομοιώνει το job πάνω σε ψεύτικα nodes που φτιάχνει από τα resource models κάθε cluster (pkg/estimator/client/general.go:241), και τα default models έχουν μόνο δύο διαστάσεις:
- grade: 0
ranges:
- {name: cpu, min: "0", max: "1"}
- {name: memory, min: "0", max: 4Gi}
# ... up to grade 8, cpu and memory only
Κανένα προσομοιωμένο node δεν έχει GPU, άρα κάθε GPU job χωράει μηδέν φορές. Με custom models δεν διορθώνεται, το documentation επιτρέπει μόνο «cpu, memory, storage, ephemeral-storage». Η λύση είναι να κλείσεις το CustomizedClusterResourceModeling στον scheduler:
NAME COMPONENTS CLUSTERS PER-CLUSTER
train-small-pytorchjob master,worker member-c [map[name:master replicas:1] map[name:worker replicas:2]]
member-c train-small-master-0 Running
member-c train-small-worker-0 Running
member-c train-small-worker-1 Running
Ολόκληρο το job σε ένα cluster. Έτσι μοιάζει το feature του v1.19 όταν δουλεύει.
Αποτέλεσμα: με τις default ρυθμίσεις, το multi-component scheduling δεν μπορεί να τοποθετήσει GPU job, και το error σε στέλνει να ψάχνεις capacity. Τα demos μόνο με CPU δεν θα σου το δείξουν.
Αποτυχία 4: Scheduled, FullyApplied, Pending για πάντα#
Το train-big θέλει δύο workers με 3 GPU ο καθένας. Το member-b έχει έξι GPU, αλλά δύο ανά node:
cond:
- Scheduled=True Binding has been scheduled successfully.
- FullyApplied=True All works have been successfully applied
clusters:
- member-b
member-b train-big-master-0 Running
member-b train-big-worker-0 Pending
member-b train-big-worker-1 Pending
# 0/4 nodes are available: ... 3 Insufficient nvidia.com/gpu
Πράσινο στο Karmada, κολλημένο για πάντα στο member, την ώρα που το member-c έχει οκτώ ελεύθερες GPU σε ένα node. Ο general estimator διαιρεί τα σύνολα του cluster με το request του job, και όπως λέει ο ίδιος «has no per-node view» (pkg/estimator/client/general.go:176).
Η λύση είναι ο ακριβής scheduler-estimator, ένας ανά member, που απαντάει με βάση τα πραγματικά nodes:
clusters:
- member-c
member-c train-big-master-0 Running
member-c train-big-worker-0 Running
member-c train-big-worker-1 Running
Από την Αποτυχία 3 δεν σε γλιτώνει. Ο scheduler κρατάει το ελάχιστο από όλους τους estimators, και με το modeling ξανά ανοιχτό κερδίζει πάλι το μηδέν του general estimator: no enough resource when selecting 1 clusters.
Αποτέλεσμα: για GPU jobs χρειάζεσαι και τα δύο: modeling κλειστό και τον ακριβή estimator.
Το configuration που δουλεύει#
| Ρύθμιση | Πού | Γιατί |
|---|---|---|
MultiplePodTemplatesScheduling=true | controller-manager, scheduler, webhook | Parsing, χρήση και validation των components |
spreadByField: cluster, minGroups: 1, maxGroups: 1 | PropagationPolicy | Η μόνη μορφή που ενεργοποιεί το path |
CustomizedClusterResourceModeling=false | scheduler | Τα default models δεν έχουν GPU |
Ένας karmada-scheduler-estimator ανά member | host cluster | Έλεγχος ανά node αντί για σύνολα του cluster |
Υπέρ και κατά#
| Υπέρ | Κατά |
|---|---|
| Τοποθέτηση ολόκληρου του job, όχι μισό job ανά region | Alpha, τρία components και ένα constraint που πρέπει να είναι ακριβώς έτσι |
| Αποτέλεσμα ανά component στο API, η βάση για quota και rescheduling | Σιωπηλά fallbacks: πλήρη αντίγραφα παντού, πράσινο status |
| Οκτώ built-in interpreters, καθόλου Lua για Kubeflow v1, Ray, Volcano, Flink, Spark | Defaults που δεν βλέπουν GPU, και ένα παραπλανητικό error |
| Ένα control plane για services και training | Χωρίς rescheduling και χωρίς Trainer v2 προς το παρόν |
Το repository του demo#
Όλα τα παραπάνω βρίσκονται στο srekubecraft-demo/karmada-multi-component, με ένα task ανά αποτυχία:
task up # 4 kind clusters, fake GPUs, Karmada, training-operator
task copies # Failure 1
task gate:on && task copies:gated # Failure 2
task one # Failure 3
task modeling:off && task one # fixed
task fragment # Failure 4
task estimator && task fragment # fixed
Το README καλύπτει και τις παγίδες του setup όταν τρέχεις Karmada σε kind πάνω σε macOS, ανάμεσά τους και το ότι τα karmadactl init και join καταγράφουν endpoints που το host cluster δεν μπορεί να φτάσει.
Συμπέρασμα#
Το Karmada v1.19 κάνει αυτό που λένε τα release notes. Το είδα να βάζει ένα PyTorchJob σε ένα cluster και να καταγράφει ποιο component πήρε πόσα replicas. Για να φτάσω εκεί χρειάστηκαν τέσσερις αλλαγές, και όποια έλειπε έμοιαζε είτε με επιτυχία είτε με πρόβλημα capacity. Χρησιμοποίησε τον πίνακα με το configuration παραπάνω, και μη στήνεις alerts μόνο πάνω στα conditions του Karmada: τα Scheduled=True και FullyApplied=True ίσχυαν και τα δύο για ένα job που οι workers του δεν θα ξεκινούσαν ποτέ.
Το feature είναι alpha, και τα defaults του φτιάχτηκαν για stateless replicas, ενώ ο λόγος που υπάρχει είναι οι GPU. Τρέξε το σε staging fleet με το παραπάνω configuration, και παρακολούθησε τη δουλειά για το rescheduling στα επόμενα releases.
Αν σου φάνηκε χρήσιμο, ίσως σε ενδιαφέρουν και τα σχετικά posts μου για scheduling και multi-cluster AI workloads:
- Kubernetes 1.37 Gang Scheduling - Το Workload API, ένα field που χάνεται σιωπηλά και το σημείο όπου σταματά το preemption
- Modelplane - Fleet inference σε πολλά clusters, demo χωρίς ούτε μία GPU
- llm-d - Kubernetes-native distributed LLM inference σε κλίμακα
