Skip to content

CNTRLPLANE-3776: In-Place Config Refresh for NodePool Worker Nodes#2053

Open
csrwng wants to merge 1 commit into
openshift:masterfrom
csrwng:enhancement/in-place-config-refresh
Open

CNTRLPLANE-3776: In-Place Config Refresh for NodePool Worker Nodes#2053
csrwng wants to merge 1 commit into
openshift:masterfrom
csrwng:enhancement/in-place-config-refresh

Conversation

@csrwng

@csrwng csrwng commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Enhancement proposal for OCPSTRAT-3299 — In-Place Config Refresh for NodePool Worker Nodes.

  • Introduces a config refresh controller in the HCCO that delivers non-disruptive configuration changes (image bumps, SSH key rotations, trust bundles, registry mirrors) to existing HyperShift worker nodes without node replacement, drain, or reboot.
  • Builds on OCPSTRAT-3298 (Predictable NodePool Rollout Control) hash split to detect payload-only changes.
  • Uses the existing MCD pod pattern with an additional no-reboot-paths ConfigMap key to communicate safe file paths and their post-apply actions to MCD, requiring a small targeted change to MCD's HyperShift code path.
  • Works for both Replace and InPlace upgrade strategy NodePools.

Test plan

🤖 Generated with Claude Code

@openshift-ci
openshift-ci Bot requested review from enxebre and sjenning July 2, 2026 14:05
@openshift-ci

openshift-ci Bot commented Jul 2, 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 sjenning 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

@csrwng csrwng changed the title Enhancement: In-Place Config Refresh for NodePool Worker Nodes OCPSTRAT-3299: Enhancement: In-Place Config Refresh for NodePool Worker Nodes Jul 2, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 2, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 2, 2026

Copy link
Copy Markdown

@csrwng: This pull request references OCPSTRAT-3299 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 feature to target either version "5.0." or "openshift-5.0.", but it targets "openshift-5.1" instead.

Details

In response to this:

Summary

Enhancement proposal for OCPSTRAT-3299 — In-Place Config Refresh for NodePool Worker Nodes.

  • Introduces a config refresh controller in the HCCO that delivers non-disruptive configuration changes (image bumps, SSH key rotations, trust bundles, registry mirrors) to existing HyperShift worker nodes without node replacement, drain, or reboot.
  • Builds on OCPSTRAT-3298 (Predictable NodePool Rollout Control) hash split to detect payload-only changes.
  • Uses the existing MCD pod pattern with an additional no-reboot-paths ConfigMap key to communicate safe file paths and their post-apply actions to MCD, requiring a small targeted change to MCD's HyperShift code path.
  • Works for both Replace and InPlace upgrade strategy NodePools.

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.

@csrwng csrwng changed the title OCPSTRAT-3299: Enhancement: In-Place Config Refresh for NodePool Worker Nodes OCPSTRAT-3299: In-Place Config Refresh for NodePool Worker Nodes Jul 2, 2026

@muraee muraee 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.

Review of the in-place config refresh enhancement. Five comments below — three substantive gaps, one API gap, one nit.

4. The config refresh controller in the HCCO detects the payload hash change
via updated annotations on Node/Machine objects.
5. The controller computes the file-level diff between the current node config
and the target config.

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.

Gap: How is the file-level diff computed?

This step says the controller "computes the file-level diff between the current node config and the target config" but doesn't explain where the "current" side comes from.

The target config is straightforward (token secret / user-data secret payload). But for the current config:

  • For InPlace pools, MCD sets a currentConfig annotation on the node — but that's just a hash, not the full ignition payload. You can't compute a file-level diff from a hash.
  • For Replace pools, there's no mechanism today for the HCCO to know what config a node is running.

The controller would need to either:

  1. Regenerate the old payload from the config version the node was built with — but old release images or MachineConfigs may have been garbage collected.
  2. Store the previous payload (ConfigMap, annotation, etc.) — extra storage, staleness risk.
  3. Read from the node via a privileged pod — adds a pre-flight step.

This is a fundamental implementation detail that should be explicitly specified.

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.

Addressed. The design has been revised so the controller no longer computes a file-level diff at all. Instead:

  1. The hash split classifies inputs (spec fields) into rollout hash vs payload hash at the NodePool controller level.
  2. The config refresh controller only detects that the payload hash changed while the rollout hash did not — this guarantees only refresh-eligible inputs changed.
  3. The controller sends the full updated ignition payload to MCD via the upgrade ConfigMap.
  4. MCD handles the file-level diff by comparing the desired config from the ConfigMap against the current config read from disk (/etc/mcd-currentconfig.json) — this is MCD's existing behavior.

This sidesteps the "where does the current config come from" problem entirely — MCD already has it on disk. See the revised "Config Refresh Controller" and "Safety Invariant" sections.


1. If a Replace rollout is in progress for a NodePool, the config refresh
controller skips nodes that are pending replacement.
2. Refresh annotations are separate from upgrade annotations, so the two

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.

Gap: Interaction with the existing in-place upgrader needs more detail.

This section covers the Replace rollout case but doesn't address the InPlace upgrader interaction:

  1. Simultaneous upgrade + refresh: If an InPlace-strategy node needs both a version upgrade (via the existing upgrader) and a config refresh, does the upgrade subsume the refresh? Do they race? Both would create MCD pods on the same node.

  2. "Pending replacement" detection: The enhancement says "the config refresh controller skips nodes pending replacement," but MachineDeployment rollouts delete Machines — they don't annotate nodes as "pending." How does the controller know a node is about to be replaced?

The statement "Refresh annotations are separate from upgrade annotations, so the two mechanisms do not interfere" is not sufficient — separate annotations don't prevent two MCD pods from running on the same node simultaneously.

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.

Addressed. Added two new workflow sections:

"Interaction with concurrent Replace rollout" now specifies:

  • Controller pauses refresh for the entire NodePool during active rollouts, detected via MachineDeployment.status.updatedReplicas != status.replicas or Machines with deletion timestamps.
  • Controller checks for existing MCD pods on target nodes before creating new ones — prevents two MCD pods on the same node.

"Interaction with InPlace upgrade strategy" (new section) specifies:

  • Version upgrades take precedence — the upgrade delivers the full config (including refresh-eligible changes), subsuming the refresh.
  • Controller checks for active upgrade MCD pods before creating refresh MCD pods.
  • After upgrade completes, controller re-evaluates whether refresh is still needed by comparing annotations.

]
```

The `no-reboot-paths` key contains a JSON array of objects, each specifying:

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.

Security: threat model for no-reboot-paths ConfigMap tampering.

The no-reboot-paths key is effectively a privileged instruction to MCD: "don't reboot for these paths, execute these actions instead." If an attacker gains write access to the upgrade namespace/ConfigMap in the guest cluster, they could:

  • Suppress reboots for changes that actually require them (e.g., adding a kernel arg path to no-reboot-paths).
  • Inject arbitrary service restart/reload commands.

The enhancement should discuss:

  • Who has write access to the <pool>-upgrade namespace and its ConfigMaps in the guest cluster?
  • Should MCD independently validate paths against its own hardcoded reboot triggers (OS, kargs, FIPS, etc.) before honoring no-reboot-paths, as a defense-in-depth measure?
  • This is especially relevant since the enhancement extends MCD pods to Replace-strategy pools for the first time, broadening the attack surface.

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.

Addressed. Added a "Security: no-reboot-paths Threat Model" subsection under Risks and Mitigations covering all three points:

  1. Access control: The <pool>-upgrade namespace uses restricted RBAC — only the HCCO service account has write access. cluster-admin access is expected and consistent with the existing threat model.

  2. Defense-in-depth in MCD: MCD independently validates hardcoded reboot triggers (osUpdate, kargs, fips, kernelType, extensions) in the machineConfigDiff struct before honoring no-reboot-paths. Even if an attacker adds a kernel arg path to no-reboot-paths, MCD still reboots because it detects the structural kargs change.

  3. Service name restriction: The services field is limited to a hardcoded set of valid service names in MCD (e.g., crio, kubelet, update-ca-trust). Unrecognized names are rejected/ignored, preventing arbitrary service injection.

  4. Attack surface: Clarified that this does not broaden the MCD pod attack surface — the existing in-place upgrader already creates MCD pods using the same ConfigMap pattern. The no-reboot-paths key is additive and gated by MCD's defense-in-depth checks.

4. Changes that require a reboot are excluded from the refresh path and
deferred to the next Replace rollout.
5. NodePool status reports the refresh state, indicating whether nodes are
up-to-date with the latest non-disruptive config or have a refresh pending.

@muraee muraee Jul 3, 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.

Gap: NodePool status conditions for refresh state should be defined.

Goal #5 says "NodePool status reports the refresh state" and the GA graduation criteria include "NodePool status reporting for refresh state," but the enhancement doesn't define what conditions would be added.

At minimum, sketch the condition types (e.g., ConfigRefreshProgressing, ConfigRefreshDegraded, ConfigRefreshUpToDate), their semantics, and when they transition. This is an API contract that reviewers and downstream consumers need to evaluate.

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.

Addressed. Added a "NodePool Status Conditions" subsection under API Extensions defining three conditions:

  • ConfigRefreshProgressing: True when MCD pods are running on nodes; message reports progress (e.g., "Refreshing 2/5 nodes").
  • ConfigRefreshComplete: True when all nodes have been refreshed to the latest payload hash.
  • ConfigRefreshDegraded: True when refresh has failed on one or more nodes (MCD pod failure, timeout, post-apply action error); message identifies affected nodes and failure reason.

Also documented the 5 condition transition scenarios (new payload hash detected → progressing → per-node completion → all complete, failure case, post-Replace-rollout reset).

| Change Category | File Path(s) | Post-Apply Action |
|----------------|-------------|-------------------|
| Management-side container image refs in static pod manifests | `/etc/kubernetes/manifests/kube-apiserver-proxy.yaml` | None |
| SSH authorized key changes | `passwd.users.sshAuthorizedKeys` | None |

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.

nit: passwd.users.sshAuthorizedKeys is not a file path — it's an ignition passwd section entry. The allowlist is described elsewhere as mapping "file paths" to actions. Consider clarifying in the allowlist description that it covers both storage.files paths and specific ignition passwd section entries, so the safety gate logic accounts for both.

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 catch — addressed. The "Eligible vs. Ineligible Changes" section now explicitly states:

The allowlist covers both storage.files paths and ignition passwd section entries (e.g., sshAuthorizedKeys).

The table entry for SSH keys now uses passwd.users[].sshAuthorizedKeys to make it clear this is an ignition passwd entry, not a file path. The "Built-in Path-to-Action Map" section (formerly "Built-in Allowlist") also references "all file paths (and ignition passwd entries)".

@csrwng csrwng changed the title OCPSTRAT-3299: In-Place Config Refresh for NodePool Worker Nodes CNTRLPLANE-3776: In-Place Config Refresh for NodePool Worker Nodes Jul 6, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 6, 2026

Copy link
Copy Markdown

@csrwng: This pull request references CNTRLPLANE-3776 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-3299 — In-Place Config Refresh for NodePool Worker Nodes.

  • Introduces a config refresh controller in the HCCO that delivers non-disruptive configuration changes (image bumps, SSH key rotations, trust bundles, registry mirrors) to existing HyperShift worker nodes without node replacement, drain, or reboot.
  • Builds on OCPSTRAT-3298 (Predictable NodePool Rollout Control) hash split to detect payload-only changes.
  • Uses the existing MCD pod pattern with an additional no-reboot-paths ConfigMap key to communicate safe file paths and their post-apply actions to MCD, requiring a small targeted change to MCD's HyperShift code path.
  • Works for both Replace and InPlace upgrade strategy NodePools.

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.

@csrwng

csrwng commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

/jira refresh

@openshift-ci-robot

openshift-ci-robot commented Jul 6, 2026

Copy link
Copy Markdown

@csrwng: This pull request references CNTRLPLANE-3776 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:

/jira refresh

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.

Introduces a config refresh mechanism (OCPSTRAT-3299) that delivers
non-disruptive configuration changes to existing HyperShift worker
nodes without node replacement, drain, or reboot. Uses the existing
MCD pod pattern with an additional `no-reboot-paths` ConfigMap key
to communicate safe file paths and post-apply actions to MCD.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@csrwng
csrwng force-pushed the enhancement/in-place-config-refresh branch from 611db6c to 0b6a409 Compare July 9, 2026 18:07
@openshift-ci

openshift-ci Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@csrwng: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/markdownlint 0b6a409 link true /test markdownlint

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.

@yuqi-zhang yuqi-zhang 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.

I'm generally a bit hesitant on the current implementation, which has some nuances that I don't think it handles as well, plus it drifts between standalone and HCP behaviour quite a bit. Some initial questions/comments in line

Rollout Control) introduces a hash split so that management-side changes no
longer trigger Replace rollouts. However, OCPSTRAT-3298's hash split only
covers management-side content (CPO-generated configs, HAProxy image refs,
etc.). User-driven HostedCluster spec changes — such as SSH key rotations,

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.

A few notes as I read this (maybe covered later in the doc - will update)

  1. This is (looks like deliberately) different behaviour compared to the MCO, where we don't split the actual content, but instead just calculate the diff in the MCD when upgrading and compare against user+system provided NDPs. I think we should consider whether it's worth matching those behaviours so we're not introducing additional drift between MCO and Hypershift.

  2. It seems to imply that the use cases covered by 3298 could use this mechanism as well, just that those changes would get written but not take effect until a spec-change-reboot? Or would changes covered by 3298 cause disruption directly (e.g. static pod definitions that would cause restarts)

  3. Can the user specify a "only allow non-disruptive diffs" mode? If the hypershift controller sees multiple changes (some disruptive, some not) how does it determine whether/how to apply those? Does that happen in parallel or are they all done by the main disruptive path?

  4. Some of the use cases here also have varying levels of disruption to the cluster in the MCO. Are you planning on implementing different mechanisms to handle different updates?

  5. The MCO has two paths: a cert writer and a MCD updater. The cert writer isn't implemented in Hypershift at all so those certs, despite not being part of machineconfig objects anymore, are rotated live. Do you plan on implementing that as well?

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.

I see a Mixed changes section below that answers the second half of 3, and I guess the first half of 3 is "no"

3. The mechanism guarantees no node reboots by classifying inputs into rollout
hash (reboot-requiring) and payload hash (refresh-eligible) categories, with
defense-in-depth validation in MCD.
4. Changes that require a reboot are excluded from the refresh path and

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.

(as asked above) how does this deferral get determined?

2. Changing the behavior of the InPlace upgrade strategy for full version
upgrades. The config refresh is a complementary mechanism for non-disruptive
changes, not a replacement for the existing upgrade lifecycle.
3. User-facing API changes to NodePool spec. The config refresh mechanism is

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.

The MCO does allow user NDPs, it sounds like that wouldn't be the case for Hypershift?

type, extensions, OS image.
- **Payload-only inputs** (changes eligible for in-place refresh):
`spec.sshKey`, `spec.pullSecret`, `spec.additionalTrustBundle`,
`spec.imageContentSources`, `spec.configuration.proxy`,

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.

Hmm, is imageContentSources ICSP? Or something else entirely?

I thought we switched over to IDMS/ITMS generally speaking (and there's additional logic to these changes in the MCO, not always disruptionless)

- **Rollout hash inputs** (changes trigger node replacement): release image,
NodePool `.config` (user MachineConfigs), FIPS mode, kernel arguments, kernel
type, extensions, OS image.
- **Payload-only inputs** (changes eligible for in-place refresh):

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.

(As asked above) do we plan on handling certificate refreshes?

configuration (including both spec-driven and refresh-eligible changes),
so no post-rollout refresh is needed for those nodes.

#### Interaction with InPlace upgrade strategy

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.

Does the inplace upgrader/replace upgrader "wait for a refresh to finish"? It sounds so far that the guard is one directional - the refresh controller waits for the an ongoing upgrade, but if it's already refreshing (and is slow) I think there's a possibility of conflict the other direction.


#### ConfigMap `no-reboot-paths` Key

Today, the in-place upgrader creates a ConfigMap in the guest cluster with two

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.

Some of these at least are shared with the MCO. Would you be able to take the base MCO list and build on it instead of completely redefining?

- `action`: Post-apply action type (`None`, `Restart`, `Reload`)
- `services`: List of services to restart/reload (when action is not `None`)

Paths already in MCD's legacy safe list (e.g., `/var/lib/kubelet/config.json`,

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.

I forget if that's the case, but I think we should depend on the nodedisruptionpolicy list for these so they're always up to date.

Also, many certificate paths are handled differently, I think /var/lib/kubelet/config.json is maybe one of them?

4. **Create ConfigMap**: Creates the upgrade ConfigMap in the guest cluster with
the full ignition payload (`config`), hash (`hash`), and the
`no-reboot-paths` key containing entries for all refresh-eligible file paths
and their post-apply actions. The `no-reboot-paths` list is a static,

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.

(also asked above) so if we do see a diff with some refresh paths and some non-refresh paths, do we provide the MCD with just the refresh paths? Or we just do the "full reboot upgrade" workflow here?

unexpected full-fleet node replacements from management-side image bumps — and
would leave no path to deliver non-disruptive changes without node replacement.

### Node Disruption Policies (MachineConfiguration resource)

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.

Even if we don't use the API, I think it would be best if we can share some of the logic so it's not a completely separate implementation as designed right now

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.

4 participants