NodePool Disruption Budgets
NodePool disruption budgets limit how many nodes CloudPilot’s native provisioning controller can disrupt at the same time for a given NodePool. Use them to slow down voluntary replacements, pause a specific disruption reason, or apply stricter limits during sensitive periods.
Budgets are stored in the native NodePool spec.disruption.budgets array. They constrain eligible disruption activity; saving a budget does not itself schedule or trigger a node replacement.
These budgets are different from Kubernetes PodDisruptionBudgets. A PodDisruptionBudget protects application availability during pod eviction, while a NodePool disruption budget limits node-level disruption for one NodePool. Both controls can apply to the same operation.
Budget fields
Each budget supports the following native fields:
| Field | Required | Description |
|---|---|---|
nodes | Yes | Maximum node disruptions allowed by this budget. Use a fixed count such as "2" or a percentage such as "20%". Values are strings, and "0" blocks matching disruptions while the budget is active. |
reasons | No | Limits the budget to one or more of Empty, Underutilized, and Drifted. Omit this field to apply the budget to all three reasons. |
schedule | With duration | UTC cron expression that starts each active window. Use a standard five-field cron expression or a supported alias such as @daily. |
duration | With schedule | How long the budget remains active after each schedule occurrence. Use hours, minutes, or both, for example 8h, 30m, or 1h30m. |
You can define up to 50 budgets. schedule and duration must either both be present or both be omitted. A budget without either field is always active.
Select all reasons by omitting reasons
Omitting reasons means that the budget applies to Empty, Underutilized, and Drifted disruptions:
disruption:
budgets:
- nodes: "20%"Do not use reasons: []. An empty list selects no disruption reason, so it is not equivalent to “all reasons.” The CloudPilot Console treats an empty list as invalid; either omit reasons or select at least one supported reason.
How multiple budgets are evaluated
For each disruption reason, the controller evaluates the budgets that both:
- Apply to that reason, either because
reasonsis omitted or because the reason is listed. - Are currently active, either because they have no schedule or because the current UTC time is inside their scheduled active window.
When multiple budgets match, the most restrictive value wins. In other words, the controller uses the minimum allowance from all matching active budgets.
Percentages are rounded up to a whole-node ceiling. The controller then subtracts nodes that are already deleting and nodes that are NotReady from both percentage and fixed-count ceilings. For example, 20% on a 12-node pool has a ceiling of three only when no nodes are deleting or NotReady. A fixed count is not scaled with pool size.
For example, if an always-active 20% budget allows eight disruptions and a second active budget allows two, the effective allowance is two.
If no active budget applies to a disruption reason, that reason is not constrained by these budget entries. Keep an always-active budget with reasons omitted as a baseline unless leaving a reason unconstrained is intentional.
Common configurations
Limit all native disruption reasons to 20%
This always-active budget limits all supported reasons because reasons is omitted:
disruption:
budgets:
- nodes: "20%"Pause native drift replacements
Set the allowance to zero for Drifted nodes to block native drift-driven replacements:
disruption:
budgets:
- nodes: "10%"
- nodes: "0"
reasons:
- DriftedThis does not disable drift detection. Nodes can still be marked as drifted, but the native drift disruption path cannot replace them while this budget remains active. The first entry explicitly preserves Karpenter’s usual 10% baseline for the other reasons; replace it with your existing baseline if the NodePool already uses a different value.
Replace at most one drifted node at a time
Use a fixed count of one for gradual native drift replacement:
disruption:
budgets:
- nodes: "10%"
- nodes: "1"
reasons:
- DriftedThe always-active baseline continues to constrain the other reasons while the second entry limits drift replacement to one node.
Restrict disruptions during a UTC window
This budget becomes active at 09:00 UTC from Monday through Friday and blocks all supported native disruption reasons for eight hours:
disruption:
budgets:
- nodes: "0"
schedule: "0 9 * * 1-5"
duration: 8hSchedules always use UTC; NodePool disruption budgets do not support a configurable time zone. The cron expression starts a restriction window and duration controls how long it lasts. It does not schedule a node disruption. Outside the window, this scheduled budget is inactive, so any other matching active budgets determine the allowance.
What a drift budget does not control
A budget scoped to Drifted constrains only the native drift disruption path. It does not control or rate-limit:
- CloudPilot Scheduled Rebalance operations
- Node expiration through
expireAfter - Cloud-provider interruption handling
- Node repair
- Manual node deletion
These paths have separate triggers and safety controls. Review them independently before relying on a drift budget as a complete change-management policy.
Configure budgets in the Console
Open the NodePool editor and go to Lifecycle & Disruption. Each budget card maps directly to one entry in spec.disruption.budgets, including reason selection and optional scheduled restrictions. Review the effective scope of all entries before saving, especially when combining an always-active baseline with reason-specific or scheduled limits.