Skip to content

docs: add observability design - #344

Open
ajaysundark wants to merge 1 commit into
kubernetes-sigs:mainfrom
ajaysundark:docs/observability-design
Open

docs: add observability design#344
ajaysundark wants to merge 1 commit into
kubernetes-sigs:mainfrom
ajaysundark:docs/observability-design

Conversation

@ajaysundark

@ajaysundark ajaysundark commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Description

Add Observability Design Document for Node Readiness Controller

Assisted by: AGY / Gemini

Related Issue

None

Type of Change

/kind documentation
/kind feature
/kind design

Testing

NA

Checklist

  • make test passes
  • make lint passes

Does this PR introduce a user-facing change?

NONE

TAG=agy
CONV=196812f1-9aa8-42af-991d-51a74392b7bd
@kubernetes-prow kubernetes-prow Bot added kind/documentation Categorizes issue or PR as related to documentation. kind/feature Categorizes issue or PR as related to a new feature. labels Aug 4, 2026
@kubernetes-prow

Copy link
Copy Markdown

@ajaysundark: The label(s) kind/design cannot be applied, because the repository doesn't have them.

Details

In response to this:

Description

Add Observability Design Document for Node Readiness Controller

Related Issue

None

Type of Change

/kind documentation
/kind feature
/kind design

Testing

NA

Checklist

  • make test passes
  • make lint passes

Does this PR introduce a user-facing change?

NONE

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy Preview for node-readiness-controller canceled.

Name Link
🔨 Latest commit 9244f4e
🔍 Latest deploy log https://app.netlify.com/projects/node-readiness-controller/deploys/6a71a1ea02cf2c000883bf8c

@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: ajaysundark

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow
kubernetes-prow Bot requested a review from mrunalp August 4, 2026 08:25
@kubernetes-prow kubernetes-prow Bot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Aug 4, 2026
@kubernetes-prow kubernetes-prow Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 4, 2026
@ajaysundark

Copy link
Copy Markdown
Contributor Author

cc @AnuragThePathak

@kubernetes-prow kubernetes-prow Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Aug 4, 2026
@ajaysundark

Copy link
Copy Markdown
Contributor Author

/cc @rawadhossain

@kubernetes-prow

Copy link
Copy Markdown

@ajaysundark: GitHub didn't allow me to request PR reviews from the following users: rawadhossain.

Note that only kubernetes-sigs members and repo collaborators can review this PR, and authors cannot review their own PRs.

Details

In response to this:

/cc @rawadhossain

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@Karthik-K-N Karthik-K-N left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this /docs/proposals
so in future we can keep all proposals at one place?


## Design

### 1. Personas

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add user stories like personas?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User-stories felt bit redundant to me. I captured as 'questions' in SLIs/SLOs table below. Do you think I could may be extract that out at the top?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, What I thought is if any reader goes through this doc they can relate themself to any user story so the expectations may be clear.


* Add granular per-rule metrics.
* Build a Grafana dashboard for fleet-wide SLOs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are also building Scrape-Time Collector, isn't a goal of this exercise?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's a good point. it could be a goal.


---

### 4. New Scrape-Time Collector Surface

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it mandatory requirement or performance improvement?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not measured individually. But I had few other optimizations like batched status updates (something similar to endpointslice-updates-batch-period in kcm, and it showed significant perf difference in my 5k+ node testing.

I'll add it in my list to present it to one of our discussions

@AvineshTripathi AvineshTripathi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some questions, rest lgtm

We design observability around three distinct personas. Each persona asks different questions and looks at different interfaces.

1. **Infrastructure Owners (Cluster Operators)**: These operators run the cluster and manage the node lifecycle. They need to know if the fleet is healthy and if the controller is working.
2. **Component / Rule Owners**: These are teams that own the infrastructure components that gate node readiness, like CNI plugins or GPU drivers. They need to know whether / which specific check is failing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
2. **Component / Rule Owners**: These are teams that own the infrastructure components that gate node readiness, like CNI plugins or GPU drivers. They need to know whether / which specific check is failing.
2. **Component / Rule Owners**: These are teams that own the infrastructure components that gate node readiness, like CNI plugins or GPU drivers. They need to know whether / which specific component is delaying the readiness.

A check could be a generic check. I am fine with either but feel above suggestion fits more. Thoughts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that reads a little better, but also underwhelms the need for same rule granularity signals.

For instance, I was thinking to capture that when multiple conditions in a rule map to a network readiness-rule, the rule owner would also want to distinguish whether the latency is from IPAMReady or CNIReady condition -- note, kube_node_status_condition (KSM) already gives this per-condition observability,.

^^May be identifying 'rule owner' identical to 'component owner' is not the best fit. As a rule may include multiple components as we support multiple conditions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a valid point. How about component(s) or one or more components maybe


#### Metric vs status vs event vs log decisions
* Metrics to track aggregate cluster rates, durations, and counts over time. NRC will never use node names as metric labels for scale concerns.
* Custom Resource status (`NodeReadinessRule.status`) will hold current rule health and evaluation summaries. New `NodeReadinessEvaluation` api will also be implemented for the individual node readiness status.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we referring NodeReadinessRule.status to this or the new api?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both. This is recommending us to follow best-practices[1] for including a metav1.Condition[] in rule.status; I'm imagining something like "Progressing" to show rule reconciliation in-progress, "Ready" when complete.. This is helpful to tell controller's status on whether there are still some nodes in the process queue for handling a rule.

@Karthik-K-N also brought a point on the need to align this convention, to help operators to use standard workflow like:

 kubectl wait --for=condition=ready node-readiness-rule ..

I agree it would be necessary for us to also define these here in a document. But I wanted to get the initial thoughts out and add more details to agree on the 'states'

[1] ref-


### 2. Signals

We identify four telemetry surface in NRC: metric, status, event, or log and will follow below principles to assign each observability need to the right channel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we consider annotations as a signal for bootstraping currently?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a valid question maybe worth even clarifying at the doc. Annotation is 'metadata' and not observability signal. This is suppose to be treated only as internal state of controller. ie: no user can design a system on top based on the annotation we maintain as that could change.

| **Rule discovery (count, mode)** | Metric (`node_readiness_rules`) + `kubectl get nrr` | While `kubectl get` provides an API-level list, this gauge gives operators a controller-side view. The existing `node_readiness_rules_total` gauge violates naming conventions and ignores rule modes. We deprecate it in favor of `node_readiness_rules` without the `_total` suffix, adding labels to slice by `enforcement_mode` and `dry_run`. |
| **Selector match (nodes grouped per rule)** | Metric (`node_readiness_rule_matched_nodes`) | Tracks how many nodes match each rule selector. Because we are removing per-node lists from `NodeReadinessRule.status` for scalability, this scrape-time gauge collector preserves visibility into selector reach. A zero value signals a misconfigured selector. |
| **Nodes held or released per rule** | Metric (`node_readiness_rule_nodes`) | Shows the direct capacity impact of readiness gating on the cluster. The existing `node_readiness_nodes_by_state` gauge requires scale testing and tuning. We replace it with an always-on scrape-time collector that reads ground-truth `spec.taints` directly from the node cache. |
| **Blocking conditions per rule** | Metric (`node_readiness_blocked_nodes`) | Assesses which infrastructure component is blocking nodes across the fleet. Today, `node_readiness_condition_failures_total` counts an unsatisfied condition during normal bootstrapping as a failure, which inflates rates with every reconcile. We replace it with a `node_readiness_blocked_nodes{rule,condition}` gauge. Per-node drill-downs will live in the upcoming `NodeReadinessEvaluation` CRD. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is it different from NodesByState

@ajaysundark ajaysundark Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This proposal aims to further break down the earlier node_readiness_nodes_by_state to three more explicit per-node interested metrics:

  1. node_readiness_rule_matched_nodes (how nodes are mapped against our rules)
  2. node_readiness_rule_nodes (what are their current status as seen by controller - held vs released as opposed to ‘ready, not_ready, bootstrapping’ as it felt we are conflating with kubelet’s Ready. Also, bootstrapping doesn’t apply for ‘continuous’)
  3. node_readiness_blocked_nodes (what is pending on “held”)

| `node_readiness_blocked_nodes` | gauge | `rule`, `condition` | Number of nodes blocked by each required condition. | ALPHA |
| `node_readiness_taint_operations_total` | counter | `rule`, `operation` (`add` or `remove`) | Total taint additions and removals performed by the controller. | BETA (STABLE candidate) |
| `node_readiness_failures_total` | counter | `rule`, `reason` | Total operational failures across evaluation, taint operations, and status patches. | BETA |
| `node_readiness_api_conflicts_total` | counter | `rule`, `operation` | Number of API write conflicts per retry attempt. | ALPHA |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed? acc to the discussion here i think this can go as logs instead. wdyt?

|---|---|---|---|---|
| `node_readiness_reporter_build_info` | gauge | `version` | Reporter binary version to track fleet version skew. | ALPHA |
| `node_readiness_reporter_check_duration_seconds` | histogram | none | Duration of health probe checks. | ALPHA |
| `node_readiness_reporter_checks_total` | counter | `result` (`healthy`, `unhealthy`, or `error`) | Total probe check results over time. | ALPHA |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will have labels (healthy, unhealthy, or error)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I'm thinking they are good representative state generalizable across other reporters (eg: NRD) in the future as well. I am not sure whether more granular states / categorical counts would be needed for the http health-checks, but these three seemed good to be a starting point. This is currently proposed ALPHA so we could come back to analyze this again in the future when we decide promote to beta.

@rawadhossain rawadhossain left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together! left a few questions. Overall looks good to me.

| `node_readiness_bootstrap_duration_seconds` | Rename and reshape | Migrate into `node_readiness_bootstrap_hold_duration_seconds{rule, taint_origin="adopted"}` histogram. |
| `node_readiness_reconciliation_latency_seconds` | Rename and reshape | Rename to `node_readiness_enforcement_latency_seconds` to prevent collision with `controller-runtime` reconcile metrics. Normalize operation values to `add` and `remove`. |
| `node_readiness_condition_failures_total` | Delete | Replace state-as-counter metric with the `node_readiness_blocked_nodes` gauge. |
| `node_readiness_rule_last_reconciliation_timestamp_seconds` | Delete | Remove timestamp gauge. Unchanged rules are still actively enforced under `GenerationChangedPredicate` without resyncs. |

@rawadhossain rawadhossain Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now this is metric (node_readiness_rule_last_reconciliation_timestamp_seconds) exists for every rule, even if a rule has zero matching nodes, and some of our tooling relies on that. If we remove it, is there another metric that provides the same guarantee, or is that something we need to figure out?

|---|---|---|---|---|
| `node_readiness_build_info` | gauge | `version` | Controller binary version for managed-export monitoring. | ALPHA |
| `node_readiness_rules` | gauge | `enforcement_mode`, `dry_run` | Number of rules by enforcement mode and dry-run state. | BETA |
| `node_readiness_rule_matched_nodes` | gauge | `rule` | Number of nodes matching the rule selector. | BETA |

@rawadhossain rawadhossain Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this metric (node_readiness_rule_matched_nodes) is tied to the new collector, should this wait until the collector is added, or is the idea to implement it using the current approach first and migrate it later?


| Persona | Question | SLI / SLO | Alerts and health checks |
|---|---|---|---|
| NRC cluster operators | Are there nodes stuck in a bootstrapping state? | **Nodes held:** `sum by (rule) (node_readiness_rule_nodes{state="held"})` | **Node Capacity held:** `sum(node_readiness_rule_nodes{state="held"}) > 0` and zero bootstrap completions for 30m.<br><br>**Error budget:** `rate(node_readiness_failures_total[10m]) > 0` for 15m.<br><br>**Controller health:** `up == 0`, `workqueue_depth > 1000`. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense for this to scale with fleet size or MaxConcurrentReconciles, rather than a fixed value? Queue depth can vary quite a bit depending on how many nodes enter the queue at once, so 1000 might mean different things for different cluster sizes. wdyt?

@DsThakurRawat

Copy link
Copy Markdown
Contributor

two things from the code side that might be worth folding into the doc.

the scrape-time collector reading spec.taints fixes a blind spot that exists on main right now, not just in principle. in the #382 shape (bootstrap-only rule, completion annotation present, taint orphaned by the startup race) the node controller skips the node before any evaluation, so nothing ever updates that node's entry in the status that node_readiness_nodes_by_state is computed from. the orphaned taint is structurally invisible to the current gauge, while the proposed collector reads the taint that is actually on the node and reports it as held. i reproduced the absorbing state locally earlier today (details in #382), so this would make a concrete example for the "gauges freeze and report stale numbers" limitation in section 4.

second, node_readiness_api_conflicts_total and the *_conflict_exhausted reason strings can't fire on today's code: updateRuleStatus re-gets the rule and patches with client.MergeFrom inside retry.RetryOnConflict, and a merge patch without an optimistic lock never gets a 409, so the retry path is dead and a lost concurrent write is silent, which is #341's finding. those metrics become meaningful once #343 switches the status patches to MergeFromWithOptimisticLock, so #343 may be worth listing as an explicit dependency in the rollout plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/documentation Categorizes issue or PR as related to documentation. kind/feature Categorizes issue or PR as related to a new feature. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants