feat(kubernetes): per-node-group nodeHealthCheck overrides - #3053
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enables per-node-group overrides for MachineHealthCheck settings, specifically Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (7)
📝 WalkthroughWalkthroughAdds per-node-group overrides for ChangesPer-node-group nodeHealthCheck overrides
Estimated code review effort: 3 (Moderate) | ~25 minutes Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant NodeGroupValues
participant ClusterTemplate
participant MachineHealthCheck
NodeGroupValues->>ClusterTemplate: provide per-group overrides or empty values
ClusterTemplate->>ClusterTemplate: select override or cluster-wide fallback
ClusterTemplate->>MachineHealthCheck: render maxUnhealthy and nodeStartupTimeout
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Trivy (0.69.3)Trivy execution failed: 2026-07-13T07:27:47Z FATAL Fatal error run error: fs scan error: scan error: scan failed: failed analysis: post analysis error: post analysis error: kubernetes scan error: fs filter error: fs filter error: walk error range error: stat .golangci.yml: no such file or directory: range error: stat .golangci.yml: no such file or directory Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces per-group overrides for maxUnhealthy and nodeStartupTimeout in the Kubernetes application package, allowing individual node groups to override cluster-wide health check settings. The changes span API types, Helm templates, schemas, documentation, and tests. The review feedback identifies potential template rendering failures in cluster.yaml when overrides are explicitly set to empty strings or nil, and suggests more robust checks. Additionally, the reviewer noted that the PR description needs to include a release-note block to comply with the repository guidelines.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| {{- if hasKey $group "maxUnhealthy" }} | ||
| {{- $maxUnhealthy = toString $group.maxUnhealthy }} | ||
| {{- $muSource = printf "nodeGroups.%s.maxUnhealthy" $groupName }} | ||
| {{- end }} |
There was a problem hiding this comment.
Using hasKey alone to check for maxUnhealthy will evaluate to true even if the value is explicitly set to an empty string ("") or nil (e.g., when a user wants to unset an override and inherit the cluster-wide default). Since an empty string does not match the regex patterns, this will trigger a template rendering failure (fail).\n\nWe can make this more robust by checking that the key is present and its string representation is not empty.
{{- if and (hasKey $group "maxUnhealthy") (ne (toString $group.maxUnhealthy) "") }}
{{- $maxUnhealthy = toString $group.maxUnhealthy }}
{{- $muSource = printf "nodeGroups.%s.maxUnhealthy" $groupName }}
{{- end }}References
- Ensure Helm template correctness and avoid logic errors or rendering failures when handling optional/nullable values. (link)
| {{- end }} | ||
| nodeStartupTimeout: {{ $.Values.nodeHealthCheck.nodeStartupTimeout }} | ||
| {{- $nodeStartupTimeout := $.Values.nodeHealthCheck.nodeStartupTimeout }} | ||
| {{- if hasKey $group "nodeStartupTimeout" }}{{ $nodeStartupTimeout = $group.nodeStartupTimeout }}{{ end }} |
There was a problem hiding this comment.
Similarly, checking hasKey for nodeStartupTimeout without verifying if it is empty or nil can lead to rendering an empty value or failing validation if the user explicitly passes an empty string or nil to fall back to the cluster-wide default.\n\nWe should ensure it only overrides when the value is non-empty.
{{- if and (hasKey $group "nodeStartupTimeout") (ne (toString $group.nodeStartupTimeout) "") }}{{ $nodeStartupTimeout = $group.nodeStartupTimeout }}{{ end }}References
- Ensure Helm template correctness and avoid logic errors or rendering failures when handling optional/nullable values. (link)
| | `nodeGroups[name].maxUnhealthy` | Per-group override for `nodeHealthCheck.maxUnhealthy`. When unset, the cluster-wide `nodeHealthCheck.maxUnhealthy` applies. Accepts a bare integer ("0", "1", ...) or an integer percentage ("0%", "50%"). | `string` | `""` | | ||
| | `nodeGroups[name].nodeStartupTimeout` | Per-group override for `nodeHealthCheck.nodeStartupTimeout`. When unset, the cluster-wide `nodeHealthCheck.nodeStartupTimeout` applies. | `string` | `""` | |
There was a problem hiding this comment.
According to the repository guidelines, the PR description must contain a release note block in the following format:\n\ntext\nrelease-note\ntype(scope): human-readable changelog entry\n\n\n\nPlease add this block to the PR description before merging.
References
- PR body must contain a release-note block in the specified format. (link)
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
LGTM — the per-node-group nodeHealthCheck override flow is correct and well-tested.
Verified:
- The MachineHealthCheck renders inside
range $groupName, $group := .Values.nodeGroups, and its selector targetscluster.x-k8s.io/deployment-name: <release>-<groupName>, so each override applies only to its own group with no cross-group leakage. - When a group omits the field,
hasKeyfalls back cleanly to the cluster-widenodeHealthCheck.*— no nil-deref, no behaviour change for existing clusters. maxUnhealthyreuses the existing IntOrString handling: percentages quoted, bare integers unquoted, invalid input fails the render with a message naming the offending group.- Schema is fully in sync across types.go, values.yaml, values.schema.json, README, and the embedded openAPISchema; the codegen-drift check is green.
- helm-unittest covers percent override, bare-int override, nodeStartupTimeout override, fallback-when-unset, and invalid-input rejection.
One non-blocking robustness point before merge: hasKey alone treats an explicitly-empty value as "set", so a group with maxUnhealthy: "" (or null) would hard-fail the render and nodeStartupTimeout: "" would render a null timeout. The sibling optional-string field storageClass already guards this with {{- with .group.storageClass }} (and values.yaml ships storageClass: ""), so the empty value is a shape the chart elsewhere expects. Suggest matching that precedent — and (hasKey $group "maxUnhealthy") (ne (toString $group.maxUnhealthy) "") (same for nodeStartupTimeout) — plus a small maxUnhealthy: "" → falls back test.
The failing E2E run is unrelated — it dies on LINSTOR controller connectivity during management-cluster bootstrap, before any tenant MachineHealthCheck is rendered; a rerun should clear it.
eda99e7
ccc9900 to
eda99e7
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/apps/kubernetes/templates/cluster.yaml`:
- Around line 680-698: Empty node-group overrides are being treated as real
values, which breaks fallback to nodeHealthCheck defaults. Update the cluster
template logic around maxUnhealthy and nodeStartupTimeout so the per-group value
is used only when it is non-empty, not just when hasKey is true; keep the
existing validation/rendering paths in the same template block and preserve
fallback to $.Values.nodeHealthCheck.* for empty strings. Add a regression test
covering nodeGroups.<name>.maxUnhealthy and nodeGroups.<name>.nodeStartupTimeout
set to empty strings to confirm they inherit the global defaults.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 65a9550a-591c-4b51-9837-bb46167ee5aa
📒 Files selected for processing (7)
api/apps/v1alpha1/kubernetes/types.gopackages/apps/kubernetes/README.mdpackages/apps/kubernetes/templates/cluster.yamlpackages/apps/kubernetes/tests/mhc_rendering_test.yamlpackages/apps/kubernetes/values.schema.jsonpackages/apps/kubernetes/values.yamlpackages/system/kubernetes-rd/cozyrds/kubernetes.yaml
| {{- $maxUnhealthy := toString $.Values.nodeHealthCheck.maxUnhealthy }} | ||
| {{- $muSource := "nodeHealthCheck.maxUnhealthy" }} | ||
| {{- if hasKey $group "maxUnhealthy" }} | ||
| {{- $maxUnhealthy = toString $group.maxUnhealthy }} | ||
| {{- $muSource = printf "nodeGroups.%s.maxUnhealthy" $groupName }} | ||
| {{- end }} | ||
| {{- if hasSuffix "%" $maxUnhealthy }} | ||
| {{- if not (regexMatch "^[0-9]+%$" $maxUnhealthy) }} | ||
| {{- fail (printf "nodeHealthCheck.maxUnhealthy must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $maxUnhealthy) }} | ||
| {{- fail (printf "%s must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $muSource $maxUnhealthy) }} | ||
| {{- end }} | ||
| maxUnhealthy: {{ $maxUnhealthy | quote }} | ||
| {{- else if regexMatch "^[0-9]+$" $maxUnhealthy }} | ||
| maxUnhealthy: {{ $maxUnhealthy | int }} | ||
| {{- else }} | ||
| {{- fail (printf "nodeHealthCheck.maxUnhealthy must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $maxUnhealthy) }} | ||
| {{- fail (printf "%s must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $muSource $maxUnhealthy) }} | ||
| {{- end }} | ||
| nodeStartupTimeout: {{ $.Values.nodeHealthCheck.nodeStartupTimeout }} | ||
| {{- $nodeStartupTimeout := $.Values.nodeHealthCheck.nodeStartupTimeout }} | ||
| {{- if hasKey $group "nodeStartupTimeout" }}{{ $nodeStartupTimeout = $group.nodeStartupTimeout }}{{ end }} | ||
| nodeStartupTimeout: {{ $nodeStartupTimeout }} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Ignore empty-string node-group overrides when applying fallback.
Line 682 and Line 697 switch to the per-group value on key presence alone. That means nodeGroups.<name>.maxUnhealthy: "" now fails validation and nodeGroups.<name>.nodeStartupTimeout: "" renders an empty field instead of inheriting nodeHealthCheck.*, even though the new override fields are documented as optional/empty by default. Only promote the node-group value when it is non-empty, and add a regression test for the explicit-empty case.
Suggested fix
{{- $maxUnhealthy := toString $.Values.nodeHealthCheck.maxUnhealthy }}
{{- $muSource := "nodeHealthCheck.maxUnhealthy" }}
{{- if hasKey $group "maxUnhealthy" }}
- {{- $maxUnhealthy = toString $group.maxUnhealthy }}
- {{- $muSource = printf "nodeGroups.%s.maxUnhealthy" $groupName }}
+ {{- $groupMaxUnhealthy := toString $group.maxUnhealthy }}
+ {{- if ne $groupMaxUnhealthy "" }}
+ {{- $maxUnhealthy = $groupMaxUnhealthy }}
+ {{- $muSource = printf "nodeGroups.%s.maxUnhealthy" $groupName }}
+ {{- end }}
{{- end }}
...
{{- $nodeStartupTimeout := $.Values.nodeHealthCheck.nodeStartupTimeout }}
- {{- if hasKey $group "nodeStartupTimeout" }}{{ $nodeStartupTimeout = $group.nodeStartupTimeout }}{{ end }}
+ {{- if hasKey $group "nodeStartupTimeout" }}
+ {{- $groupNodeStartupTimeout := toString $group.nodeStartupTimeout }}
+ {{- if ne $groupNodeStartupTimeout "" }}
+ {{- $nodeStartupTimeout = $groupNodeStartupTimeout }}
+ {{- end }}
+ {{- end }}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {{- $maxUnhealthy := toString $.Values.nodeHealthCheck.maxUnhealthy }} | |
| {{- $muSource := "nodeHealthCheck.maxUnhealthy" }} | |
| {{- if hasKey $group "maxUnhealthy" }} | |
| {{- $maxUnhealthy = toString $group.maxUnhealthy }} | |
| {{- $muSource = printf "nodeGroups.%s.maxUnhealthy" $groupName }} | |
| {{- end }} | |
| {{- if hasSuffix "%" $maxUnhealthy }} | |
| {{- if not (regexMatch "^[0-9]+%$" $maxUnhealthy) }} | |
| {{- fail (printf "nodeHealthCheck.maxUnhealthy must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $maxUnhealthy) }} | |
| {{- fail (printf "%s must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $muSource $maxUnhealthy) }} | |
| {{- end }} | |
| maxUnhealthy: {{ $maxUnhealthy | quote }} | |
| {{- else if regexMatch "^[0-9]+$" $maxUnhealthy }} | |
| maxUnhealthy: {{ $maxUnhealthy | int }} | |
| {{- else }} | |
| {{- fail (printf "nodeHealthCheck.maxUnhealthy must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $maxUnhealthy) }} | |
| {{- fail (printf "%s must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $muSource $maxUnhealthy) }} | |
| {{- end }} | |
| nodeStartupTimeout: {{ $.Values.nodeHealthCheck.nodeStartupTimeout }} | |
| {{- $nodeStartupTimeout := $.Values.nodeHealthCheck.nodeStartupTimeout }} | |
| {{- if hasKey $group "nodeStartupTimeout" }}{{ $nodeStartupTimeout = $group.nodeStartupTimeout }}{{ end }} | |
| nodeStartupTimeout: {{ $nodeStartupTimeout }} | |
| {{- $maxUnhealthy := toString $.Values.nodeHealthCheck.maxUnhealthy }} | |
| {{- $muSource := "nodeHealthCheck.maxUnhealthy" }} | |
| {{- if hasKey $group "maxUnhealthy" }} | |
| {{- $groupMaxUnhealthy := toString $group.maxUnhealthy }} | |
| {{- if ne $groupMaxUnhealthy "" }} | |
| {{- $maxUnhealthy = $groupMaxUnhealthy }} | |
| {{- $muSource = printf "nodeGroups.%s.maxUnhealthy" $groupName }} | |
| {{- end }} | |
| {{- end }} | |
| {{- if hasSuffix "%" $maxUnhealthy }} | |
| {{- if not (regexMatch "^[0-9]+%$" $maxUnhealthy) }} | |
| {{- fail (printf "%s must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $muSource $maxUnhealthy) }} | |
| {{- end }} | |
| maxUnhealthy: {{ $maxUnhealthy | quote }} | |
| {{- else if regexMatch "^[0-9]+$" $maxUnhealthy }} | |
| maxUnhealthy: {{ $maxUnhealthy | int }} | |
| {{- else }} | |
| {{- fail (printf "%s must be a bare integer (e.g. 0, 1) or an integer percentage (e.g. 50%%), got %q" $muSource $maxUnhealthy) }} | |
| {{- end }} | |
| {{- $nodeStartupTimeout := $.Values.nodeHealthCheck.nodeStartupTimeout }} | |
| {{- if hasKey $group "nodeStartupTimeout" }} | |
| {{- $groupNodeStartupTimeout := toString $group.nodeStartupTimeout }} | |
| {{- if ne $groupNodeStartupTimeout "" }} | |
| {{- $nodeStartupTimeout = $groupNodeStartupTimeout }} | |
| {{- end }} | |
| {{- end }} | |
| nodeStartupTimeout: {{ $nodeStartupTimeout }} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/apps/kubernetes/templates/cluster.yaml` around lines 680 - 698,
Empty node-group overrides are being treated as real values, which breaks
fallback to nodeHealthCheck defaults. Update the cluster template logic around
maxUnhealthy and nodeStartupTimeout so the per-group value is used only when it
is non-empty, not just when hasKey is true; keep the existing
validation/rendering paths in the same template block and preserve fallback to
$.Values.nodeHealthCheck.* for empty strings. Add a regression test covering
nodeGroups.<name>.maxUnhealthy and nodeGroups.<name>.nodeStartupTimeout set to
empty strings to confirm they inherit the global defaults.
IvanHunters
left a comment
There was a problem hiding this comment.
Approve — strictly opt-in and additive.
make-generate artifacts (values.yaml, values.schema.json, README, types.go, cozyrds openAPISchema) are all in sync, the per-field fallback is correct (setting only maxUnhealthy keeps the inherited nodeStartupTimeout, no accidental null-out), and upgrade / fresh-install are a no-op.
Non-blocking, but worth resolving since all three prior reviews raised it and it's still open on the current tip:
cluster.yaml:682,697use a barehasKey, so an explicitmaxUnhealthy: ""/nodeStartupTimeout: ""is treated as "set" — the former hard-fails the render, the latter emits an empty field instead of inheriting. Match the siblingstorageClassguard:and (hasKey $group "x") (ne (toString $group.x) ""), plus a"" falls backtest.nodeStartupTimeoutrenders unquoted (cluster.yaml:698); a unitless numeric override becomes a YAML int and the CAPI MachineHealthCheck webhook rejects it (metav1.Durationis a string). Suggest| quote.- PR body is missing the required
release-notefenced block.
eda99e7 to
d9f6998
Compare
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
LGTM with non-blocking notes
Purely additive, omitempty per-node-group overrides that fall back to the existing cluster-wide nodeHealthCheck via hasKey gating; no migration, no CRD tightening, no default flip, schema/README/openAPISchema regenerated consistently, and helm unittest passes 149/149. Two small consistency/hygiene notes below, none blocking.
Findings
[MINOR] packages/apps/kubernetes/templates/cluster.yaml:728-730 — per-group nodeStartupTimeout override has no render-time validation, unlike maxUnhealthy
The maxUnhealthy path validates the value (bare int or N%) and fails at render time with the offending source named. The sibling nodeStartupTimeout override is interpolated raw (nodeStartupTimeout: {{ $nodeStartupTimeout }}) with no format check, so a typo like nodeGroups.md0.nodeStartupTimeout: "10 minutes" is only rejected later by the CAPI MHC admission webhook (a worse failure surface than a chart-render error). This is not a regression — the cluster-wide nodeHealthCheck.nodeStartupTimeout already renders unvalidated the same way — so it is consistent with existing behaviour. Optional improvement: add a metav1.Duration-style regex check (^[0-9]+(s|m|h)$ or similar) mirroring the maxUnhealthy guard so both overrides fail fast at render with the group named.
Claim mismatches
[MISSING] release-note fenced block — CONTRIBUTING.md requires a release-note block in the PR body; the current body has What/Why/Notes prose but no fenced release-note block. Add one (e.g. feat(kubernetes): support per-node-group nodeHealthCheck maxUnhealthy / nodeStartupTimeout overrides).
Caveats
- Existing-customer upgrade (scenario A): verified safe. Both fields are new optional
stringmembers ofNodeGroup(api/apps/v1alpha1/kubernetes/types.go:277,282). The schemarequiredlist onnodeGroups.additionalPropertiesis unchanged (["diskSize","instanceType","maxReplicas","minReplicas"]), so existingKubernetesCRs in customer clusters re-admit unchanged. The template gates onhasKey $group "...", so clusters with no per-group values render identically to today — no migration script needed,migrations.targetVersioncorrectly untouched. No default flip, no RBAC/cozyrds surface change, no image bump. - Fresh install (scenario B): verified safe. No new
PackageSource/bundle wiring, no new.Values._cluster/_namespacekeys, no cert-manager/CRD dependency, no new image references.make generateartifacts are all present and mutually consistent:types.go,values.yaml(@fieldannotations),values.schema.json,README.md, and thekubernetes-rd/cozyrds/kubernetes.yamlopenAPISchemaall carry the two new properties as optional strings. - Edge case (non-blocking): setting a per-group override to an empty string (
maxUnhealthy: "") makeshasKeytrue and then fails render (empty matches no branch). To inherit the cluster-wide value the operator must omit the key entirely, which the field docs ("When unset ... applies") describe correctly. Acceptable, but worth a docs mention if this bites operators. - PR body says "stacked on
phase1-talos-migration(#2931), retarget to main once it merges" — this is stale: the branch is already based onmainand the commit sits on top of current main. No action needed, just note the body is out of date.
d9f6998 to
6722ca4
Compare
6722ca4 to
53192e4
Compare
Add optional nodeGroups[name].maxUnhealthy and nodeGroups[name].nodeStartupTimeout
fields that override the cluster-wide nodeHealthCheck defaults per worker node
group. When unset, a group inherits nodeHealthCheck.{maxUnhealthy,nodeStartupTimeout},
so existing values are unaffected. The MachineHealthCheck render resolves the
per-group value first, reuses the existing int-or-string validation, and names
the offending group in the fail() message when an override is invalid.
Delivers the per-group granularity of the standalone maxUnhealthy proposal on
top of the cluster-wide nodeHealthCheck from the Talos worker bootstrap PR.
Co-authored-by: mattia-eleuteri <mattia@hidora.io>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
53192e4 to
db4db4a
Compare
Adds per-node-group overrides for the worker MachineHealthCheck tuning introduced as a cluster-wide
nodeHealthCheckby #2931.What: optional
nodeGroups[name].maxUnhealthyandnodeGroups[name].nodeStartupTimeout; when unset a group inherits the cluster-widenodeHealthCheck.*(no behaviour change). The per-group value reuses the existing int-or-string validation; an invalid override fails the render naming the offending group.Why: the MachineHealthCheck is rendered per node group, so groups can warrant different remediation tolerances (e.g. a stateful group at
0%, stateless at50%). This is the per-group granularity of the standalone proposal (#2752 / #2935), rebuilt on top of #2931'snodeHealthCheck.Notes: stacked on
phase1-talos-migration(#2931) — retarget tomainonce it merges. Supersedes #2935 (original author credited as co-author). helm-unittest covers per-group override (percent + bare int),nodeStartupTimeoutoverride, fallback-when-unset, and invalid-override rejection.🤖 Generated with Claude Code
Summary by CodeRabbit
maxUnhealthyandnodeStartupTimeout, with automatic fallback to cluster-widenodeHealthCheckwhen unset.maxUnhealthyparsing/rendering to support both bare integers and percentage strings, including more precise validation errors for the exact override path.maxUnhealthy(e.g.,"abc") cases.