Configure pod disruption budgets
Control how many of a component's pods a node drain can take down, with a budget on each component or defaults for each deployment mode.
A PodDisruptionBudget limits how many of a component's pods evictions can take down at once, as when kubectl drain empties a node. On a Helm install, OME gives each RawDeployment and OMENative component a default budget of maxUnavailable: 1, so evictions take its pods one at a time. You can set your own budget on a component, or change the default for all components in a mode.
A PodDisruptionBudget limits evictions only. Rolling updates and OME's own changes delete pods rather than evict them, so the budget doesn't slow them down.
How OME picks a budget¶
The engine, the decoder and the router each take minAvailable or maxUnavailable, as in spec.engine.minAvailable. Set one of the two, to a number of pods or to a percentage of the component's pods from 0% to 100%. A runtime can set them too, in its engineConfig, decoderConfig or routerConfig, and a field that the InferenceService sets overrides the same field from the runtime.
A component that sets neither field gets its mode's default. Since v1.3, the ome-resources chart sets the RawDeployment and OMENative defaults to maxUnavailable: 1, and Step 2 changes them. A manifest install has no defaults, so only the components that set a field get a PodDisruptionBudget. On v1.2.2, a RawDeployment component that sets neither field always gets maxUnavailable: 1.
| Budget | Of four pods, evictions can take down | Notes |
|---|---|---|
maxUnavailable: 1 |
One at a time | The chart's default. A one-pod component can still lose its only pod. |
minAvailable: 50% |
Two at a time | A percentage follows the replica count, rounded up to whole pods. |
minAvailable: 0 |
All four | Opts the component out of its mode's default. |
minAvailable: 100% |
None | Blocks evictions, so node drains stall. |
Step 1: Set a budget on a component¶
Give the engine four pods, and keep at least half of them available. In qwen3-0-6b-isvc.yaml, raise minReplicas and maxReplicas, and add minAvailable:
apiVersion: ome.io/v1beta1
kind: InferenceService
metadata:
name: qwen3-0-6b
namespace: qwen3-0-6b
spec:
model:
name: qwen3-0-6b
runtime:
name: srt-qwen3-0-6b
engine:
minReplicas: 4
maxReplicas: 4
minAvailable: 50%To cap how many pods can be down instead, set maxUnavailable in place of minAvailable. Apply the file:
kubectl apply -f qwen3-0-6b-isvc.yamlinferenceservice.ome.io/qwen3-0-6b configuredOME scales the Deployment qwen3-0-6b-engine to four pods, and creates or updates the PodDisruptionBudget qwen3-0-6b-engine. Wait for the four pods to be ready:
kubectl wait deployment qwen3-0-6b-engine -n qwen3-0-6b --for=jsonpath='{.status.readyReplicas}'=4 --timeout=20mdeployment.apps/qwen3-0-6b-engine condition metList the PodDisruptionBudgets of the InferenceService:
kubectl get pdb -n qwen3-0-6b -l ome.io/inferenceservice=qwen3-0-6bNAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
qwen3-0-6b-engine 50% N/A 2 45mKubernetes resolves 50% against the Deployment's four replicas, so two pods must stay available. ALLOWED DISRUPTIONS is how many pods evictions can take down now: two, while all four pods are healthy. At 0, Kubernetes refuses to evict the engine's pods.
OME sets the budget and selector back when you edit the PodDisruptionBudget, so change the budget in the InferenceService instead.
Step 2: Set the defaults for each modeUnreleased: coming in v1.3¶
A mode's default applies in every namespace. Like a component's budget, it takes minAvailable or maxUnavailable. Set the defaults through the chart on a Helm install, or in the ConfigMap on a manifest install.
Set the defaults under ome.controller.podDisruptionBudget in your values file. This one sets the RawDeployment default to maxUnavailable: 25%, and keeps the chart's default for OMENative:
ome:
controller:
podDisruptionBudget:
rawDeployment:
maxUnavailable: 25%A mode that you leave out keeps the chart's maxUnavailable: 1. To turn a mode's default off, set the mode to null, as in omeNative: null.
Keep your other install values in the file, since helm upgrade resets any value that the file leaves out. The CHART column of helm list -n ome shows the chart version you run, such as ome-resources-1.3.0. Upgrade the release with that version:
helm upgrade --install ome oci://ghcr.io/moirai-internal/charts/ome-resources \
--namespace ome --version 1.3.0 -f values.yamlHelm prints Release "ome" has been upgraded. Happy Helming! and the release's new revision. The upgrade restarts the manager, which applies the new defaults.
Set the podDisruptionBudget key of the inferenceservice-config ConfigMap, in the manager's namespace ome, to the JSON that the chart would write. This value sets the RawDeployment default to maxUnavailable: 25%, and gives OMENative the chart's maxUnavailable: 1:
kubectl patch configmap inferenceservice-config -n ome --type merge \
-p '{"data":{"podDisruptionBudget":"{\"omeNative\":{\"maxUnavailable\":1},\"rawDeployment\":{\"maxUnavailable\":\"25%\"}}"}}'configmap/inferenceservice-config patchedWrite a percentage as a JSON string, as in "25%". A mode that you leave out has no default. Restart the manager so that it applies the new defaults now:
kubectl rollout restart deployment ome-controller-manager -n omedeployment.apps/ome-controller-manager restartedOn a Helm install, the next helm upgrade writes the chart's value back over your patch.
Wait for the new manager pods:
kubectl rollout status deployment ome-controller-manager -n omeWhen they're running, the command ends with:
deployment "ome-controller-manager" successfully rolled outCheck the defaults that the manager reads:
kubectl get configmap inferenceservice-config -n ome -o jsonpath='{.data.podDisruptionBudget}'{"omeNative":{"maxUnavailable":1},"rawDeployment":{"maxUnavailable":"25%"}}The engine keeps its own budget from Step 1. Other RawDeployment components that set neither field now get maxUnavailable: 25%.
How OMENative counts podsUnreleased: coming in v1.3¶
On OMENative, OME writes the budget as an integer minAvailable, counted in pods. An OMENative component runs Instances, and a multi-pod Instance has a leader and worker.size workers. OME multiplies the Instances that the component should run by the pods in each, and rounds a percentage up. For four Instances of a leader and two workers each, 12 pods in all:
| Budget that you set | minAvailable that OME writes |
|---|---|
minAvailable: 2 |
2 |
minAvailable: 90% |
11 |
maxUnavailable: 1, the chart's default |
11 |
maxUnavailable: 25% |
9 |
kubectl get pdb shows the number under MIN AVAILABLE, and N/A under MAX UNAVAILABLE, whichever field you set. OME updates the number as the component scales, including when an autoscaler scales it.
The budget counts pods, not Instances. By default, when a pod of a ready multi-pod Instance goes missing, OME drains the Instance and recreates all its pods. So one eviction can take the whole Instance down. For a multi-pod component, maxUnavailable: 1 lets evictions take down one Instance at a time.
To pace OMENative's own updates, set lifecycle.updateStrategy.rollingUpdate.maxUnavailable, which counts Instances.
When a component moves between RawDeployment and OMENative, its PodDisruptionBudget protects only the old pods until the new workload's replicas are ready. OME then moves it to the new pods with the new mode's budget, or deletes it when that mode has none.
Troubleshooting¶
A node drain doesn't finish¶
kubectl drain keeps retrying an eviction that a PodDisruptionBudget refuses, and prints Cannot evict pod as it would violate the pod's disruption budget. List the InferenceService's PodDisruptionBudgets as in Step 1.
A component whose ALLOWED DISRUPTIONS is 0 has no pod to spare. Either some of its pods are already unavailable, or its budget allows no disruption, as minAvailable: 100% does. Wait for the pods to be ready, lower the budget, or add replicas. On OMENative, the drain can also wait while OME recreates a multi-pod Instance after an eviction.
No PodDisruptionBudget appears¶
When kubectl get pdb -n qwen3-0-6b -l ome.io/inferenceservice=qwen3-0-6b leaves out a component, check these:
- The component sets neither field, and its mode has no default, as on a manifest install. The check at the end of Step 2 prints the defaults, if any. Set a budget as in Step 1, or defaults as in Step 2.
- The component runs in the
MultiNodemode (deprecated), which gets none. Move it to OMENative to give it one. - OME logs an error for the component, as A component stops updating describes.
The webhook rejects the budgetUnreleased: coming in v1.3¶
kubectl prints an error that includes admission webhook "inferenceservice.ome-webhook-server.validator" denied the request:, followed by a reason that names the field. For a component that sets both fields, the reason is:
spec.engine.minAvailable and spec.engine.maxUnavailable cannot both be setFor a value other than a whole number or a percentage from 0% to 100%, it's:
spec.engine.minAvailable must be a non-negative integer or a percentage from 0% to 100%Keep one field, with a value such as 2 or 50%, and apply the InferenceService again. A percentage is a whole number followed by %.
A component stops updatingUnreleased: coming in v1.3¶
OME stops creating and updating the component's workload. It also skips the components after it, in the order engine, decoder, router. It records no event, so look for the error in the manager's logs:
kubectl logs -n ome -l control-plane=ome-controller-manager --tail=-1 | grep qwen3-0-6bThe lines include Failed to reconcile component and an error that starts with one of these:
| Error starts with | Cause | Fix |
|---|---|---|
failed to resolve engine PodDisruptionBudget |
The runtime sets one field for the component and the InferenceService sets the other, or the runtime's value is invalid. | Set the runtime's field in the InferenceService in place of the other one, or fix it in the runtime. |
failed to preflight engine PodDisruptionBudget |
A PodDisruptionBudget that OME didn't create has the component's name. | Delete it with kubectl delete pdb qwen3-0-6b-engine -n qwen3-0-6b. |
For the decoder or the router, the error names that component. OME retries, so the components update once you fix the cause.
Every InferenceService stops updatingUnreleased: coming in v1.3¶
When the podDisruptionBudget defaults are invalid, OME stops creating and updating the workloads of all InferenceServices, including the ones with their own budgets. Deleting an InferenceService still works. OME records no event, so look for the error in the manager's logs:
kubectl logs -n ome -l control-plane=ome-controller-manager --tail=-1 | grep 'fails to create InferenceServicesConfig'The error starts with fails to create InferenceServicesConfig:, followed by the reason. After a Helm change, the reason is likely exactly one of podDisruptionBudget.rawDeployment.minAvailable or podDisruptionBudget.rawDeployment.maxUnavailable must be set. It means your values gave a mode minAvailable without maxUnavailable: null, as the warning in Step 2 explains. Other reasons name an invalid value, an unknown key or malformed JSON. Keys are case-sensitive.
Fix the values or the ConfigMap as in Step 2, and restart the manager after a kubectl edit. The InferenceServices then update again.
Clean up¶
Set the engine back to one pod without a budget: in qwen3-0-6b-isvc.yaml, remove minAvailable, and set minReplicas and maxReplicas back to 1. Then apply the file:
kubectl apply -f qwen3-0-6b-isvc.yamlinferenceservice.ome.io/qwen3-0-6b configuredThe engine goes back to its mode's default. When the mode has none, OME deletes the PodDisruptionBudget qwen3-0-6b-engine.
If you changed the defaults in Step 2, put them back:
Remove podDisruptionBudget from your values file, and upgrade the release as in Step 2. The chart writes its defaults back and restarts the manager.
Remove the key:
kubectl patch configmap inferenceservice-config -n ome --type json -p '[{"op":"remove","path":"/data/podDisruptionBudget"}]'configmap/inferenceservice-config patchedThen restart the manager as in Step 2.
Next steps¶
- Deployment modes and OMENative: what OME creates for a component in each mode, and how it picks the mode.
- Serve a model on OMENative: run the engine as OMENative Instances, where the budget counts pods.
- Instances: how an OMENative component runs its pods, as single-pod or multi-pod Instances.
- Set replica defaults: the default
minReplicasandmaxReplicasfor components. - Specifying a Disruption Budget for your Application: how Kubernetes applies a PodDisruptionBudget.