Skip to content

Commit 6d80d2e

Browse files
committed
taint propagation: implement propagation in machine controller
1 parent ecfb5ce commit 6d80d2e

File tree

1 file changed

+109
-2
lines changed

1 file changed

+109
-2
lines changed

internal/controllers/machine/machine_controller_noderef.go

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@ import (
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2828
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2929
"k8s.io/apimachinery/pkg/runtime/schema"
30+
"k8s.io/apimachinery/pkg/util/sets"
3031
"k8s.io/klog/v2"
32+
"k8s.io/utils/ptr"
3133
ctrl "sigs.k8s.io/controller-runtime"
3234
"sigs.k8s.io/controller-runtime/pkg/client"
3335

3436
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
3537
"sigs.k8s.io/cluster-api/api/core/v1beta2/index"
38+
"sigs.k8s.io/cluster-api/feature"
3639
"sigs.k8s.io/cluster-api/internal/controllers/machinedeployment/mdutil"
3740
"sigs.k8s.io/cluster-api/internal/util/taints"
3841
"sigs.k8s.io/cluster-api/util"
@@ -334,6 +337,15 @@ func (r *Reconciler) patchNode(ctx context.Context, remoteClient client.Client,
334337
// Drop the NodeUninitializedTaint taint on the node given that we are reconciling labels.
335338
hasTaintChanges := taints.RemoveNodeTaint(newNode, clusterv1.NodeUninitializedTaint)
336339

340+
// Propagate taints set on the Machine to the Node.
341+
var propagateTaintsChanges bool
342+
if feature.Gates.Enabled(feature.MachineTaintPropagation) {
343+
var err error
344+
if propagateTaintsChanges, err = propagateMachineTaintsToNode(newNode, m.Spec.Taints); err != nil {
345+
return errors.Wrapf(err, "failed to propagate machine taints to node %s", klog.KRef("", node.Name))
346+
}
347+
}
348+
337349
// Set Taint to a node in an old MachineSet and unset Taint from a node in a new MachineSet
338350
isOutdated, err := shouldNodeHaveOutdatedTaint(ms, md)
339351
if err != nil {
@@ -345,11 +357,106 @@ func (r *Reconciler) patchNode(ctx context.Context, remoteClient client.Client,
345357
hasTaintChanges = taints.RemoveNodeTaint(newNode, clusterv1.NodeOutdatedRevisionTaint) || hasTaintChanges
346358
}
347359

348-
if !hasAnnotationChanges && !hasLabelChanges && !hasTaintChanges {
360+
if !hasAnnotationChanges && !hasLabelChanges && !hasTaintChanges && !propagateTaintsChanges {
349361
return nil
350362
}
351363

352-
return remoteClient.Patch(ctx, newNode, client.StrategicMergeFrom(node))
364+
var mergeOptions []client.MergeFromOption
365+
if feature.Gates.Enabled(feature.MachineTaintPropagation) {
366+
mergeOptions = append(mergeOptions, client.MergeFromWithOptimisticLock{})
367+
}
368+
369+
return remoteClient.Patch(ctx, newNode, client.StrategicMergeFrom(node, mergeOptions...))
370+
}
371+
372+
// propagateMachineTaintsToNode handles propagation of taints defined on a machine to a node.
373+
// It makes use of the annotation clusterv1.TaintsFromMachineAnnotation to track which taints are owned by the controller.
374+
// OnInitialization taints are only added to the node if the tracking annotation has not been set yet.
375+
func propagateMachineTaintsToNode(node *corev1.Node, machineTaints []clusterv1.MachineTaint) (bool, error) {
376+
changed := false
377+
378+
// Get the value of the tracking annotation. If it is not set at all we also have to add the OnInitialization taints.
379+
oldTaintsAnnotation, nodeTaintsInitialized := node.Annotations[clusterv1.TaintsFromMachineAnnotation]
380+
381+
// ownedTaints contains all Always taints that the controller is owning.
382+
ownedTaints := unmarshalMachineTaintsAnnotation(oldTaintsAnnotation)
383+
384+
// newOwnedTaints will contain all Always taints from the current machine's spec.
385+
newOwnedTaints := sets.New[string]()
386+
onInitializationTaints := sets.New[string]()
387+
388+
for _, taint := range machineTaints {
389+
// Collect Always and OnInitialization taints to identify taints to delete.
390+
// Separating Always taints so the tracking annotation can be updated accordingly.
391+
switch taint.Propagation {
392+
case clusterv1.TaintPropagationAlways:
393+
newOwnedTaints.Insert(fmt.Sprintf("%s:%s", taint.Key, taint.Effect))
394+
case clusterv1.TaintPropagationOnInitialization:
395+
onInitializationTaints.Insert(fmt.Sprintf("%s:%s", taint.Key, taint.Effect))
396+
}
397+
398+
// Only add OnInitialization taints if the tracking annotation has not been set yet.
399+
if taint.Propagation == clusterv1.TaintPropagationOnInitialization && nodeTaintsInitialized {
400+
continue
401+
}
402+
403+
// Ensure the taint is set on the node and has the correct value.
404+
if changedTaints := taints.EnsureNodeTaint(node, convertMachineTaintToCoreV1Taint(taint)); changedTaints {
405+
changed = true
406+
}
407+
}
408+
409+
// Calculate ownedTaints - newOwnedTaints to identify old taints which need to be deleted from the node.
410+
taintsToDelete := ownedTaints.Difference(newOwnedTaints).Difference(onInitializationTaints)
411+
412+
// Remove all identified taints from the node.
413+
for taintToDelete := range taintsToDelete {
414+
splitted := strings.Split(taintToDelete, ":")
415+
if len(splitted) != 2 {
416+
return changed, fmt.Errorf("invalid taint format: %q", taintToDelete)
417+
}
418+
419+
if removedTaint := taints.RemoveNodeTaint(node, corev1.Taint{Key: splitted[0], Effect: corev1.TaintEffect(splitted[1])}); removedTaint {
420+
changed = true
421+
}
422+
}
423+
424+
// Update the tracking annotation with newOwnedTaints..
425+
if newTaintsAnnotation := marshalMachineTaintsAnnotation(newOwnedTaints); !nodeTaintsInitialized || newTaintsAnnotation != oldTaintsAnnotation {
426+
if node.Annotations == nil {
427+
node.Annotations = map[string]string{}
428+
}
429+
node.Annotations[clusterv1.TaintsFromMachineAnnotation] = newTaintsAnnotation
430+
changed = true
431+
}
432+
433+
return changed, nil
434+
}
435+
436+
// marshalMachineTaintsAnnotation marshals the tracking annotation value.
437+
func marshalMachineTaintsAnnotation(ownedTaints sets.Set[string]) string {
438+
taints := ownedTaints.UnsortedList()
439+
slices.Sort(taints)
440+
441+
return strings.Join(taints, ",")
442+
}
443+
444+
// unmarshalMachineTaintsAnnotation unmarshals the tracking annotation value.
445+
func unmarshalMachineTaintsAnnotation(annotationValue string) sets.Set[string] {
446+
if annotationValue == "" {
447+
return nil
448+
}
449+
450+
return sets.New(strings.Split(annotationValue, ",")...)
451+
}
452+
453+
// convertMachineTaintToCoreV1Taint converts a MachineTaint to a corev1.Taint.
454+
func convertMachineTaintToCoreV1Taint(machineTaint clusterv1.MachineTaint) corev1.Taint {
455+
return corev1.Taint{
456+
Key: machineTaint.Key,
457+
Value: ptr.Deref(machineTaint.Value, ""),
458+
Effect: machineTaint.Effect,
459+
}
353460
}
354461

355462
// shouldNodeHaveOutdatedTaint tries to compare the revision of the owning MachineSet to the MachineDeployment.

0 commit comments

Comments
 (0)