Skip to content

Commit 902f247

Browse files
committed
makes bootc-operator aware of karpenter
If a cluster contains both bootc-operator and Karpenter, it is possible that Karpenter could reap cluster nodes while in the middle of a bootc-operator driven update. To prevent that, we apply the Karpenter Do Not Repair annotation 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>
1 parent 72f93f4 commit 902f247

3 files changed

Lines changed: 46 additions & 0 deletions

File tree

‎internal/controller/bootcnodepool_controller.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,11 @@ func (r *BootcNodePoolReconciler) restoreCordonState(
886886
}
887887
modified := node.DeepCopy()
888888
modified.Spec.Unschedulable = false
889+
890+
if metav1.HasAnnotation(node.ObjectMeta, karpenterDoNotRepairAnnotationKey) {
891+
delete(modified.Annotations, karpenterDoNotRepairAnnotationKey)
892+
}
893+
889894
if err := r.Patch(ctx, modified, client.StrategicMergeFrom(node)); err != nil {
890895
return fmt.Errorf("uncordoning node: %w", err)
891896
}

‎internal/controller/rollout.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ import (
3434
// https://github.com/bootc-dev/bootc-operator/issues/99.
3535
const unhealthySlotHaltThreshold = 2
3636

37+
// Ensures that clusters using Karpenter will not have their nodes reaped when
38+
// a bootc-operator driven update / reboot is in progress.
39+
//
40+
// See:
41+
// - https://redhat.atlassian.net/browse/BIFROST-1447
42+
// - https://redhat.atlassian.net/browse/MCO-2599
43+
// - https://github.com/kubernetes-sigs/karpenter/pull/3311
44+
const karpenterDoNotRepairAnnotationKey = "karpenter.sh/do-not-repair"
45+
3746
// rolloutState holds the classified BootcNodes for a single reconcile
3847
// pass.
3948
type rolloutState struct {
@@ -225,6 +234,11 @@ func (r *BootcNodePoolReconciler) assignRebootSlot(
225234
log.Info("Cordoning node", "node", node.Name)
226235
modifiedNode := node.DeepCopy()
227236
modifiedNode.Spec.Unschedulable = true
237+
metav1.SetMetaDataAnnotation(
238+
&modifiedNode.ObjectMeta,
239+
karpenterDoNotRepairAnnotationKey,
240+
"true",
241+
)
228242
if err := r.Patch(ctx, modifiedNode, client.StrategicMergeFrom(node)); err != nil {
229243
return fmt.Errorf("cordoning node: %w", err)
230244
}

‎internal/controller/rollout_envtest_test.go‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,21 @@ func TestSimpleRollout(t *testing.T) {
100100
return node.Spec.Unschedulable, err
101101
}).Should(BeTrue(), "node %s should be cordoned", name)
102102

103+
g.Eventually(func() (bool, error) {
104+
var node corev1.Node
105+
err := k8sClient.Get(ctx, client.ObjectKey{Name: name}, &node)
106+
return metav1.HasAnnotation(node.ObjectMeta, karpenterDoNotRepairAnnotationKey), err
107+
}).Should(BeTrue(), "node %s should get karpenter do-not-repair annotation", name)
108+
103109
// Verify remaining nodes are not yet touched.
104110
for _, other := range nodeNames[i+1:] {
105111
var node corev1.Node
106112
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: other}, &node)).To(Succeed())
107113
g.Expect(node.Spec.Unschedulable).To(BeFalse(),
108114
"node %s should not be cordoned", other)
115+
g.Expect(metav1.HasAnnotation(node.ObjectMeta, karpenterDoNotRepairAnnotationKey)).
116+
To(BeFalse(),
117+
"node %s should not have the karpenter do-not-repair annotation", other)
109118

110119
var bn bootcv1alpha1.BootcNode
111120
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: other}, &bn)).To(Succeed())
@@ -134,6 +143,12 @@ func TestSimpleRollout(t *testing.T) {
134143
err := k8sClient.Get(ctx, client.ObjectKey{Name: name}, &node)
135144
return node.Spec.Unschedulable, err
136145
}).Should(BeFalse(), "node %s should be uncordoned after reboot", name)
146+
147+
g.Eventually(func() (bool, error) {
148+
var node corev1.Node
149+
err := k8sClient.Get(ctx, client.ObjectKey{Name: name}, &node)
150+
return metav1.HasAnnotation(node.ObjectMeta, karpenterDoNotRepairAnnotationKey), err
151+
}).Should(BeFalse(), "node %s should not have karpenter do-not-repair annotation after reboot", name)
137152
}
138153
}
139154

@@ -661,6 +676,12 @@ func TestNodeLeavesPoolCancelsDrain(t *testing.T) {
661676
return n.Spec.Unschedulable, err
662677
}).Should(BeTrue())
663678

679+
g.Eventually(func() (bool, error) {
680+
var n corev1.Node
681+
err := k8sClient.Get(ctx, client.ObjectKey{Name: nodeName}, &n)
682+
return metav1.HasAnnotation(n.ObjectMeta, karpenterDoNotRepairAnnotationKey), err
683+
}).Should(BeTrue())
684+
664685
// Verify desiredImageState is still Staged (drain hasn't completed).
665686
var bn bootcv1alpha1.BootcNode
666687
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: nodeName}, &bn)).To(Succeed())
@@ -686,6 +707,12 @@ func TestNodeLeavesPoolCancelsDrain(t *testing.T) {
686707
return n.Spec.Unschedulable, err
687708
}).Should(BeFalse())
688709

710+
g.Eventually(func() (bool, error) {
711+
var n corev1.Node
712+
err := k8sClient.Get(ctx, client.ObjectKey{Name: nodeName}, &n)
713+
return metav1.HasAnnotation(n.ObjectMeta, karpenterDoNotRepairAnnotationKey), err
714+
}).Should(BeFalse())
715+
689716
g.Eventually(func() (map[string]string, error) {
690717
var n corev1.Node
691718
err := k8sClient.Get(ctx, client.ObjectKey{Name: nodeName}, &n)

0 commit comments

Comments
 (0)