Skip to content

Commit 3813555

Browse files
authored
Merge pull request #8057 from ykakarap/node-label_kcp-machine-in-place
⚠️ in-place propagation support for KCP
2 parents 9fe7ff5 + a535e78 commit 3813555

File tree

14 files changed

+1145
-376
lines changed

14 files changed

+1145
-376
lines changed

controlplane/kubeadm/internal/control_plane.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ type ControlPlane struct {
4949

5050
// TODO: we should see if we can combine these with the Machine objects so we don't have all these separate lookups
5151
// See discussion on https://github.com/kubernetes-sigs/cluster-api/pull/3405
52-
kubeadmConfigs map[string]*bootstrapv1.KubeadmConfig
53-
infraResources map[string]*unstructured.Unstructured
52+
KubeadmConfigs map[string]*bootstrapv1.KubeadmConfig
53+
InfraResources map[string]*unstructured.Unstructured
5454
}
5555

5656
// NewControlPlane returns an instantiated ControlPlane.
@@ -77,8 +77,8 @@ func NewControlPlane(ctx context.Context, client client.Client, cluster *cluster
7777
Cluster: cluster,
7878
Machines: ownedMachines,
7979
machinesPatchHelpers: patchHelpers,
80-
kubeadmConfigs: kubeadmConfigs,
81-
infraResources: infraObjects,
80+
KubeadmConfigs: kubeadmConfigs,
81+
InfraResources: infraObjects,
8282
reconciliationTime: metav1.Now(),
8383
}, nil
8484
}
@@ -158,7 +158,7 @@ func (c *ControlPlane) HasDeletingMachine() bool {
158158

159159
// GetKubeadmConfig returns the KubeadmConfig of a given machine.
160160
func (c *ControlPlane) GetKubeadmConfig(machineName string) (*bootstrapv1.KubeadmConfig, bool) {
161-
kubeadmConfig, ok := c.kubeadmConfigs[machineName]
161+
kubeadmConfig, ok := c.KubeadmConfigs[machineName]
162162
return kubeadmConfig, ok
163163
}
164164

@@ -169,15 +169,15 @@ func (c *ControlPlane) MachinesNeedingRollout() collections.Machines {
169169

170170
// Return machines if they are scheduled for rollout or if with an outdated configuration.
171171
return machines.Filter(
172-
NeedsRollout(&c.reconciliationTime, c.KCP.Spec.RolloutAfter, c.KCP.Spec.RolloutBefore, c.infraResources, c.kubeadmConfigs, c.KCP),
172+
NeedsRollout(&c.reconciliationTime, c.KCP.Spec.RolloutAfter, c.KCP.Spec.RolloutBefore, c.InfraResources, c.KubeadmConfigs, c.KCP),
173173
)
174174
}
175175

176176
// UpToDateMachines returns the machines that are up to date with the control
177177
// plane's configuration and therefore do not require rollout.
178178
func (c *ControlPlane) UpToDateMachines() collections.Machines {
179179
return c.Machines.Filter(
180-
collections.Not(NeedsRollout(&c.reconciliationTime, c.KCP.Spec.RolloutAfter, c.KCP.Spec.RolloutBefore, c.infraResources, c.kubeadmConfigs, c.KCP)),
180+
collections.Not(NeedsRollout(&c.reconciliationTime, c.KCP.Spec.RolloutAfter, c.KCP.Spec.RolloutBefore, c.InfraResources, c.KubeadmConfigs, c.KCP)),
181181
)
182182
}
183183

@@ -258,3 +258,8 @@ func (c *ControlPlane) PatchMachines(ctx context.Context) error {
258258
}
259259
return kerrors.NewAggregate(errList)
260260
}
261+
262+
// SetPatchHelpers updates the patch helpers.
263+
func (c *ControlPlane) SetPatchHelpers(patchHelpers map[string]*patch.Helper) {
264+
c.machinesPatchHelpers = patchHelpers
265+
}

controlplane/kubeadm/internal/controllers/controller.go

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ import (
4444
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
4545
expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
4646
"sigs.k8s.io/cluster-api/feature"
47-
"sigs.k8s.io/cluster-api/internal/labels"
47+
"sigs.k8s.io/cluster-api/internal/contract"
48+
"sigs.k8s.io/cluster-api/internal/util/ssa"
4849
"sigs.k8s.io/cluster-api/util"
4950
"sigs.k8s.io/cluster-api/util/annotations"
5051
"sigs.k8s.io/cluster-api/util/collections"
@@ -55,6 +56,8 @@ import (
5556
"sigs.k8s.io/cluster-api/util/version"
5657
)
5758

59+
const kcpManagerName = "capi-kubeadmcontrolplane"
60+
5861
// +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;patch
5962
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch
6063
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io;bootstrap.cluster.x-k8s.io;controlplane.cluster.x-k8s.io,resources=*,verbs=get;list;watch;create;update;patch;delete
@@ -77,6 +80,12 @@ type KubeadmControlPlaneReconciler struct {
7780

7881
managementCluster internal.ManagementCluster
7982
managementClusterUncached internal.ManagementCluster
83+
84+
// disableInPlacePropagation should only be used for tests. This is used to skip
85+
// some parts of the controller that need SSA as the current test setup does not
86+
// support SSA. This flag should be dropped after all affected tests are migrated
87+
// to envtest.
88+
disableInPlacePropagation bool
8089
}
8190

8291
func (r *KubeadmControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
@@ -331,25 +340,16 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, cluster *
331340
return ctrl.Result{}, err
332341
}
333342

343+
if !r.disableInPlacePropagation {
344+
if err := r.syncMachines(ctx, controlPlane); err != nil {
345+
return ctrl.Result{}, errors.Wrap(err, "failed to sync Machines")
346+
}
347+
}
348+
334349
// Aggregate the operational state of all the machines; while aggregating we are adding the
335350
// source ref (reason@machine/name) so the problem can be easily tracked down to its source machine.
336351
conditions.SetAggregate(controlPlane.KCP, controlplanev1.MachinesReadyCondition, ownedMachines.ConditionGetters(), conditions.AddSourceRef(), conditions.WithStepCounterIf(false))
337352

338-
// Ensure all required labels exist on the controlled Machines.
339-
// This logic is needed to add the `cluster.x-k8s.io/control-plane-name` label to Machines
340-
// which were created before the `cluster.x-k8s.io/control-plane-name` label was introduced
341-
// or if a user manually removed the label.
342-
// NOTE: Changes will be applied to the Machines in reconcileControlPlaneConditions.
343-
// NOTE: cluster.x-k8s.io/control-plane is already set at this stage (it is used when reading controlPlane.Machines).
344-
for i := range controlPlane.Machines {
345-
machine := controlPlane.Machines[i]
346-
// Note: MustEqualValue and MustFormatValue is used here as the label value can be a hash if the control plane
347-
// name is longer than 63 characters.
348-
if value, ok := machine.Labels[clusterv1.MachineControlPlaneNameLabel]; !ok || !labels.MustEqualValue(kcp.Name, value) {
349-
machine.Labels[clusterv1.MachineControlPlaneNameLabel] = labels.MustFormatValue(kcp.Name)
350-
}
351-
}
352-
353353
// Updates conditions reporting the status of static pods and the status of the etcd cluster.
354354
// NOTE: Conditions reporting KCP operation progress like e.g. Resized or SpecUpToDate are inlined with the rest of the execution.
355355
if result, err := r.reconcileControlPlaneConditions(ctx, controlPlane); err != nil || !result.IsZero() {
@@ -535,6 +535,86 @@ func (r *KubeadmControlPlaneReconciler) ClusterToKubeadmControlPlane(o client.Ob
535535
return nil
536536
}
537537

538+
// syncMachines updates Machines, InfrastructureMachines and KubeadmConfigs to propagate in-place mutable fields from KCP.
539+
// Note: It also cleans up managed fields of all Machines so that Machines that were
540+
// created/patched before (< v1.4.0) the controller adopted Server-Side-Apply (SSA) can also work with SSA.
541+
// Note: For InfrastructureMachines and KubeadmConfigs it also drops ownership of "metadata.labels" and
542+
// "metadata.annotations" from "manager" so that "capi-kubeadmcontrolplane" can own these fields and can work with SSA.
543+
// Otherwise, fields would be co-owned by our "old" "manager" and "capi-kubeadmcontrolplane" and then we would not be
544+
// able to e.g. drop labels and annotations.
545+
func (r *KubeadmControlPlaneReconciler) syncMachines(ctx context.Context, controlPlane *internal.ControlPlane) error {
546+
patchHelpers := map[string]*patch.Helper{}
547+
for machineName := range controlPlane.Machines {
548+
m := controlPlane.Machines[machineName]
549+
// If the machine is already being deleted, we don't need to update it.
550+
if !m.DeletionTimestamp.IsZero() {
551+
continue
552+
}
553+
554+
// Cleanup managed fields of all Machines.
555+
// We do this so that Machines that were created/patched before the controller adopted Server-Side-Apply (SSA)
556+
// (< v1.4.0) can also work with SSA. Otherwise, fields would be co-owned by our "old" "manager" and
557+
// "capi-kubeadmcontrolplane" and then we would not be able to e.g. drop labels and annotations.
558+
if err := ssa.CleanUpManagedFieldsForSSAAdoption(ctx, r.Client, m, kcpManagerName); err != nil {
559+
return errors.Wrapf(err, "failed to update Machine: failed to adjust the managedFields of the Machine %s", klog.KObj(m))
560+
}
561+
// Update Machine to propagate in-place mutable fields from KCP.
562+
updatedMachine, err := r.updateMachine(ctx, m, controlPlane.KCP, controlPlane.Cluster)
563+
if err != nil {
564+
return errors.Wrapf(err, "failed to update Machine: %s", klog.KObj(m))
565+
}
566+
controlPlane.Machines[machineName] = updatedMachine
567+
// Since the machine is updated, re-create the patch helper so that any subsequent
568+
// Patch calls use the correct base machine object to calculate the diffs.
569+
// Example: reconcileControlPlaneConditions patches the machine objects in a subsequent call
570+
// and, it should use the updated machine to calculate the diff.
571+
// Note: If the patchHelpers are not re-computed based on the new updated machines, subsequent
572+
// Patch calls will fail because the patch will be calculated based on an outdated machine and will error
573+
// because of outdated resourceVersion.
574+
// TODO: This should be cleaned-up to have a more streamline way of constructing and using patchHelpers.
575+
patchHelper, err := patch.NewHelper(updatedMachine, r.Client)
576+
if err != nil {
577+
return errors.Wrapf(err, "failed to create patch helper for Machine %s", klog.KObj(updatedMachine))
578+
}
579+
patchHelpers[machineName] = patchHelper
580+
581+
labelsAndAnnotationsManagedFieldPaths := []contract.Path{
582+
{"f:metadata", "f:annotations"},
583+
{"f:metadata", "f:labels"},
584+
}
585+
infraMachine := controlPlane.InfraResources[machineName]
586+
// Cleanup managed fields of all InfrastructureMachines to drop ownership of labels and annotations
587+
// from "manager". We do this so that InfrastructureMachines that are created using the Create method
588+
// can also work with SSA. Otherwise, labels and annotations would be co-owned by our "old" "manager"
589+
// and "capi-kubeadmcontrolplane" and then we would not be able to e.g. drop labels and annotations.
590+
if err := ssa.DropManagedFields(ctx, r.Client, infraMachine, kcpManagerName, labelsAndAnnotationsManagedFieldPaths); err != nil {
591+
return errors.Wrapf(err, "failed to clean up managedFields of InfrastructureMachine %s", klog.KObj(infraMachine))
592+
}
593+
// Update in-place mutating fields on InfrastructureMachine.
594+
if err := r.updateExternalObject(ctx, infraMachine, controlPlane.KCP, controlPlane.Cluster); err != nil {
595+
return errors.Wrapf(err, "failed to update InfrastructureMachine %s", klog.KObj(infraMachine))
596+
}
597+
598+
kubeadmConfig := controlPlane.KubeadmConfigs[machineName]
599+
// Note: Set the GroupVersionKind because updateExternalObject depends on it.
600+
kubeadmConfig.SetGroupVersionKind(m.Spec.Bootstrap.ConfigRef.GroupVersionKind())
601+
// Cleanup managed fields of all KubeadmConfigs to drop ownership of labels and annotations
602+
// from "manager". We do this so that KubeadmConfigs that are created using the Create method
603+
// can also work with SSA. Otherwise, labels and annotations would be co-owned by our "old" "manager"
604+
// and "capi-kubeadmcontrolplane" and then we would not be able to e.g. drop labels and annotations.
605+
if err := ssa.DropManagedFields(ctx, r.Client, kubeadmConfig, kcpManagerName, labelsAndAnnotationsManagedFieldPaths); err != nil {
606+
return errors.Wrapf(err, "failed to clean up managedFields of KubeadmConfig %s", klog.KObj(kubeadmConfig))
607+
}
608+
// Update in-place mutating fields on BootstrapConfig.
609+
if err := r.updateExternalObject(ctx, kubeadmConfig, controlPlane.KCP, controlPlane.Cluster); err != nil {
610+
return errors.Wrapf(err, "failed to update KubeadmConfig %s", klog.KObj(kubeadmConfig))
611+
}
612+
}
613+
// Update the patch helpers.
614+
controlPlane.SetPatchHelpers(patchHelpers)
615+
return nil
616+
}
617+
538618
// reconcileControlPlaneConditions is responsible of reconciling conditions reporting the status of static pods and
539619
// the status of the etcd cluster.
540620
func (r *KubeadmControlPlaneReconciler) reconcileControlPlaneConditions(ctx context.Context, controlPlane *internal.ControlPlane) (ctrl.Result, error) {

0 commit comments

Comments
 (0)