Skip to content

CNTRLPLANE-3631: Predictable NodePool Rollout Control#2042

Open
csrwng wants to merge 1 commit into
openshift:masterfrom
csrwng:cntrlplane-3631
Open

CNTRLPLANE-3631: Predictable NodePool Rollout Control#2042
csrwng wants to merge 1 commit into
openshift:masterfrom
csrwng:cntrlplane-3631

Conversation

@csrwng

@csrwng csrwng commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Enhancement proposal for OCPSTRAT-3298 — Predictable NodePool Rollout Control for Hosted Control Planes.

Today, the HyperShift NodePool controller uses a single hash over the entire rendered ignition config to drive rollout decisions. Any change — including automated management-side image digest bumps (e.g., HAProxy) — triggers a full Replace rollout of all worker nodes. This has caused production incidents.

This enhancement proposes:

  • A rollout hash derived only from spec-driven inputs (user MachineConfigs, release version, pull secret, trust bundle, global config), excluding management-side content
  • A new nodePoolCurrentRolloutConfig annotation with safe first-reconcile seeding for existing NodePools
  • A ConfigUpdatePending condition for observability into management-side configuration drift
  • Support for both Replace and InPlace upgrade strategies

Jira

Test plan

🤖 Generated with Claude Code

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jun 15, 2026
@openshift-ci-robot

openshift-ci-robot commented Jun 15, 2026

Copy link
Copy Markdown

@csrwng: This pull request references CNTRLPLANE-3631 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

Enhancement proposal for OCPSTRAT-3298 — Predictable NodePool Rollout Control for Hosted Control Planes.

Today, the HyperShift NodePool controller uses a single hash over the entire rendered ignition config to drive rollout decisions. Any change — including automated management-side image digest bumps (e.g., HAProxy) — triggers a full Replace rollout of all worker nodes. This has caused production incidents.

This enhancement proposes:

  • A rollout hash derived only from spec-driven inputs (user MachineConfigs, release version, pull secret, trust bundle, global config), excluding management-side content
  • A new nodePoolCurrentRolloutConfig annotation with safe first-reconcile seeding for existing NodePools
  • A ConfigUpdatePending condition for observability into management-side configuration drift
  • Support for both Replace and InPlace upgrade strategies

Jira

Test plan

🤖 Generated with Claude Code

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot requested review from enxebre and sjenning June 15, 2026 20:50
@openshift-ci

openshift-ci Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign csrwng for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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

1. An automated image-updater bumps the HAProxy image digest in the HyperShift
operator deployment.
2. The operator reconciles all NodePools. The full `Hash()` changes (because it
includes HAProxy), so a new user-data secret is generated with the latest

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why is a new userdata secret generated here if there's no consumers for it?

Besides, as soon as a new userdata is created, the existing one gets deleted via token.cleanupOutdated
which would make the nodepool unable to perform scaling operations.
And the token secret gets an expiration timestamp (IgnitionServerTokenExpirationTimestampAnnotation), which will result in the payload being deleted from the ignition server

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.

You're right — this is the same issue I addressed in my response to your comment on the workflow section header. The current wording in step 2 is wrong: when only management-side content changes and no rollout is triggered, the controller should NOT create new token/user-data secrets or run cleanupOutdated(). The existing secrets remain valid and the MachineDeployment continues to reference them.

I'll rewrite this workflow to reflect the corrected behavior:

  1. Operator reconciles all NodePools. The RolloutHashWithoutVersion() does NOT change.
  2. No new token or user-data secrets are created. The existing secrets remain valid.
  3. The ConfigUpdatePending condition transitions to True with reason ManagementConfigDrift.
  4. No MachineDeployment or MachineSet spec change occurs. Existing nodes remain undisturbed. Scale-up continues to work using the existing user-data secret.

`nodePoolCurrentRolloutConfig` annotation.
4. The MachineDeployment spec is updated with the new version and
`DataSecretName`, triggering a CAPI rolling Replace.
5. When the rollout completes (`MachineDeploymentComplete()`), the controller

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we articulate the steps for what happens if during a rollout the service provider config changes again.

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.

Good question. I'll add a fourth workflow scenario covering this. The behavior:

Mid-rollout management-side config change:

  1. A spec-driven rollout is in progress — the MachineDeployment is rolling, UpdatingConfig condition is True.
  2. While the rollout is running, a management-side change occurs (e.g., HAProxy image bump).
  3. The rollout hash has NOT changed (management-side content is excluded), so no new rollout is triggered.
  4. The in-progress rollout continues using the existing token/user-data secrets. No new secrets are created (rollout hash unchanged).
  5. When the rollout completes, the nodePoolCurrentRolloutConfig annotation is updated to the current rollout hash. The nodes that were just replaced have the ignition payload that was generated at the start of the rollout — they do NOT automatically pick up the mid-rollout management-side change.
  6. The ConfigUpdatePending condition may transition to True if the management-side change means the current payload differs from what newly-created nodes would get on a fresh provision.

Mid-rollout spec-driven config change:

This is an existing behavior and is unchanged by this enhancement. CAPI handles this via the MachineDeployment's rolling update strategy — the new desired state supersedes the in-progress one, and CAPI continues rolling until all machines match the latest template.


### Goals

1. Management-plane image digest bumps (e.g., HAProxy, CPO) MUST NOT trigger

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we be specific about what things owned by the platform might cause a rollout today? haproxy dataplane image/overrides. What else?
It's also probably worth mentioning that haproxy dataplane image bumping is a particular exception because of early haproxy needs. There's no reason for that image to not come from payload now, unless we ever support shared ingress in selfhosted.

Also should probably include as still a risk to be addressed separately that some config.openshift.io changes to defaults might result in accidental intent for a rollout

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.

Good points. I'll update the Goals section to enumerate the specific platform-owned inputs that can trigger rollouts today:

  1. HAProxy data plane image — the kube-apiserver-proxy static pod image. For shared ingress clusters (ROSA HCP, ARO HCP), this comes from the operator's IMAGE_SHARED_INGRESS_HAPROXY env var rather than the NodePool's release payload. For non-shared-ingress (self-hosted) clusters, it already comes from the NodePool's release payload (haproxy-router component), so it's not an issue there. This is a historical artifact from early shared ingress bootstrapping — there's no reason it can't come from the payload now.

  2. Registry overrides applied to the HAProxy image--registry-overrides on the management cluster rewrites the image reference that gets embedded in the ignition payload, even though data plane CRI-O handles mirroring natively (tracked in OCPBUGS-86415).

  3. config.openshift.io computed defaults — the globalConfigString() function reconciles proxy and image configs with platform-specific defaults (e.g., Status.NoProxy entries like network CIDRs, 169.254.169.254 for AWS). If the operator code changes these defaults, the serialized config changes and triggers a rollout even though the user's spec didn't change.

For (3), the two-hash architecture can address this by hashing only the user's raw spec inputs (HostedCluster.Spec.Configuration.Proxy, HostedCluster.Spec.Configuration.Image) in the rollout hash — without reconciliation or computed defaults. The full reconciled config continues to be used for payload generation. This follows the same pattern as HAProxy: user intent drives rollouts, platform-computed values flow into the payload silently.

I'll update the enhancement to cover this as part of the rollout hash design rather than calling it out as a separate risk.

not yet applied to existing nodes.

### Workflow Description

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we articulate how any workflow impact the lifecycle of the token secrets and payload cache generation and expiration

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.

Good call — this is a gap in the current design that needs to be addressed. Let me trace the problem:

When only management-side content changes (e.g., HAProxy bump):

  1. Hash() changes → isOutdated() returns true
  2. cleanupOutdated() expires the old token secret (2hr TTL) and deletes the old user-data secret
  3. New token/user-data secrets are created with names based on the new Hash()
  4. No rollout triggered (rollout hash unchanged) → MachineDeployment still references the old (now deleted) user-data secret
  5. Scale-up would reference a non-existent secret

The fix: when the rollout hash has not changed, the controller should not create new token/user-data secrets or cleanup existing ones. The existing secrets remain valid — they contain a working ignition payload, and the MachineDeployment continues to reference them. Scale-up nodes get the same config as existing nodes, which is the correct behavior since we're explicitly choosing not to roll out.

For spec-driven rollouts, the lifecycle stays the same as today: new secrets are created, old ones are expired/cleaned up, and the MachineDeployment is updated to reference the new secret.

I'll add a "Token secret and payload cache lifecycle" section to each workflow scenario covering this.

return cg.doParse(configs, cg.haproxyRawConfig)
}

func (cg *ConfigGenerator) parseWithoutHaproxy(configs []corev1.ConfigMap) (string, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

might want to name this after rollout vs non rollout so it's extendable

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.

Agreed. I'll rename the table headers and descriptions to use "rollout" vs "non-rollout" terminology:

Hash Category Inputs Used for
Hash() / HashWithoutVersion() Non-rollout Full MCO config including HAProxy, pull secret name, additional trust bundle name, reconciled global config User-data secret naming, payload generation
RolloutHash() / RolloutHashWithoutVersion() Rollout MCO config excluding HAProxy, pull secret name, additional trust bundle name, user-set global config (proxy spec, image spec — without computed defaults) Rollout decisions

This makes it clearer that the categories are extensible — if new management-side content is added in the future, it goes into the "non-rollout" hash only.

Comment on lines +40 to +41
ignition payload so new nodes always receive the latest configuration, but they
no longer trigger rollouts of existing nodes.

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.

This doesn't sound correct. When only HAProxy changes:

  • A new user-data secret IS generated (different name from Hash())
  • But propagateVersionAndTemplate does NOT update MachineDeployment.Spec.Template.Spec.Bootstrap.DataSecretName (because rollout hash didn't change)
  • Therefore scale-up nodes created by the MachineDeployment will reference the OLD DataSecretName
  • Scale-up nodes get stale management-side config, not "the full payload"

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.

You're right — the summary as written is incorrect. With the corrected design (discussed in the thread with @enxebre), when only management-side content changes, no new token/user-data secrets are created at all. The existing secrets remain valid, and the MachineDeployment continues to reference them. Scale-up nodes get the same config as existing nodes — which is the correct and intended behavior, since we're explicitly choosing not to roll out.

I'll update the summary to reflect this accurately: "Management-side changes do not trigger rollouts, and both existing and scale-up nodes retain the current configuration until the next spec-driven rollout."

@csrwng csrwng force-pushed the cntrlplane-3631 branch from 8703185 to f1de573 Compare June 16, 2026 18:14
reference embedded in the ignition payload, even though data plane CRI-O
handles mirroring natively
([OCPBUGS-86415](https://issues.redhat.com/browse/OCPBUGS-86415)).
- **`config.openshift.io` computed defaults**: The `globalConfigString()`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

how would we differentiate default changes from user intent changes? I don't think this is addressed by this enhacement

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 is addressed in the Implementation Details section under "Two-hash architecture". The rolloutGlobalConfigString() function hashes only the user-set spec fields (HostedCluster.Spec.Configuration.Proxy, HostedCluster.Spec.Configuration.Image) without reconciling platform-specific defaults. The full reconciled config (with computed defaults like Status.NoProxy entries) continues to be used for payload generation via the non-rollout hash.

So the differentiation is: user intent = raw spec fields in the rollout hash; computed defaults = only in the non-rollout hash used for payload generation.

updated to the current rollout hash. The nodes that were replaced have the
ignition payload that was generated at the start of the rollout — they do NOT
automatically pick up the mid-rollout management-side change.
6. The `ConfigUpdatePending` condition may transition to `True` if the

@enxebre enxebre Jun 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we detail how would this be implemented in the impl details section?
how does the controller decides what to set to current not rollout config, if there are two targets, the latest (written in the target annotation) and the one in flight (which is not stored in the annotation anymore because latest overrode that)?

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.

The controller doesn't need to track the in-flight target separately. The nodePoolCurrentRolloutConfig annotation only gets updated when MachineDeploymentComplete() returns true — at that point all machines match the latest template, so the controller writes the current computed rollout hash.

If a second spec-driven change arrives mid-rollout, CAPI handles the superseding: the new desired state updates the MachineDeployment template, and CAPI continues rolling until all machines match the newest template. The annotation simply records "what rollout hash are the nodes actually running" — it doesn't need to know what the previous in-flight target was, because by the time MachineDeploymentComplete() fires, the machines are already on the latest state.

This is already covered in the "Annotation lifecycle" table and the "Rollout decision change" section — the annotation is unchanged during a rollout and only updated on completion.

(e.g., `Status.NoProxy` entries like network CIDRs, `169.254.169.254` for
AWS). If operator code changes these defaults, the serialized config changes
and triggers a rollout even though the user's spec did not change.
2. Customer-facing spec changes (release upgrades, proxy config, user

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Are changes like the HostedCluster's CR ImageContentSources included in this part as well?

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 — ImageContentSources is a user-set field on the HostedCluster spec, so changes to it are considered customer-facing and would continue to trigger rollouts. It flows into the ignition config and is included in both the rollout and non-rollout hashes.

That said, for ARO-HCP where this field has a dual nature (OCP image entries managed by the platform alongside user-provided entries), a platform-side change to the OCP image entries would also trigger a rollout since the field is treated as a single unit. If that becomes a concern, it could be addressed separately by splitting the platform-managed entries out of the rollout hash — but that's not in scope for this enhancement.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks


#### Spec-driven rollout

1. The cluster administrator updates `NodePool.spec.config` to add a

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What about the HostedCluster.Spec.ImageContentSources changes? Same as #2042

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

For aro-hcp this is two folds;

  • OCP images are in this list; these are managed by ARO-HCP
  • And some images comes from the user provided input

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.

Same answer as above — ImageContentSources is treated as a customer-facing spec field and changes to it trigger rollouts. The ARO-HCP dual-nature concern (platform-managed OCP image entries mixed with user-provided entries) is acknowledged but out of scope for this enhancement. If needed, splitting platform-managed entries out of the rollout hash could be a follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The ARO-HCP dual-nature concern (platform-managed OCP image entries mixed with user-provided entries) is acknowledged but out of scope for this enhancement. If needed, splitting platform-managed entries out of the rollout hash could be a follow-up.

I agree. I see it happening as existing clusters will be upgrade to v5 but there are possibilities of solving that e.g do the change a part of the upgrade trigger so that it falls under that one major upgrade

This enhancement decouples the rollout-triggering hash from management-side
implementation details by introducing a *rollout hash* derived only from
customer-facing spec inputs. Management-side changes do not trigger rollouts,
and both existing and scale-up nodes retain the current configuration until the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What is perhaps not super clear - are we omitting things from the hash which truly do not influence ignition, in which case this is fixing a bug, or which do influence it, so we are relaxing the behavior at a risk of correctness? If the latter, what is the risk and why is it worth the benefit?

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.

Good question — I've updated the Summary to make this distinction explicit. This is a deliberate relaxation, not a bug fix: the omitted inputs (HAProxy image, registry overrides, computed config defaults) do influence the ignition payload content. Excluding them from the rollout hash means existing and scale-up nodes will run with stale management-side configuration until the next spec-driven rollout.

The trade-off is acceptable because the previous management-side configuration remains functional — existing nodes continue to operate correctly — and the operational cost of unexpected fleet-wide node replacements far outweighs the benefit of immediate propagation of non-customer-facing changes.

an expiration timestamp (2-hour TTL), and the previous user-data secret is
deleted (the MachineDeployment will be updated to reference the new one).
3. The controller detects that `RolloutHashWithoutVersion()` differs from the
`nodePoolCurrentRolloutConfig` annotation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Which CR will have this annotation the NodePool CR?
Followup question; is this an existing annotation or a new one? If a new one why an annotation over say a .status condition or something?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Which CR will have this annotation the NodePool CR? Followup question; is this an existing annotation or a new one?

This is answered here https://github.com/csrwng/enhancements/blob/f1de573248003660bd274bc825d7d409b4208807/enhancements/hypershift/predictable-nodepool-rollout-control.md#api-extensions

If a new one why an annotation over say a .status condition or something?

I am curious to lean more on the difference between the two design choices. annotation vs .status.

In https://github.com/csrwng/enhancements/blob/f1de573248003660bd274bc825d7d409b4208807/enhancements/hypershift/predictable-nodepool-rollout-control.md#api-extensions I read that the annotation is controller-managed so a user wouldn't be able to set it, but I am keen to learn more on why an annotation was preferred here

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, this annotation is on the NodePool CR — it's a new annotation described in the API Extensions section.

For why an annotation over .status: the annotation stores an opaque hash value that the controller uses internally to compare rollout state across reconciles. It's not meaningful to users — it's just a string like "a3f8b2c1". Status conditions are booleans with reason/message, designed for human-readable observability. An opaque comparison value doesn't fit that model.

The human-readable part is covered by the ConfigUpdatePending status condition, which tells operators whether management-side drift exists. So the two work together: the annotation is the internal mechanism, the condition is the external signal.

We also follow the existing pattern — nodePoolAnnotationCurrentConfig and nodePoolAnnotationCurrentConfigVersion are both annotations used the same way.


#### Spec-driven rollout

1. The cluster administrator updates `NodePool.spec.config` to add a

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The HAProxy image can also be driven via the NodePoolHAProxyImageAnnotation annotation. Does this enhancement consider this a spec driven as well?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In ARO-HCP we don't allow the user to BYO HAProxy image, so this technically a platform driven choice. But a question is worth asking on what hypershift considers this to be.

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.

The NodePoolHAProxyImageAnnotation is platform-driven, not spec-driven — it's the mechanism by which the operator injects the HAProxy image into the ignition payload. This enhancement explicitly treats it as management-side content excluded from the rollout hash. That's the core use case this enhancement addresses.

Changes to this annotation will update the non-rollout hash (affecting payload generation and user-data secret naming) but will NOT change the rollout hash, so no node replacement is triggered.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks

1. A `rolloutMcoRawConfig` field, computed by a new `parseWithoutHaproxy()`
method that calls the existing parsing logic with an empty haproxy config
string.
2. A `rolloutGlobalConfig` field, computed by serializing only the user-set spec

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I guess HC.spec.imageContentSources falls under this category as well?

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 — ImageContentSources feeds into the ignition config through the MCO config generation pipeline, so it's already captured in the "MCO config excluding HAProxy" input in the rollout hash. Changes to it will change the rollout hash and trigger a rollout.

| Management-side change only | Annotation unchanged — no rollout |

The `currentRolloutConfigHash != ""` guard in the rollout decision ensures that
if the annotation is somehow absent (e.g., manually deleted), the controller

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What happens when user sets it to some non empty value (Is it even possible or this action won't be possible to do by the user)?

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.

There's no admission webhook preventing it, so a user with direct access to NodePool objects (i.e., the service provider, not customers who own the hosted clusters — customers don't have access to management cluster resources like NodePools) could technically edit the annotation.

If they set it to a value that doesn't match the current computed rollout hash, the controller would see a mismatch and trigger a rollout on the next reconcile. If they set it to the current rollout hash, it's a no-op. If they delete it, the controller re-seeds it without triggering a rollout (the `currentRolloutConfigHash != """ guard handles this).

This is the same trust model as the existing nodePoolAnnotationCurrentConfig and nodePoolAnnotationCurrentConfigVersion annotations — service providers shouldn't touch them, but if they do, the worst case is an unnecessary rollout.

@SudoBrendan SudoBrendan left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is forced updates by platforms on or off the table? If this has already been decided outside this doc, my apologies - tried to flag what such a thing might imply.

Comment on lines +99 to +104
1. **In-place config refresh for existing nodes.** When management-side
configuration changes are excluded from triggering a Replace rollout, existing
worker nodes retain their previous configuration until the next spec-driven
rollout. Delivering non-disruptive configuration changes to existing nodes
without node replacement is tracked separately as
[OCPSTRAT-3299](https://issues.redhat.com/browse/OCPSTRAT-3299).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm still getting accustomed to all the nuances here operationally - but I could imagine a scenario where we need to push out a fleet-wide update related to vulnerabilities or similar (e.g. HAProxy has a critical CVE). Will we have a mechanism to force reconciliation if needed (either immediately or within a timeframe)? If I'm reading correctly - SREs would have no way to do this; customers are the only ones that can push updates out - if they don't do so, they don't get the fixes, potentially ever. Is this the known customer experience/acceptable risk?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't believe we are on the hook for forcing a customer to change what is running in their subscription - we need to provide them the option to upgrade, but do not need to enforce that e.g. all those applications running in the customer's side are truly upgraded.

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.

Agreed with Steve's point — we're not on the hook for forcing customers to roll out changes in their subscription.

That said, this enhancement does provide the visibility layer: the ConfigUpdatePending condition tells SREs exactly which NodePools have pending management-side changes that haven't been rolled out yet. This can be used to build fleet-level dashboards showing which clusters are running stale management-side config.

For the force-rollout mechanism: that's out of scope for this enhancement but could be a follow-up. The architecture supports it — a service provider with access to NodePool objects could trigger a rollout by making a no-op spec change (e.g., adding and removing a label on a MachineConfig). A more formal mechanism (e.g., an annotation that explicitly requests a rollout) could be designed separately if needed.

Delivering management-side changes to existing nodes without full node replacement is tracked as OCPSTRAT-3299.

Comment on lines +502 to +503
- `ConfigUpdatePending` condition is documented and surfaced in monitoring
dashboards.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I assume monitoring dashboards implies that we'll have a metric to query? Is it worth defining in this doc?

Operationally, this would be invaluable for us to see what percentage of clusters in our fleet are pending an update, and which have completed. It would be even better if there was some way to differentiate distinct updates we push in this metric - I can almost guarantee we'll have some customers that don't make an update call for months or even years (e.g. If we could see they got update foo but not bar or biz). This would also be incredible if we do have that "force" button for critical things that must go out immediately - because we can see/prove with a dashboard that "yes, the fleet got the update - every single cluster" or "cluster ABC missed the update, we have to figure out why".

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.

The ConfigUpdatePending condition is the metric source — kube-state-metrics already exposes NodePool conditions as Prometheus metrics, so fleet-level queries like "what percentage of NodePools have pending management-side updates" are immediately available without defining new custom metrics.

For differentiating distinct updates: the condition's message field describes the drift source (e.g., ManagementConfigDrift), but it doesn't currently encode which specific update is pending. Adding a more granular breakdown (e.g., which hash changed, or what content differs) could be a follow-up if the basic condition isn't sufficient for operational needs.

Specific dashboard and alerting design is listed as a GA graduation criterion — we'll define those details as part of the production rollout rather than in the enhancement itself.

Comment on lines +559 to +561
The new `ConfigUpdatePending` condition is a standard NodePool status condition
and does not affect API throughput or availability. It is updated as part of the
existing NodePool reconciliation loop.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

note: if we do add a "force" button for SRE - we'd likely want to differentiate what kind of rollout happened (customer or platform initiated)

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.

Good note. The ConfigUpdatePending condition already provides some of this distinction — if it was True before a rollout and transitions to False after, you know the rollout picked up pending management-side changes alongside the spec-driven change.

Explicit tracking of "who initiated this rollout" (customer vs platform) would be a follow-up concern if a force-rollout mechanism is added. Noted for future design.

and does not affect API throughput or availability. It is updated as part of the
existing NodePool reconciliation loop.

### Failure Modes

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

what happens when customers have PDBs that block rollouts? Is this worth documenting here - how it may/may not impact the annotation lifecycle?

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.

PDBs blocking rollouts is existing CAPI behavior unchanged by this enhancement. If a PDB blocks machine draining, the MachineDeployment rollout stalls — MachineDeploymentComplete() never returns true, so the nodePoolCurrentRolloutConfig annotation stays at the old value and the UpdatingConfig condition remains True.

The rollout eventually times out per CAPI's progressDeadlineSeconds. This enhancement doesn't change any of that — the annotation lifecycle is purely driven by the existing completion signals. No special PDB handling is needed.

Introduces a two-hash architecture in the NodePool controller to decouple
rollout decisions from management-side configuration changes. A new "rollout
hash" derived only from customer-facing spec inputs (user MachineConfigs,
release version, pull secret, trust bundle, user-set proxy/image config)
determines whether to trigger Replace or InPlace rollouts. Management-side
changes (HAProxy image bumps, registry overrides, config.openshift.io computed
defaults) no longer trigger rollouts.

Tracking: CNTRLPLANE-3631
@csrwng csrwng force-pushed the cntrlplane-3631 branch from f1de573 to 4f3596e Compare July 6, 2026 19:38
@openshift-ci

openshift-ci Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@csrwng: all tests passed!

Full PR test history. Your PR dashboard.

Details

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. I understand the commands that are listed here.

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

Labels

jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants