sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/controllers/helpers.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controllers
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"strings"
    23  
    24  	"github.com/pkg/errors"
    25  	corev1 "k8s.io/api/core/v1"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	kerrors "k8s.io/apimachinery/pkg/util/errors"
    31  	"k8s.io/apiserver/pkg/storage/names"
    32  	ctrl "sigs.k8s.io/controller-runtime"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  
    35  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    36  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    37  	"sigs.k8s.io/cluster-api/controllers/external"
    38  	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
    39  	"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
    40  	"sigs.k8s.io/cluster-api/internal/util/ssa"
    41  	"sigs.k8s.io/cluster-api/util"
    42  	"sigs.k8s.io/cluster-api/util/certs"
    43  	"sigs.k8s.io/cluster-api/util/conditions"
    44  	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
    45  	"sigs.k8s.io/cluster-api/util/kubeconfig"
    46  	"sigs.k8s.io/cluster-api/util/patch"
    47  	"sigs.k8s.io/cluster-api/util/secret"
    48  )
    49  
    50  func (r *KubeadmControlPlaneReconciler) reconcileKubeconfig(ctx context.Context, controlPlane *internal.ControlPlane) (ctrl.Result, error) {
    51  	log := ctrl.LoggerFrom(ctx)
    52  
    53  	endpoint := controlPlane.Cluster.Spec.ControlPlaneEndpoint
    54  	if endpoint.IsZero() {
    55  		return ctrl.Result{}, nil
    56  	}
    57  
    58  	controllerOwnerRef := *metav1.NewControllerRef(controlPlane.KCP, controlplanev1.GroupVersion.WithKind(kubeadmControlPlaneKind))
    59  	clusterName := util.ObjectKey(controlPlane.Cluster)
    60  	configSecret, err := secret.GetFromNamespacedName(ctx, r.SecretCachingClient, clusterName, secret.Kubeconfig)
    61  	switch {
    62  	case apierrors.IsNotFound(err):
    63  		createErr := kubeconfig.CreateSecretWithOwner(
    64  			ctx,
    65  			r.SecretCachingClient,
    66  			clusterName,
    67  			endpoint.String(),
    68  			controllerOwnerRef,
    69  		)
    70  		if errors.Is(createErr, kubeconfig.ErrDependentCertificateNotFound) {
    71  			return ctrl.Result{RequeueAfter: dependentCertRequeueAfter}, nil
    72  		}
    73  		// always return if we have just created in order to skip rotation checks
    74  		return ctrl.Result{}, createErr
    75  	case err != nil:
    76  		return ctrl.Result{}, errors.Wrap(err, "failed to retrieve kubeconfig Secret")
    77  	}
    78  
    79  	if err := r.adoptKubeconfigSecret(ctx, configSecret, controlPlane.KCP); err != nil {
    80  		return ctrl.Result{}, err
    81  	}
    82  
    83  	// only do rotation on owned secrets
    84  	if !util.IsControlledBy(configSecret, controlPlane.KCP) {
    85  		return ctrl.Result{}, nil
    86  	}
    87  
    88  	needsRotation, err := kubeconfig.NeedsClientCertRotation(configSecret, certs.ClientCertificateRenewalDuration)
    89  	if err != nil {
    90  		return ctrl.Result{}, err
    91  	}
    92  
    93  	if needsRotation {
    94  		log.Info("rotating kubeconfig secret")
    95  		if err := kubeconfig.RegenerateSecret(ctx, r.Client, configSecret); err != nil {
    96  			return ctrl.Result{}, errors.Wrap(err, "failed to regenerate kubeconfig")
    97  		}
    98  	}
    99  
   100  	return ctrl.Result{}, nil
   101  }
   102  
   103  // Ensure the KubeadmConfigSecret has an owner reference to the control plane if it is not a user-provided secret.
   104  func (r *KubeadmControlPlaneReconciler) adoptKubeconfigSecret(ctx context.Context, configSecret *corev1.Secret, kcp *controlplanev1.KubeadmControlPlane) (reterr error) {
   105  	patchHelper, err := patch.NewHelper(configSecret, r.Client)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	defer func() {
   110  		if err := patchHelper.Patch(ctx, configSecret); err != nil {
   111  			reterr = kerrors.NewAggregate([]error{reterr, err})
   112  		}
   113  	}()
   114  	controller := metav1.GetControllerOf(configSecret)
   115  
   116  	// If the current controller is KCP, ensure the owner reference is up to date and return early.
   117  	// Note: This ensures secrets created prior to v1alpha4 are updated to have the correct owner reference apiVersion.
   118  	if controller != nil && controller.Kind == kubeadmControlPlaneKind {
   119  		configSecret.SetOwnerReferences(util.EnsureOwnerRef(configSecret.GetOwnerReferences(), *metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind(kubeadmControlPlaneKind))))
   120  		return nil
   121  	}
   122  
   123  	// If secret type is a CAPI-created secret ensure the owner reference is to KCP.
   124  	if configSecret.Type == clusterv1.ClusterSecretType {
   125  		// Remove the current controller if one exists and ensure KCP is the controller of the secret.
   126  		if controller != nil {
   127  			configSecret.SetOwnerReferences(util.RemoveOwnerRef(configSecret.GetOwnerReferences(), *controller))
   128  		}
   129  		configSecret.SetOwnerReferences(util.EnsureOwnerRef(configSecret.GetOwnerReferences(), *metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind(kubeadmControlPlaneKind))))
   130  	}
   131  	return nil
   132  }
   133  
   134  func (r *KubeadmControlPlaneReconciler) reconcileExternalReference(ctx context.Context, cluster *clusterv1.Cluster, ref *corev1.ObjectReference) error {
   135  	if !strings.HasSuffix(ref.Kind, clusterv1.TemplateSuffix) {
   136  		return nil
   137  	}
   138  
   139  	if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref); err != nil {
   140  		return err
   141  	}
   142  
   143  	obj, err := external.Get(ctx, r.Client, ref, cluster.Namespace)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	// Note: We intentionally do not handle checking for the paused label on an external template reference
   149  
   150  	patchHelper, err := patch.NewHelper(obj, r.Client)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	obj.SetOwnerReferences(util.EnsureOwnerRef(obj.GetOwnerReferences(), metav1.OwnerReference{
   156  		APIVersion: clusterv1.GroupVersion.String(),
   157  		Kind:       "Cluster",
   158  		Name:       cluster.Name,
   159  		UID:        cluster.UID,
   160  	}))
   161  
   162  	return patchHelper.Patch(ctx, obj)
   163  }
   164  
   165  func (r *KubeadmControlPlaneReconciler) cloneConfigsAndGenerateMachine(ctx context.Context, cluster *clusterv1.Cluster, kcp *controlplanev1.KubeadmControlPlane, bootstrapSpec *bootstrapv1.KubeadmConfigSpec, failureDomain *string) error {
   166  	var errs []error
   167  
   168  	// Compute desired Machine
   169  	machine, err := r.computeDesiredMachine(kcp, cluster, failureDomain, nil)
   170  	if err != nil {
   171  		return errors.Wrap(err, "failed to create Machine: failed to compute desired Machine")
   172  	}
   173  
   174  	// Since the cloned resource should eventually have a controller ref for the Machine, we create an
   175  	// OwnerReference here without the Controller field set
   176  	infraCloneOwner := &metav1.OwnerReference{
   177  		APIVersion: controlplanev1.GroupVersion.String(),
   178  		Kind:       kubeadmControlPlaneKind,
   179  		Name:       kcp.Name,
   180  		UID:        kcp.UID,
   181  	}
   182  
   183  	// Clone the infrastructure template
   184  	infraRef, err := external.CreateFromTemplate(ctx, &external.CreateFromTemplateInput{
   185  		Client:      r.Client,
   186  		TemplateRef: &kcp.Spec.MachineTemplate.InfrastructureRef,
   187  		Namespace:   kcp.Namespace,
   188  		Name:        machine.Name,
   189  		OwnerRef:    infraCloneOwner,
   190  		ClusterName: cluster.Name,
   191  		Labels:      internal.ControlPlaneMachineLabelsForCluster(kcp, cluster.Name),
   192  		Annotations: kcp.Spec.MachineTemplate.ObjectMeta.Annotations,
   193  	})
   194  	if err != nil {
   195  		// Safe to return early here since no resources have been created yet.
   196  		conditions.MarkFalse(kcp, controlplanev1.MachinesCreatedCondition, controlplanev1.InfrastructureTemplateCloningFailedReason,
   197  			clusterv1.ConditionSeverityError, err.Error())
   198  		return errors.Wrap(err, "failed to clone infrastructure template")
   199  	}
   200  	machine.Spec.InfrastructureRef = *infraRef
   201  
   202  	// Clone the bootstrap configuration
   203  	bootstrapRef, err := r.generateKubeadmConfig(ctx, kcp, cluster, bootstrapSpec, machine.Name)
   204  	if err != nil {
   205  		conditions.MarkFalse(kcp, controlplanev1.MachinesCreatedCondition, controlplanev1.BootstrapTemplateCloningFailedReason,
   206  			clusterv1.ConditionSeverityError, err.Error())
   207  		errs = append(errs, errors.Wrap(err, "failed to generate bootstrap config"))
   208  	}
   209  
   210  	// Only proceed to generating the Machine if we haven't encountered an error
   211  	if len(errs) == 0 {
   212  		machine.Spec.Bootstrap.ConfigRef = bootstrapRef
   213  
   214  		if err := r.createMachine(ctx, kcp, machine); err != nil {
   215  			conditions.MarkFalse(kcp, controlplanev1.MachinesCreatedCondition, controlplanev1.MachineGenerationFailedReason,
   216  				clusterv1.ConditionSeverityError, err.Error())
   217  			errs = append(errs, errors.Wrap(err, "failed to create Machine"))
   218  		}
   219  	}
   220  
   221  	// If we encountered any errors, attempt to clean up any dangling resources
   222  	if len(errs) > 0 {
   223  		if err := r.cleanupFromGeneration(ctx, infraRef, bootstrapRef); err != nil {
   224  			errs = append(errs, errors.Wrap(err, "failed to cleanup generated resources"))
   225  		}
   226  
   227  		return kerrors.NewAggregate(errs)
   228  	}
   229  
   230  	return nil
   231  }
   232  
   233  func (r *KubeadmControlPlaneReconciler) cleanupFromGeneration(ctx context.Context, remoteRefs ...*corev1.ObjectReference) error {
   234  	var errs []error
   235  
   236  	for _, ref := range remoteRefs {
   237  		if ref == nil {
   238  			continue
   239  		}
   240  		config := &unstructured.Unstructured{}
   241  		config.SetKind(ref.Kind)
   242  		config.SetAPIVersion(ref.APIVersion)
   243  		config.SetNamespace(ref.Namespace)
   244  		config.SetName(ref.Name)
   245  
   246  		if err := r.Client.Delete(ctx, config); err != nil && !apierrors.IsNotFound(err) {
   247  			errs = append(errs, errors.Wrap(err, "failed to cleanup generated resources after error"))
   248  		}
   249  	}
   250  
   251  	return kerrors.NewAggregate(errs)
   252  }
   253  
   254  func (r *KubeadmControlPlaneReconciler) generateKubeadmConfig(ctx context.Context, kcp *controlplanev1.KubeadmControlPlane, cluster *clusterv1.Cluster, spec *bootstrapv1.KubeadmConfigSpec, name string) (*corev1.ObjectReference, error) {
   255  	// Create an owner reference without a controller reference because the owning controller is the machine controller
   256  	owner := metav1.OwnerReference{
   257  		APIVersion: controlplanev1.GroupVersion.String(),
   258  		Kind:       kubeadmControlPlaneKind,
   259  		Name:       kcp.Name,
   260  		UID:        kcp.UID,
   261  	}
   262  
   263  	bootstrapConfig := &bootstrapv1.KubeadmConfig{
   264  		ObjectMeta: metav1.ObjectMeta{
   265  			Name:            name,
   266  			Namespace:       kcp.Namespace,
   267  			Labels:          internal.ControlPlaneMachineLabelsForCluster(kcp, cluster.Name),
   268  			Annotations:     kcp.Spec.MachineTemplate.ObjectMeta.Annotations,
   269  			OwnerReferences: []metav1.OwnerReference{owner},
   270  		},
   271  		Spec: *spec,
   272  	}
   273  
   274  	if err := r.Client.Create(ctx, bootstrapConfig); err != nil {
   275  		return nil, errors.Wrap(err, "Failed to create bootstrap configuration")
   276  	}
   277  
   278  	bootstrapRef := &corev1.ObjectReference{
   279  		APIVersion: bootstrapv1.GroupVersion.String(),
   280  		Kind:       "KubeadmConfig",
   281  		Name:       bootstrapConfig.GetName(),
   282  		Namespace:  bootstrapConfig.GetNamespace(),
   283  		UID:        bootstrapConfig.GetUID(),
   284  	}
   285  
   286  	return bootstrapRef, nil
   287  }
   288  
   289  // updateExternalObject updates the external object with the labels and annotations from KCP.
   290  func (r *KubeadmControlPlaneReconciler) updateExternalObject(ctx context.Context, obj client.Object, kcp *controlplanev1.KubeadmControlPlane, cluster *clusterv1.Cluster) error {
   291  	updatedObject := &unstructured.Unstructured{}
   292  	updatedObject.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind())
   293  	updatedObject.SetNamespace(obj.GetNamespace())
   294  	updatedObject.SetName(obj.GetName())
   295  	// Set the UID to ensure that Server-Side-Apply only performs an update
   296  	// and does not perform an accidental create.
   297  	updatedObject.SetUID(obj.GetUID())
   298  
   299  	// Update labels
   300  	updatedObject.SetLabels(internal.ControlPlaneMachineLabelsForCluster(kcp, cluster.Name))
   301  	// Update annotations
   302  	updatedObject.SetAnnotations(kcp.Spec.MachineTemplate.ObjectMeta.Annotations)
   303  
   304  	if err := ssa.Patch(ctx, r.Client, kcpManagerName, updatedObject, ssa.WithCachingProxy{Cache: r.ssaCache, Original: obj}); err != nil {
   305  		return errors.Wrapf(err, "failed to update %s", obj.GetObjectKind().GroupVersionKind().Kind)
   306  	}
   307  	return nil
   308  }
   309  
   310  func (r *KubeadmControlPlaneReconciler) createMachine(ctx context.Context, kcp *controlplanev1.KubeadmControlPlane, machine *clusterv1.Machine) error {
   311  	if err := ssa.Patch(ctx, r.Client, kcpManagerName, machine); err != nil {
   312  		return errors.Wrap(err, "failed to create Machine")
   313  	}
   314  	// Remove the annotation tracking that a remediation is in progress (the remediation completed when
   315  	// the replacement machine has been created above).
   316  	delete(kcp.Annotations, controlplanev1.RemediationInProgressAnnotation)
   317  	return nil
   318  }
   319  
   320  func (r *KubeadmControlPlaneReconciler) updateMachine(ctx context.Context, machine *clusterv1.Machine, kcp *controlplanev1.KubeadmControlPlane, cluster *clusterv1.Cluster) (*clusterv1.Machine, error) {
   321  	updatedMachine, err := r.computeDesiredMachine(kcp, cluster, machine.Spec.FailureDomain, machine)
   322  	if err != nil {
   323  		return nil, errors.Wrap(err, "failed to update Machine: failed to compute desired Machine")
   324  	}
   325  
   326  	err = ssa.Patch(ctx, r.Client, kcpManagerName, updatedMachine, ssa.WithCachingProxy{Cache: r.ssaCache, Original: machine})
   327  	if err != nil {
   328  		return nil, errors.Wrap(err, "failed to update Machine")
   329  	}
   330  	return updatedMachine, nil
   331  }
   332  
   333  // computeDesiredMachine computes the desired Machine.
   334  // This Machine will be used during reconciliation to:
   335  // * create a new Machine
   336  // * update an existing Machine
   337  // Because we are using Server-Side-Apply we always have to calculate the full object.
   338  // There are small differences in how we calculate the Machine depending on if it
   339  // is a create or update. Example: for a new Machine we have to calculate a new name,
   340  // while for an existing Machine we have to use the name of the existing Machine.
   341  func (r *KubeadmControlPlaneReconciler) computeDesiredMachine(kcp *controlplanev1.KubeadmControlPlane, cluster *clusterv1.Cluster, failureDomain *string, existingMachine *clusterv1.Machine) (*clusterv1.Machine, error) {
   342  	var machineName string
   343  	var machineUID types.UID
   344  	var version *string
   345  	annotations := map[string]string{}
   346  	if existingMachine == nil {
   347  		// Creating a new machine
   348  		machineName = names.SimpleNameGenerator.GenerateName(kcp.Name + "-")
   349  		version = &kcp.Spec.Version
   350  
   351  		// Machine's bootstrap config may be missing ClusterConfiguration if it is not the first machine in the control plane.
   352  		// We store ClusterConfiguration as annotation here to detect any changes in KCP ClusterConfiguration and rollout the machine if any.
   353  		// Nb. This annotation is read when comparing the KubeadmConfig to check if a machine needs to be rolled out.
   354  		clusterConfig, err := json.Marshal(kcp.Spec.KubeadmConfigSpec.ClusterConfiguration)
   355  		if err != nil {
   356  			return nil, errors.Wrap(err, "failed to marshal cluster configuration")
   357  		}
   358  		annotations[controlplanev1.KubeadmClusterConfigurationAnnotation] = string(clusterConfig)
   359  
   360  		// In case this machine is being created as a consequence of a remediation, then add an annotation
   361  		// tracking remediating data.
   362  		// NOTE: This is required in order to track remediation retries.
   363  		if remediationData, ok := kcp.Annotations[controlplanev1.RemediationInProgressAnnotation]; ok {
   364  			annotations[controlplanev1.RemediationForAnnotation] = remediationData
   365  		}
   366  	} else {
   367  		// Updating an existing machine
   368  		machineName = existingMachine.Name
   369  		machineUID = existingMachine.UID
   370  		version = existingMachine.Spec.Version
   371  
   372  		// For existing machine only set the ClusterConfiguration annotation if the machine already has it.
   373  		// We should not add the annotation if it was missing in the first place because we do not have enough
   374  		// information.
   375  		if clusterConfig, ok := existingMachine.Annotations[controlplanev1.KubeadmClusterConfigurationAnnotation]; ok {
   376  			annotations[controlplanev1.KubeadmClusterConfigurationAnnotation] = clusterConfig
   377  		}
   378  
   379  		// If the machine already has remediation data then preserve it.
   380  		// NOTE: This is required in order to track remediation retries.
   381  		if remediationData, ok := existingMachine.Annotations[controlplanev1.RemediationForAnnotation]; ok {
   382  			annotations[controlplanev1.RemediationForAnnotation] = remediationData
   383  		}
   384  	}
   385  
   386  	// Construct the basic Machine.
   387  	desiredMachine := &clusterv1.Machine{
   388  		TypeMeta: metav1.TypeMeta{
   389  			APIVersion: clusterv1.GroupVersion.String(),
   390  			Kind:       "Machine",
   391  		},
   392  		ObjectMeta: metav1.ObjectMeta{
   393  			UID:       machineUID,
   394  			Name:      machineName,
   395  			Namespace: kcp.Namespace,
   396  			// Note: by setting the ownerRef on creation we signal to the Machine controller that this is not a stand-alone Machine.
   397  			OwnerReferences: []metav1.OwnerReference{
   398  				*metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind(kubeadmControlPlaneKind)),
   399  			},
   400  			Labels:      map[string]string{},
   401  			Annotations: map[string]string{},
   402  		},
   403  		Spec: clusterv1.MachineSpec{
   404  			ClusterName:   cluster.Name,
   405  			Version:       version,
   406  			FailureDomain: failureDomain,
   407  		},
   408  	}
   409  
   410  	// Set the in-place mutable fields.
   411  	// When we create a new Machine we will just create the Machine with those fields.
   412  	// When we update an existing Machine will we update the fields on the existing Machine (in-place mutate).
   413  
   414  	// Set labels
   415  	desiredMachine.Labels = internal.ControlPlaneMachineLabelsForCluster(kcp, cluster.Name)
   416  
   417  	// Set annotations
   418  	// Add the annotations from the MachineTemplate.
   419  	// Note: we intentionally don't use the map directly to ensure we don't modify the map in KCP.
   420  	for k, v := range kcp.Spec.MachineTemplate.ObjectMeta.Annotations {
   421  		desiredMachine.Annotations[k] = v
   422  	}
   423  	for k, v := range annotations {
   424  		desiredMachine.Annotations[k] = v
   425  	}
   426  
   427  	// Set other in-place mutable fields
   428  	desiredMachine.Spec.NodeDrainTimeout = kcp.Spec.MachineTemplate.NodeDrainTimeout
   429  	desiredMachine.Spec.NodeDeletionTimeout = kcp.Spec.MachineTemplate.NodeDeletionTimeout
   430  	desiredMachine.Spec.NodeVolumeDetachTimeout = kcp.Spec.MachineTemplate.NodeVolumeDetachTimeout
   431  
   432  	if existingMachine != nil {
   433  		desiredMachine.Spec.InfrastructureRef = existingMachine.Spec.InfrastructureRef
   434  		desiredMachine.Spec.Bootstrap.ConfigRef = existingMachine.Spec.Bootstrap.ConfigRef
   435  	}
   436  
   437  	return desiredMachine, nil
   438  }