Gemini Scheduler Pod Placement Failure After Adding New Model Microservice
Problem
The Google Gemini scheduler repeatedly reports FailedScheduling for pods belonging to the newly introduced model‑service microservice. Deployments stall, and the kubectl get pods output shows pods stuck in Pending state:
NAME READY STATUS RESTARTS AGE
model-service-7c9f5d9d9b-abcde 0/1 Pending 0 2m30s
Cluster-wide impact includes:
- Increased latency for downstream services awaiting model inference.
- Autoscaler churn as the scheduler repeatedly attempts placement.
- Alert fatigue from
PodPendingalerts.
Root Cause
The failure originates from two interacting constraints:
- Resource Exhaustion – The new model microservice requests
cpu: 4andmemory: 8Gi. Existing workloads already consume ~85% of the node pool’s allocatable resources, leaving insufficient headroom for the additional request. - Affinity Rule Conflict – The deployment includes a
podAntiAffinityrule that forces eachmodel-servicepod to avoid nodes already running anymodel-servicepod. Combined with the resource shortage, the scheduler cannot find a node that satisfies both the resource request and the anti‑affinity constraint.
Both constraints are enforced by the default kube-scheduler predicates Fit and PodAntiAffinity. When no node satisfies the predicate set, the scheduler emits a FitError with detailed reasons, which appear in the pod events:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 1m default-scheduler 0/5 nodes are available: 5 Insufficient cpu, 5 Insufficient memory, 5 pod anti-affinity rule conflicts.
Debug
Step‑by‑step investigation:
- Inspect pod events
kubectl describe pod model-service-7c9f5d9d9b-abcde
- Check node resource availability
kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
node-a 28 93% 112Gi 92%
node-b 27 90% 108Gi 89%
...
- Validate affinity rules
kubectl get deployment model-service -o yaml | grep -A5 affinity
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- model-service
topologyKey: kubernetes.io/hostname
- Simulate scheduler fit using
kubectl dry-runandkube-schedulerdry‑run mode (available in v1.27+).
kube-scheduler --dry-run=client \
--policy-configmap=default/kube-scheduler-policy \
--kubeconfig=$KUBECONFIG \
--scheduler-name=default-scheduler \
--pod=model-service-7c9f5d9d9b-abcde.yaml
The dry‑run output confirms both Insufficient cpu and pod anti-affinity rule conflicts as reasons.
Solution
Address the two constraints independently, then verify combined effect.
1. Adjust Resource Requests
Reduce the pod’s request to values that fit within the current node pool, or provision a dedicated node pool with larger instance types.
Before:
resources:
requests:
cpu: "4"
memory: "8Gi"
limits:
cpu: "8"
memory: "16Gi"
After (scaled‑down request):
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "4"
memory: "8Gi"
2. Relax Anti‑Affinity or Use Preferred Anti‑Affinity
If strict isolation is not required, switch to preferredDuringSchedulingIgnoredDuringExecution so the scheduler can place pods on the same node when necessary.
Before (required):
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- model-service
topologyKey: kubernetes.io/hostname
After (preferred):
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- model-service
topologyKey: kubernetes.io/hostname
3. Deploy a Dedicated Node Pool (optional)
If the model service must retain its original resource profile, create a new node pool with sufficient capacity and node‑selector labels.
apiVersion: v1
kind: Node
metadata:
labels:
model-node: "true"
spec:
template:
spec:
nodeSelector:
model-node: "true"
Verify
After applying the changes, run the following checks:
- Confirm pods transition to
Running:
kubectl get pods -l app=model-service -o wide
NAME READY STATUS RESTARTS AGE NODE
model-service-7c9f5d9d9b-abcde 1/1 Running 0 3m node-c
kubectl top nodes node-c
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
node-c 12 45% 48Gi 55%
kubectl get pods -o wide | grep model-service
All pods should be on distinct hosts unless the scheduler had to relax the rule, which is reflected by the PreferredDuringScheduling weight.
Prevent
Implement safeguards to avoid recurrence:
- Capacity Planning: Integrate
kubectl top nodesmetrics into CI pipelines; fail PRs if new deployments would exceed 80% of node allocatable resources. - Affinity Policy Review: Enforce a lint rule (e.g.,
kube-linter) that flagsrequiredDuringSchedulinganti‑affinity on high‑request pods unless justified. - Pod Disruption Budgets: Define PDBs for model services to ensure graceful scaling without saturating the cluster.
- Cluster Autoscaler Tuning: Set
scale-up-from-zeroandmax-node-provision-timethresholds to allow rapid provisioning of larger nodes when needed. - Observability: Add alerts on
scheduler_attempts_total{result="unschedulable"}and on node‑levelcpu/allocatableandmemory/allocatablethresholds.
FAQ
- Why does the scheduler report both “Insufficient cpu” and “pod anti‑affinity rule conflicts”?
Both predicates are evaluated independently. If any node fails any predicate, the scheduler aggregates the reasons. In this case, every node lacked enough free CPU *and* violated the required anti‑affinity, so both messages appear. - Can I keep the original resource requests and still schedule the pods?
Yes, by adding a dedicated node pool sized for the model workload and using anodeSelectorornodeAffinityto target those nodes. This isolates the high‑resource pods from the rest of the cluster. - Is “preferredDuringSchedulingIgnoredDuringExecution” safe for production?
It is safe when strict isolation is not a hard requirement. The scheduler will still try to spread pods, but will fall back to co‑location if no suitable node exists, preventing unschedulable states. - How do I know if a future microservice will cause similar placement failures?
Run a “dry‑run” scheduler simulation (as shown in the Debug section) against a snapshot of current node resources, or usekubectl explain pod --recursiveto validate resource requests against cluster capacity before merging. - What role does the Cluster Autoscaler play in this scenario?
If the autoscaler is enabled and the pod requests exceed current capacity, it can provision new nodes. However, the anti‑affinity rule prevented any existing node from being a fit, so the autoscaler never triggered. Relaxing or removing the required anti‑affinity allows the autoscaler to act.
Related Topic Hub: LLM Systems Troubleshooting Hub