Skip to content

MCO-2599: makes bootc-operator aware of karpenter - #204

Open
cheesesashimi wants to merge 1 commit into
bootc-dev:mainfrom
cheesesashimi:zzlotnik/karpenter-annotations
Open

cheesesashimi wants to merge 1 commit into
bootc-dev:mainfrom
cheesesashimi:zzlotnik/karpenter-annotations

Conversation

@cheesesashimi

@cheesesashimi cheesesashimi commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

If a cluster contains both bootc-operator and Karpenter, it is possible that Karpenter could reap cluster nodes while a bootc-operator driven update is in progress. To prevent that, we detect whether the Node object is owned by a Karpenter NodeClaim object. And in that situation, we apply both the Karpenter Do Not Repair (karpenter.sh/do-not-repair) and Do Not Disrupt (karpenter.sh/do-not-disrupt) annotations to each node when it is cordoned for an update and we remove it after the update is completed.

@cheesesashimi
cheesesashimi force-pushed the zzlotnik/karpenter-annotations branch from cedee9d to 902f247 Compare September 29, 2026 18:49
Comment thread internal/controller/rollout.go Outdated
log.Info("Cordoning node", "node", node.Name)
modifiedNode := node.DeepCopy()
modifiedNode.Spec.Unschedulable = true
metav1.SetMetaDataAnnotation(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is there an annotation to understand if the nodes have been provisioned by karpeneter and eventually adding this annotation conditionally? Otherwise, it is always there. Not that it does any harm but just wondering if we could keep the node clean if there is no karpenter at all involved

@cheesesashimi cheesesashimi Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

There are two ways to do this conditionally: Using the Kubernetes Discovery API and looking for CustomResourceDefinitions (CRD). I've outlined their benefits and drawbacks here:

Discovery API

An issue we could run into with the Discovery API is that it does not have informers, watchers, etc., which means that this API is not event-driven. To avoid unnecessary load on the API server, client-go provides a caching Discovery API client (link). However, one must still define the cache invalidation policy, meaning that we could end up with a race where Karpenter could reap a node unexpectedly. Here's how that could happen:

  1. A cluster admin starts a bootc-operator update.
  2. While the bootc-operator update is in progress, Karpenter is installed.
  3. The Discovery API cache has not been invalidated and bootc-operator selects the next node for an update without applying the do-not-repair annotation.
  4. The node update process takes longer than expected and Karpenter reaps the node because the do-not-repair annotation was not present.

While the concerns around this are likely overblown, it nevertheless is an edge-case that we should be able to gracefully handle.

Watching for CustomResourceDefinitions

We could add a watcher for CustomResourceDefinitions (CRD) which would allow us to be notified immediately whenever a given CRD is installed or deleted. In this case, we can check for the presence of Karpenter CRDs and adjust the rollout accordingly. This approach would not completely eliminate the possibility of a race, but it would reduce it substantially. It would require some additional permissions for the controller, but it is doable.

Both of those solutions increase complexity and in this particular situation, I'm not sure that it is necessary since adding / removing this annotation unconditionally will do nothing if Karpenter is not installed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

is there an annotation to understand if the nodes have been provisioned by karpeneter and eventually adding this annotation conditionally?

I revisited this question today and determined that the best way to determine whether the nodes' lifecycle is managed by Karpenter is to check if an OwnerReference exists which points to a Karpenter NodeClaim object which would be present in that case. So we could conditionally set the annotation in that case.

Comment on lines +890 to +892
if metav1.HasAnnotation(node.ObjectMeta, karpenterDoNotRepairAnnotationKey) {
delete(modified.Annotations, karpenterDoNotRepairAnnotationKey)
}

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.

curious, why do we need to delete?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The only time we want Karpenter to ignore a node is when we're performing an update. At any other time, if the node enters a failure mode that Karpenter can remediate (e.g., by deleting / replacing it), we want Karpenter to be able to handle it.

@alicefr

alicefr commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

This requires a rebase to include the testing for k8s 1.37

@cheesesashimi
cheesesashimi force-pushed the zzlotnik/karpenter-annotations branch from 902f247 to 84b20c9 Compare October 8, 2026 16:57
Comment thread docs/src/operations/karpenter.md Outdated
bootc-operator can run alongside [Karpenter]. During a node update, the
operator drains and reboots one or more nodes. To prevent Karpenter from
repairing or replacing a node while that operation is in progress, the
operator uses the `karpenter.sh/do-not-repair` 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.

What about do-not-disrupt feels like this should also be applied during update?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The do-not-disrupt annotation is used for identifying workloads that Karpenter should not disrupt whereas the do-not-repair annotation is used to identify nodes that Karpenter should ignore once that node meets Karpenters criteria for repair / replacement.

@cverna cverna Oct 8, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe I don't understand this correctly, I haven't launched a cluster to try. But, I think if a node as the do-not-repair annotation then it is not protected from consolidation and drift event from Karpenter?

So with my limited understanding, if the bootc-operator drains a node with the annotation do-not-repair. Karpenter would see that node being empty (ie no pods running) and mark it for Consolidation (deletion). I guess there might be some race involved here but that could happen.

@cheesesashimi cheesesashimi Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I was incorrect about the do-not-disrupt annotation. After doing a bit more research, it looks like we'll need both annotations for this to work correctly. I'll update this PR to include both of them. Thanks for challenging my assumptions here!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No worries, trying to wrap my head around this, so thanks for being my sounding board 😊

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

After doing a bit more research, even with both of those annotations set, it is still possible that a node could be reaped while a bootc update is in progress. However, in that case, it is due to expiration and forceful termination policies and not because of the bootc update. I'll be sure to put this in the documentation that I'm including with this PR.

Comment thread docs/src/operations/karpenter.md Outdated
complexity than benefit, and clusters without Karpenter simply do not consume
these annotations.

## Annotation lifecycle

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

How do both operator works together, IIUC Karpenter will replace the whole fleet when one updates the AMI refs.

When using the bootc-operator should an Admin freeze the AMI refs in the Karpenter objects?

Then when Karpenter scale-out new nodes it will start from an old AMI which would trigger the bootc-operator to update and reboot? This is not great and we have just recreated our problem with old boot-images :-(

I am not sure if we have a good solution for this 🤔

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Karpenter will replace the whole fleet when one updates the AMI refs

I think it depends on how many NodeClasses and NodePools one has. If all of the cluster nodes are in a single NodeClass and its AMI is updated, then yes, Karpetner would replace the whole fleet.

When using the bootc-operator should an Admin freeze the AMI refs in the Karpenter objects?

As I understand it, these are frozen by default in Karpenter, although one can use a more dynamic AMI selection if they wanted to.

Then when Karpenter scale-out new nodes it will start from an old AMI which would trigger the bootc-operator to update and reboot? This is not great and we have just recreated our problem with old boot-images :-(

It is the boot-image problem again. But I think that the solution here could be easier, provided that one has multiple NodeClasses and BootcNodePools since one could then use a blue/green rollout pattern. We'd need to come up with some guidance around that.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes I think the emphasis/technology of Karpenter clashes with bootc-operator.

I would say there's something a bit more general going on here in that actually most uses of Kubernetes can be ~stateless by default (which is not what bootc defaults to, but can be opted-into), where you don't even support in-place updates in general.

But that approach gets hard in some cases where one has good reasons for machines with a more persistent identity and state (sometimes e.g. databases and the like), and that's where a more bootc-like approach can shine.

It is the boot-image problem again.

Hmm but that's only in the case where there is "AMI vs container" skew - in most cases one can update to the latest AMI/disk-image by default for new nodes right?

But I think that the solution here could be easier, provided that one has multiple NodeClasses and BootcNodePools since one could then use a blue/green rollout pattern. We'd need to come up with some guidance around that.

I am not fully following this, would this be "blue" is in-place updates and "green" is newly provisioned? But yes blue/green for pure bootc (or Karpenter) make sense.

Offhand, it feels to me like use of bootc-operator with Karpenter would be a thing where one would want to opt-in for specific systems?

@cheesesashimi cheesesashimi Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes I think the emphasis/technology of Karpenter clashes with bootc-operator.

I agree. Although the two technologies clash, we should ensure that they clash in a known and graceful manner, which is what this PR is for.

Hmm but that's only in the case where there is "AMI vs container" skew - in most cases one can update to the latest AMI/disk-image by default for new nodes right?

Yes they can.

I am not fully following this, would this be "blue" is in-place updates and "green" is newly provisioned? But yes blue/green for pure bootc (or Karpenter) make sense.

Yes. In the example I'm thinking about, blue are nodes that were provisioned from an older AMI and have been in-place updated to the point where the AMI / container image skew is too big. And green are nodes that were provisioned from the latest AMI. And over time, the blue nodes are replaced with green nodes. To my understanding, this is possible today with Karpenter. If someone is using bootc-operator + Karpenter, then they'd have to include BootcNodePools as well.

Offhand, it feels to me like use of bootc-operator with Karpenter would be a thing where one would want to opt-in for specific systems?

I agree. Though I'd also argue that by installing both operators at the same time that one is opting into this. If that was done right now, they must be careful to configure both operators so they won't act on the same nodes. But what happens if they either don't do that or they add configs (intentionally or otherwise) which allow both operators to act on the same nodes?

In the best-case scenario, bootc-operator might complete the update before Karpenter can reap the node. In the worst-case scenario, it could lead to infinite node churn, resembling an infinite loop:

  1. bootc-operator cordons and drains the node to perform the in-place update.
  2. Karpenter thinks the node is "unhealthy" or "empty", so it removes the node and provisions a replacement.
  3. bootc-operator eventually selects the replacement node for an update because the bootc image does not match the desired state.
  4. GOTO 1.

In this situation, we should prevent the node churn from happening by teaching bootc-operator to tell Karpenter not to attempt to reap the nodes it is currently updating for reasons relating to the node being empty or unhealthy. And that's what this PR does.

@cheesesashimi
cheesesashimi force-pushed the zzlotnik/karpenter-annotations branch from 84b20c9 to 75e251b Compare October 9, 2026 18:14
If a cluster contains both bootc-operator and Karpenter, it is possible
that Karpenter could reap cluster nodes while a bootc-operator driven
update is in progress. To prevent that, we detect whether the Node
object is owned by a Karpenter NodeClaim object. And in that situation,
we apply both the Karpenter Do Not Repair and Do Not Disrupt annotations
to each node when it is cordoned for an update and we remove it after
the update is completed.

Signed-off-by: Zack Zlotnik <zzlotnik@redhat.com>

Assisted-by: AI
@cheesesashimi
cheesesashimi force-pushed the zzlotnik/karpenter-annotations branch from 75e251b to 30e06a1 Compare October 9, 2026 19:06

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants