-
Notifications
You must be signed in to change notification settings - Fork 1.6k
fix(operator): run Dynamo + Grove operators HA to stop webhook-flap CI failures #12712
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| # Keep at least one operator pod (and therefore one serving admission-webhook | ||
| # endpoint) available during voluntary disruptions such as node drains and | ||
| # upgrades. Only rendered when running >1 replica; with a single replica a | ||
| # minAvailable:1 budget would block node drains entirely. | ||
| {{- if gt (int .Values.controllerManager.replicas) 1 }} | ||
| apiVersion: policy/v1 | ||
| kind: PodDisruptionBudget | ||
| metadata: | ||
| name: {{ include "dynamo-operator.fullname" . }}-controller-manager | ||
| labels: | ||
| app.kubernetes.io/component: manager | ||
| {{- include "dynamo-operator.labels" . | nindent 4 }} | ||
| spec: | ||
| minAvailable: 1 | ||
| selector: | ||
| matchLabels: | ||
| control-plane: controller-manager | ||
| {{- include "dynamo-operator.selectorLabels" . | nindent 6 }} | ||
| {{- end }} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,7 +92,13 @@ controllerManager: | |
| requests: | ||
| cpu: 512m | ||
| memory: 1Gi | ||
| replicas: 1 | ||
| # Run 2 replicas so a single pod restart/reschedule never leaves the admission | ||
| # webhook without a serving endpoint (the cause of intermittent | ||
| # "dial tcp ...:443: connect: connection refused" DGD-admission failures). | ||
| # Safe with >1 replica: leader election elects one active manager; the webhook | ||
| # is stateless and served by all replicas. Paired with a PodDisruptionBudget | ||
| # (poddisruptionbudget.yaml) and a RollingUpdate strategy (deployment.yaml). | ||
| replicas: 2 | ||
|
Comment on lines
+95
to
+101
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Two operator pods starting together can fight over the webhook certificate and crash on startup Both operator copies generate and write the webhook certificate at startup without any coordination or retry ( Unsynchronized bootstrap of the webhook TLS secret
Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
+95
to
+101
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 Namespace-restricted mode with 2 replicas is untested by this change The replica default is global; namespace-restricted installs ( Was this helpful? React with 👍 or 👎 to provide feedback. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Defaulting the operator to two replicas races the pre-leader-election webhook certificate bootstrap because both pods can update the same TLS Secret and one exits on an Update conflict. Fix: make certificate bootstrap conflict-tolerant before enabling HA by default. 🤖 AI FixIn deploy/operator/internal/cert/cert.go, update CertManager.bootstrapCertSecret or refreshCertSecret to wrap the Secret get/validation/update path in retry.RetryOnConflict, re-fetch the Secret on each retry, and return success when another replica has already written a valid CA/server certificate. |
||
| serviceAccount: | ||
| annotations: {} | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||||||||
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||||||||||
| # SPDX-License-Identifier: Apache-2.0 | ||||||||||
| # | ||||||||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||||
| # you may not use this file except in compliance with the License. | ||||||||||
| # You may obtain a copy of the License at | ||||||||||
| # | ||||||||||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||
| # | ||||||||||
| # Unless required by applicable law or agreed to in writing, software | ||||||||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||
| # See the License for the specific language governing permissions and | ||||||||||
| # limitations under the License. | ||||||||||
| # | ||||||||||
| # The upstream Grove chart ships no PodDisruptionBudget, so define one here to | ||||||||||
| # keep at least one grove-operator pod (and its PodCliqueSet admission webhook) | ||||||||||
| # available during voluntary disruptions. Only rendered when Grove is installed | ||||||||||
| # and running >1 replica (see the `grove:` block in values.yaml). | ||||||||||
| {{- $grove := .Values.grove | default dict }} | ||||||||||
| {{- $groveInstalled := and .Values.global .Values.global.grove (or .Values.global.grove.install .Values.global.grove.enabled) }} | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Grove PDB renders when global.grove.enabled=true even if this chart did not install Grove, so the platform chart can create or manage a disruption budget for an externally managed operator with unrelated replica settings. Fix: render this PDB only when global.grove.install=true. 🤖 AI FixIn deploy/helm/charts/platform/templates/grove-poddisruptionbudget.yaml, change $groveInstalled to require only .Values.global.grove.install and remove .Values.global.grove.enabled from the PDB render condition. |
||||||||||
| {{- if and $groveInstalled (gt (int ($grove.replicaCount | default 1)) 1) }} | ||||||||||
|
Comment on lines
+21
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Availability budget is created for an externally installed Grove operator, blocking node drains A minimum-availability rule for Grove is created ( install vs. enabled semantics and the replica-count gate
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||
| apiVersion: policy/v1 | ||||||||||
| kind: PodDisruptionBudget | ||||||||||
| metadata: | ||||||||||
| name: grove-operator | ||||||||||
| namespace: {{ .Release.Namespace }} | ||||||||||
| labels: | ||||||||||
| app.kubernetes.io/name: grove-operator | ||||||||||
| app.kubernetes.io/part-of: dynamo-platform | ||||||||||
| spec: | ||||||||||
| minAvailable: 1 | ||||||||||
| selector: | ||||||||||
| matchLabels: | ||||||||||
| app.kubernetes.io/name: grove-operator | ||||||||||
| {{- end }} | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -282,6 +282,12 @@ dynamo-operator: | |
| # Grove component - distributed inference orchestration | ||
| # Installation is controlled by global.grove.install above. | ||
| grove: | ||
| # -- Run 2 Grove operator replicas so a single pod restart (e.g. the expected | ||
| # cert-resync restart, grove issue #701) never leaves the PodCliqueSet webhook | ||
| # without a serving endpoint. Grove enables leader election by default, so only | ||
| # one replica reconciles; the webhook is stateless and served by all replicas. | ||
| # Paired with a PodDisruptionBudget (templates/grove-poddisruptionbudget.yaml). | ||
| replicaCount: 2 | ||
|
Comment on lines
+285
to
+290
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 Grove subchart value key The parent chart adds Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| # -- Node tolerations for Grove pods | ||
| tolerations: [] | ||
| # -- Affinity for Grove pods | ||
|
|
||
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.
🔴 CI operator-readiness wait never succeeds once each operator runs two pods
The number of operator pods is doubled by defaulting to two copies (
replicas: 2atdeploy/helm/charts/platform/components/operator/values.yaml:101, plusreplicaCount: 2atdeploy/helm/charts/platform/values.yaml:290) while the CI readiness gate still insists on seeing exactly two pods in total, so every deploy job waits the full 30 minutes and then fails.Impact: The very CI jobs this change is meant to stabilize will time out and fail at the operator-readiness step.
Hard-coded pod count in the setup-dynamo-operator readiness loop
.github/actions/setup-dynamo-operator/action.yml:494-514collects pods whose names start withdynamo-platform-dynamo-operator-controller-manager-orgrove-operator-and breaks out of the wait only when[ "${#POD_STATUSES[@]}" -eq 2 ]and both entries are1/1. With 2 replicas of each operator there will be 4 matching pods, so the condition never holds and the step errors with "Expected two ready 1/1 operator Pods after 1800s". The loop must be generalized (e.g. require all matching pods ready and at least the expected count, or usekubectl rollout status).Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.