Repository navigation
feat(metrics): add karpenter_pods_disrupted_total counter - #3015
kubernetes-prow[bot] merged 3 commits into
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: praveen9354 The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Friendly ping @DerekFrank when you have a moment — this is the
Happy to drop the Presubmit matrix (1.29.x → 1.35.x) was green; one flake on |
|
@DerekFrank gentle re-ping when you have a moment — this is the |
|
/assign @DerekFrank |
|
I think I misunderstood the initial PR. I'll try to take a deeper look next week |
|
@MaciekPytel @tallaxes — gentle ping. A duplicate PR (#3087) for the same
CI checks here are green (14 ok). Happy to address any review feedback — let me know if there's a scope or design concern blocking the review queue. |
DerekFrank
left a comment
There was a problem hiding this comment.
Okay, some minor code architecture points but I'm aligned on the need for this metric
Separate issue, not this PR: putting nodepool on pods_drained_total gets us
per-nodepool eviction success rate (drained / disrupted). Different site
(terminator/eviction.go) and it bumps cardinality on an existing metric, so it
wants its own PR.
| // disruption.Candidate.reschedulablePods so per-disruption-reason metrics across | ||
| // queue/expiration/health/GC controllers count pods consistently. An empty nodeName | ||
| // returns 0 with no error so callers operating on un-registered NodeClaims don't fail. | ||
| func CountReschedulablePodsOnNode(ctx context.Context, kubeClient client.Client, nodeName string) (int, error) { |
There was a problem hiding this comment.
This is GetPods + an IsReschedulable filter, which is the same thing
NewCandidate already does inline. GetPods only uses node.Name, so I'd make
it take nodeNames ...string (the existing callers all have a node, so it's a
mechanical .Name) and add a ReschedulablePods(...) helper on top. Then the
metric sites just do len(...) and this function goes away.
| prometheus.CounterOpts{ | ||
| Namespace: Namespace, | ||
| Subsystem: PodSubsystem, | ||
| Name: "disrupted_total", |
There was a problem hiding this comment.
I think I prefer disruption_initiated_total
d86765e to
f8db4c7
Compare
|
Thanks for the review. Addressed both points in split commits: 1. Helper refactor — 2. Metric rename — Rebased onto latest main (main had moved significantly since I opened this, including your #3009 and #3045 clock refactors — my test now uses
|
|
Gentle ping @DerekFrank on whether the addressed points look good, and @MaciekPytel / @tallaxes for the maintainer approval when you have a moment. Happy to rebase or address any additional feedback. |
Mirrors karpenter_nodeclaims_disrupted_total but counts the reschedulable pods that were on each disrupted node. Operators can now correlate the NodeClaim-level disruption volume with the pod-level user impact across the same (reason, nodepool, capacity_type) labels. Emit sites match the four NodeClaim disruption paths where pod records exist on the node: - pkg/controllers/disruption/queue.go uses Candidate.reschedulablePods directly (already cached on the Candidate) - pkg/controllers/nodeclaim/expiration/controller.go, pkg/controllers/node/health/controller.go, and pkg/controllers/nodeclaim/garbagecollection/controller.go list pods via the new node.CountReschedulablePodsOnNode helper, which applies the same pod.IsReschedulable filter (active or stateful-set terminating; not DaemonSet- or Node-owned) Skipped: nodeclaim/lifecycle/launch.go and liveness.go disrupt NodeClaims that never bound pods (launch failure, liveness watchdog), so a pods counter on those paths would always be 0. A list error during pod counting is logged at V(1) and the metric reports 0 for that disruption — never blocks the disruption itself. Signed-off-by: praveen9354 <praveen9354@gmail.com>
…blePods helper Reviewer noted that CountReschedulablePodsOnNode was doing the same work NewCandidate already does inline (GetPods + pod.IsReschedulable filter), and that GetPods itself only ever used node.Name from its Node argument. - GetPods now takes nodeNames ...string. All four existing callers (termination controller, terminator drain, statenode, debug node) become mechanical .Name passes. - Empty node names are skipped inside GetPods so the "spec.nodeName" field selector never accidentally matches unscheduled pods for callers operating on un-registered NodeClaims. - New nodeutils.ReschedulablePods composes GetPods with the same pod.IsReschedulable filter used by disruption.Candidate. - CountReschedulablePodsOnNode is removed; the three metric emit sites (health, expiration, garbagecollection) now call ReschedulablePods and take len(...) at the emit line. - GetCurrentlyReschedulablePods keeps its Node-based public signature; the Node -> name conversion happens inline before calling GetPods. No metric semantics change. Signed-off-by: praveen9354 <praveen9354@gmail.com>
…ted_total Reviewer prefers "disruption_initiated_total" over "disrupted_total" for the pod-level counter: pods are not yet disrupted at the point the counter is incremented; what has been initiated is the disruption of the underlying nodeclaim, and the counter records the reschedulable pod count at that moment. NodeClaimsDisruptedTotal is left unchanged — the per-nodeclaim semantic is unambiguous and already shipped. - PodsDisruptedTotal -> PodsDisruptionInitiatedTotal - karpenter_pods_disrupted_total -> karpenter_pods_disruption_initiated_total - Help string updated to describe pod disruptions initiated - Emit sites (queue, health, expiration, garbagecollection) and the expiration suite reference the renamed identifier. Signed-off-by: praveen9354 <praveen9354@gmail.com>
f8db4c7 to
a913585
Compare
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: DerekFrank, praveen9354 The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Reopens #3012 — closed on 2026-05-11 with the question "does pods_drained_total satisfy this?" After comparing both metric definitions, the two are adjacent rather than overlapping.
Why pods_disrupted_total is not a duplicate of pods_drained_total
eviction.go:215)reasonreason,nodepool,capacity_typenode/healthcontroller)terminator/eviction.go)Three concrete operator questions only
pods_disrupted_totalanswers:Pods that fail to evict (PDB violations, repeated 429s, ungraceful terminations) —
pods_drained_totalonly increments on the 200 path interminator/eviction.go:215, so the denominator of intended disruption is invisible.pods_drained_total / pods_disrupted_totalis the eviction success rate per nodepool/reason; neither metric alone expresses this.Ungraceful node loss (
node/health/controller.goGC path) — when an unhealthy node's NodeClaim is GC'd, the terminator never runs, sopods_drained_totalis structurally blind to those pod disruptions.pods_disrupted_totalcatches them because it increments in the same 4 controllers that already incrementnodeclaims_disrupted_total.Label cardinality parity with
nodeclaims_disrupted_total— that metric already exists atpkg/metrics/metrics.go:60with(reason, nodepool, capacity_type). Operators can answer "how many NodeClaims were disrupted per nodepool" but not the corresponding pod-level question with matching labels.Open to adjustments
If cardinality is a concern, happy to drop the
capacity_typelabel.If the name suggests overlap with
pods_drained_total, happy to rename topods_disruption_initiated_totalto make the timing difference explicit.Implementation
Mirrors
karpenter_nodeclaims_disrupted_totalbut counts the reschedulable pods that were on each disrupted node, across the four NodeClaim disruption paths where pod records exist:pkg/controllers/disruption/queue.gousesCandidate.reschedulablePodsdirectly (already cached on the Candidate)pkg/controllers/nodeclaim/expiration/controller.go,pkg/controllers/node/health/controller.go, andpkg/controllers/nodeclaim/garbagecollection/controller.golist pods via the newnode.CountReschedulablePodsOnNodehelper, which applies the samepod.IsReschedulablefilter (active or stateful-set terminating; not DaemonSet- or Node-owned)Skipped:
nodeclaim/lifecycle/launch.goandliveness.godisrupt NodeClaims that never bound pods (launch failure, liveness watchdog), so a pods counter on those paths would always be 0.A list error during pod counting is logged at V(1) and the metric reports 0 for that disruption — never blocks the disruption itself.
Refs 2020