-
Notifications
You must be signed in to change notification settings - Fork 557
feat: RFC for disruption performance metrics #3255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
GnatorX
wants to merge
1
commit into
kubernetes-sigs:main
Choose a base branch
from
GnatorX:garvinp/disruption-perf-metrics-rfc
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+165
−0
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| # RFC: Disruption Performance Metrics | ||
|
|
||
| ## Motivation | ||
|
|
||
| Operators need to answer four questions when disruption is slow or stuck: | ||
|
|
||
| 1. Is Karpenter evaluating and taking disruption actions? | ||
| 2. What is waiting, for how long, and in which NodePool? | ||
| 3. Why is it blocked? | ||
| 4. For consolidation, what proven savings are delayed? | ||
|
|
||
| Current metrics answer pieces of this: eligible nodes, decision duration, budgets, validation failures, timeouts, decisions, disruption initiation, and termination. They do not provide an end-to-end, per-NodePool view. This RFC adds that view for voluntary disruption, expiration, node repair, and node deletion. | ||
|
|
||
| ### Use Cases | ||
|
|
||
| 1. Validate a performance change. Compare latency, candidate coverage, timeouts, actions, and remaining backlog before and after a change. Lower latency with less coverage is not a win. | ||
|
|
||
| 2. Triage a backlog. Identify the affected NodePool, oldest blocked work, and whether the cause is budget, feasibility, validation, timeout, static-pool limits, or a repair health gate. | ||
|
|
||
| 3. Operate lifecycle disruption. See expired NodeClaims or unhealthy nodes waiting for action, and actions that take too long to delete a node. | ||
|
|
||
| 4. Understand consolidation impact. Separate feasible, positive-saving opportunities from source cost that was never proven consolidatable. | ||
|
|
||
| ### Non-Goals | ||
|
|
||
| - Change disruption behavior, budgets, scheduling, validation, or execution. | ||
| - Estimate billed or realized cloud savings. | ||
| - Run extra scheduling simulations for telemetry. | ||
| - Add node, pod, namespace, instance-type, zone, ID, or raw-error labels. | ||
|
|
||
| ## Proposal | ||
|
|
||
| All new metrics include nodepool. Existing metrics are unchanged. | ||
|
|
||
| nodepool is the owning NodePool. Standalone NodeClaims use <none>. A shared multi-node operation across pools uses <multiple> for operation metrics; candidate metrics remain attributed to each candidate's pool. | ||
|
|
||
| blocker is a bounded label with these initial values: budget, consolidation_disabled, buffer_pods, not_consolidatable, static_replica_excess, static_node_limit, no_feasible_schedule, same_or_more_expensive_replacement, min_values, score, validation_churn, validation_budget, nominated, timeout, nodepool_health_gate, and cluster_health_gate. | ||
|
|
||
| ### Proposed Spec | ||
|
|
||
| This RFC adds metrics only; there is no CRD, configuration, or feature gate. | ||
|
|
||
| #### Operator view: progress and latency | ||
|
|
||
| | Metric | Type | Labels | Meaning | | ||
| |---|---|---|---| | ||
| | passes_total | Counter | nodepool, reason, consolidation_type, outcome | Completed method passes: no_candidates, no_command, selected, timed_out, or error. | | ||
| | last_evaluated_timestamp_seconds | Gauge | nodepool, reason, consolidation_type | Timestamp of the latest completed pass. Use time() - metric to detect stale data when an earlier method succeeds and prevents later methods from running. | | ||
| | candidate_evaluation_duration_seconds | Histogram | nodepool, reason, consolidation_type, stage | Duration of one SimulateScheduling call. stage is evaluate or validate. | | ||
| | simulations_total | Counter | nodepool, reason, consolidation_type, stage, outcome | Simulation result: feasible, infeasible, candidate_deleting, timeout, or error. | | ||
| | candidate_batch_size | Histogram | nodepool, reason, consolidation_type, stage | Candidates supplied to a simulation; exposes multi-node binary-search batch sizes. | | ||
| | validation_duration_seconds | Histogram | nodepool, reason, consolidation_type | Validation delay plus refreshed-candidate and scheduling validation. | | ||
| | action_initiation_delay_seconds | Histogram | nodepool, reason, consolidation_type, termination_mode, stage | Time from selection, expiry, or repair eligibility to successful disruption action. | | ||
| | action_to_node_deletion_duration_seconds | Histogram | nodepool, termination_mode | NodeClaim deletion timestamp to node-finalizer removal. | | ||
|
|
||
| #### Operator view: backlog and blockers | ||
|
|
||
| | Metric | Type | Labels | Meaning | | ||
| |---|---|---|---| | ||
| | eligible_candidates | Gauge | nodepool, reason, consolidation_type | Candidates passing the latest method filter. | | ||
| | candidates_total | Counter | nodepool, reason, consolidation_type, outcome, blocker, pricing_status | Candidate outcomes: selected, infeasible, skipped, rejected, timed out, or error. | | ||
| | candidates_remaining | Gauge | nodepool, reason, consolidation_type, blocker | Candidates that become actionable when the named blocker changes. | | ||
| | oldest_backlog_age_seconds | Gauge | nodepool, reason, consolidation_type, blocker | Oldest blocked work, measured from its relevant condition, expiry, or unhealthy transition. | | ||
| | timeout_candidates_total | Counter | nodepool, consolidation_type, state | Candidates evaluated or remaining at a consolidation timeout. | | ||
| | expiration_candidates | Gauge | nodepool, state | Managed NodeClaims in configured, due, or deleting state. | | ||
| | expiration_lateness_seconds | Histogram | nodepool, termination_mode | Expiry to successful deletion action. | | ||
| | node_repair_candidates | Gauge | nodepool, state | Unhealthy managed nodes in within_toleration, eligible, blocked, or deleting state. | | ||
| | node_repair_blocked_total | Counter | nodepool, blocker | Deferrals caused by NodePool or cluster health gates. | | ||
| | node_repair_unhealthy_duration_seconds | Histogram | nodepool, condition | Unhealthy-condition transition to deletion action. | | ||
|
|
||
| #### Operator view: consolidation impact | ||
|
|
||
| pricing_status is a diagnostic label: available, unavailable, or not_applicable. Unavailable pricing does not block evaluation, but Karpenter must not calculate savings from it. | ||
|
|
||
| | Metric | Type | Labels | Meaning | | ||
| |---|---|---|---| | ||
| | consolidation_observed_savings_usd_per_hour | Histogram | nodepool, consolidation_type, decision, disposition, pricing_status | Positive savings from a feasible command. disposition is selected, score rejected, validation rejected, or queue failed. | | ||
| | consolidation_known_savings_backlog_usd_per_hour | Gauge | nodepool, consolidation_type, blocker, pricing_status | Feasible positive-saving commands blocked in the latest pass. It answers what known saving is blocked now, not historical or total potential savings. | | ||
| | consolidation_source_cost_upper_bound_usd_per_hour | Gauge | nodepool, consolidation_type, blocker, pricing_status | Source cost of pre-simulation-blocked candidates; an upper bound, never a savings estimate. | | ||
|
|
||
| The known-savings gauge is a latest-pass snapshot. It is set from the completed pass and reset to zero when the next pass finds none; it does not retain candidate identity. | ||
|
|
||
| ### How It Works | ||
|
|
||
| Candidate construction and disruption methods record outcomes where they are known. The SimulateScheduling wrapper records duration, result, and batch size without extra work. Validation records its full duration. The initiator records action delay; the termination path records action-to-node-deletion duration. | ||
|
|
||
| | Path | Primary signals | | ||
| |---|---| | ||
| | Empty | Budget/validation backlog and exact deletion savings; no simulation. | | ||
| | Static drift | Backlog, budget, replica excess, node limits, and action delay. | | ||
| | Drift | Backlog age, simulations to first feasible action, and budget blocking. | | ||
| | Multi/single consolidation | Simulation throughput, timeout coverage, blockers, and known savings. | | ||
| | Expiration | Due backlog, expiry lateness, action delay, and deletion duration. | | ||
| | Node repair | Unhealthy backlog, health-gate blocks, action delay, and deletion duration. | | ||
|
|
||
| #### metrics.disruption controller | ||
|
|
||
| A singleton metrics.disruption controller computes forceful-path gauges: expiration_candidates, node_repair_candidates, and forceful oldest_backlog_age_seconds. Every five seconds it derives the complete metric set and calls metrics.Store.ReplaceAll, following the existing metrics.node pattern and clearing stale series. | ||
|
|
||
| It reads cluster state plus cached managed NodeClaims. Both are required because expiration can act on a NodeClaim before it has a registered Node. It does not simulate, call pricing APIs, mutate resources, or make disruption decisions. Voluntary latest-pass gauges remain owned by the disruption controller because only it knows simulation outcomes. | ||
|
|
||
| ### Interaction with Existing Features | ||
|
|
||
| Existing metrics remain the baseline and are not renamed or relabelled: decision evaluation duration, eligible nodes, budgets, timeouts, validation failures, queue failures, balanced-consolidation metrics, decisions_by_nodepool_total, disruption initiation, and node/NodeClaim termination metrics. | ||
|
|
||
| Budgets remain authoritative through existing per-NodePool budget gauges. Events retain node-specific detail; these metrics provide the aggregate entry point. | ||
|
|
||
| ### Observability | ||
|
|
||
| The dashboard has four views: progress, backlog and age, blockers, and impact/execution. | ||
|
|
||
| Start voluntary diagnosis with time() - last_evaluated_timestamp_seconds: a later method may be stale because an earlier method already succeeded. Then inspect eligible_candidates, candidates_remaining, and oldest_backlog_age_seconds. Slice candidate outcomes by blocker before using events or logs for resource-level detail. | ||
|
|
||
| For cluster totals, aggregate away nodepool; retain it to diagnose a pool. Histogram quantiles must aggregate buckets first: | ||
|
|
||
| histogram_quantile(0.95, | ||
| sum by (le, reason, consolidation_type) ( | ||
| rate(karpenter_voluntary_disruption_candidate_evaluation_duration_seconds_bucket[15m]) | ||
| ) | ||
| ) | ||
|
|
||
| Do not sum <multiple> with per-NodePool series for a pool total: it is one shared operation. | ||
|
|
||
| ### Edge Cases | ||
|
|
||
| - Early success does not classify later, unexamined candidates as infeasible; freshness shows that lower-priority methods may be stale. | ||
| - Partial empty-node validation records only dropped candidates as validation blocked. | ||
| - pricing_status=unavailable appears on candidate metrics; savings metrics only observe finite positive available prices. | ||
| - Counters describe activity. Gauges are current snapshots and are reset by a later pass or metrics.disruption scan. | ||
|
|
||
| ## Backward Compatibility | ||
|
|
||
| No API, behavior, or existing metric changes. New dashboards must tolerate absent new series during rollout. | ||
|
|
||
| ## Graduation Criteria | ||
|
|
||
| All metrics introduced by this RFC launch as alpha and graduate across Karpenter | ||
| release versions. Graduation is based on metric stability and user experience, | ||
| not on a feature gate. | ||
|
|
||
| ### Alpha | ||
|
|
||
| - Ship the metrics as alpha in the first Karpenter release containing them. | ||
| - Metric names, labels, and semantics may change while operators validate them. | ||
| - Unit and integration tests cover label vocabulary, gauge cleanup, pricing, | ||
| blockers, forceful backlog, and lifecycle timing. | ||
| - Benchmarks show no material controller regression. | ||
|
|
||
| ### Beta | ||
|
|
||
| - Graduate in a later Karpenter release after at least one release as alpha. | ||
| - No unresolved user-reported correctness, usability, cardinality, or performance | ||
| issues. | ||
| - Dashboards demonstrate that operators can distinguish budget, feasibility, | ||
| validation, timeout, and repair-gate backlogs. | ||
| - Names, labels, and semantics are expected to remain stable. | ||
|
|
||
| ### GA | ||
|
|
||
| - Graduate in a later Karpenter release after at least one release as beta. | ||
| - No unresolved user-reported issues with the beta metrics. | ||
| - Maintainers have operational confidence in metric accuracy, cost, and stale | ||
| series cleanup at supported cluster scales. | ||
| - Names, labels, and semantics follow Karpenter's stable metric compatibility | ||
| guarantees. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
honestly, I'd ship these without an RFC, we can always deprecate metrics :D