sigs.k8s.io/cluster-api@v1.6.3/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 errors.Wrap(err, "failed to create patch helper for the kubeconfig secret")
   108  	}
   109  	defer func() {
   110  		if err := patchHelper.Patch(ctx, configSecret); err != nil {
   111  			reterr = errors.Wrap(err, "failed to patch the kubeconfig secret")
   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  	// Since the cloned resource should eventually have a controller ref for the Machine, we create an
   169  	// OwnerReference here without the Controller field set
   170  	infraCloneOwner := &metav1.OwnerReference{
   171  		APIVersion: controlplanev1.GroupVersion.String(),
   172  		Kind:       kubeadmControlPlaneKind,
   173  		Name:       kcp.Name,
   174  		UID:        kcp.UID,
   175  	}
   176  
   177  	// Clone the infrastructure template
   178  	infraRef, err := external.CreateFromTemplate(ctx, &external.CreateFromTemplateInput{
   179  		Client:      r.Client,
   180  		TemplateRef: &kcp.Spec.MachineTemplate.InfrastructureRef,
   181  		Namespace:   kcp.Namespace,
   182  		OwnerRef:    infraCloneOwner,
   183  		ClusterName: cluster.Name,
   184  		Labels:      internal.ControlPlaneMachineLabelsForCluster(kcp, cluster.Name),
   185  		Annotations: kcp.Spec.MachineTemplate.ObjectMeta.Annotations,
   186  	})
   187  	if err != nil {
   188  		// Safe to return early here since no resources have been created yet.
   189  		conditions.MarkFalse(kcp, controlplanev1.MachinesCreatedCondition, controlplanev1.InfrastructureTemplateCloningFailedReason,
   190  			clusterv1.ConditionSeverityError, err.Error())
   191  		return errors.Wrap(err, "failed to clone infrastructure template")
   192  	}
   193  
   194  	// Clone the bootstrap configuration
   195  	bootstrapRef, err := r.generateKubeadmConfig(ctx, kcp, cluster, bootstrapSpec)
   196  	if err != nil {
   197  		conditions.MarkFalse(kcp, controlplanev1.MachinesCreatedCondition, controlplanev1.BootstrapTemplateCloningFailedReason,
   198  			clusterv1.ConditionSeverityError, err.Error())
   199  		errs = append(errs, errors.Wrap(err, "failed to generate bootstrap config"))
   200  	}
   201  
   202  	// Only proceed to generating the Machine if we haven't encountered an error
   203  	if len(errs) == 0 {
   204  		if err := r.createMachine(ctx, kcp, cluster, infraRef, bootstrapRef, failureDomain); err != nil {
   205  			conditions.MarkFalse(kcp, controlplanev1.MachinesCreatedCondition, controlplanev1.MachineGenerationFailedReason,
   206  				clusterv1.ConditionSeverityError, err.Error())
   207  			errs = append(errs, errors.Wrap(err, "failed to create Machine"))
   208  		}
   209  	}
   210  
   211  	// If we encountered any errors, attempt to clean up any dangling resources
   212  	if len(errs) > 0 {
   213  		if err := r.cleanupFromGeneration(ctx, infraRef, bootstrapRef); err != nil {
   214  			errs = append(errs, errors.Wrap(err, "failed to cleanup generated resources"))
   215  		}
   216  
   217  		return kerrors.NewAggregate(errs)
   218  	}
   219  
   220  	return nil
   221  }
   222  
   223  func (r *KubeadmControlPlaneReconciler) cleanupFromGeneration(ctx context.Context, remoteRefs ...*corev1.ObjectReference) error {
   224  	var errs []error
   225  
   226  	for _, ref := range remoteRefs {
   227  		if ref == nil {
   228  			continue
   229  		}
   230  		config := &unstructured.Unstructured{}
   231  		config.SetKind(ref.Kind)
   232  		config.SetAPIVersion(ref.APIVersion)
   233  		config.SetNamespace(ref.Namespace)
   234  		config.SetName(ref.Name)
   235  
   236  		if err := r.Client.Delete(ctx, config); err != nil && !apierrors.IsNotFound(err) {
   237  			errs = append(errs, errors.Wrap(err, "failed to cleanup generated resources after error"))
   238  		}
   239  	}
   240  
   241  	return kerrors.NewAggregate(errs)
   242  }
   243  
   244  func (r *KubeadmControlPlaneReconciler) generateKubeadmConfig(ctx context.Context, kcp *controlplanev1.KubeadmControlPlane, cluster *clusterv1.Cluster, spec *bootstrapv1.KubeadmConfigSpec) (*corev1.ObjectReference, error) {
   245  	// Create an owner reference without a controller reference because the owning controller is the machine controller
   246  	owner := metav1.OwnerReference{
   247  		APIVersion: controlplanev1.GroupVersion.String(),
   248  		Kind:       kubeadmControlPlaneKind,
   249  		Name:       kcp.Name,
   250  		UID:        kcp.UID,
   251  	}
   252  
   253  	bootstrapConfig := &bootstrapv1.KubeadmConfig{
   254  		ObjectMeta: metav1.ObjectMeta{
   255  			Name:            names.SimpleNameGenerator.GenerateName(kcp.Name + "-"),
   256  			Namespace:       kcp.Namespace,
   257  			Labels:          internal.ControlPlaneMachineLabelsForCluster(kcp, cluster.Name),
   258  			Annotations:     kcp.Spec.MachineTemplate.ObjectMeta.Annotations,
   259  			OwnerReferences: []metav1.OwnerReference{owner},
   260  		},
   261  		Spec: *spec,
   262  	}
   263  
   264  	if err := r.Client.Create(ctx, bootstrapConfig); err != nil {
   265  		return nil, errors.Wrap(err, "Failed to create bootstrap configuration")
   266  	}
   267  
   268  	bootstrapRef := &corev1.ObjectReference{
   269  		APIVersion: bootstrapv1.GroupVersion.String(),
   270  		Kind:       "KubeadmConfig",
   271  		Name:       bootstrapConfig.GetName(),
   272  		Namespace:  bootstrapConfig.GetNamespace(),
   273  		UID:        bootstrapConfig.GetUID(),
   274  	}
   275  
   276  	return bootstrapRef, nil
   277  }
   278  
   279  // updateExternalObject updates the external object with the labels and annotations from KCP.
   280  func (r *KubeadmControlPlaneReconciler) updateExternalObject(ctx context.Context, obj client.Object, kcp *controlplanev1.KubeadmControlPlane, cluster *clusterv1.Cluster) error {
   281  	updatedObject := &unstructured.Unstructured{}
   282  	updatedObject.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind())
   283  	updatedObject.SetNamespace(obj.GetNamespace())
   284  	updatedObject.SetName(obj.GetName())
   285  	// Set the UID to ensure that Server-Side-Apply only performs an update
   286  	// and does not perform an accidental create.
   287  	updatedObject.SetUID(obj.GetUID())
   288  
   289  	// Update labels
   290  	updatedObject.SetLabels(internal.ControlPlaneMachineLabelsForCluster(kcp, cluster.Name))
   291  	// Update annotations
   292  	updatedObject.SetAnnotations(kcp.Spec.MachineTemplate.ObjectMeta.Annotations)
   293  
   294  	if err := ssa.Patch(ctx, r.Client, kcpManagerName, updatedObject, ssa.WithCachingProxy{Cache: r.ssaCache, Original: obj}); err != nil {
   295  		return errors.Wrapf(err, "failed to update %s", obj.GetObjectKind().GroupVersionKind().Kind)
   296  	}
   297  	return nil
   298  }
   299  
   300  func (r *KubeadmControlPlaneReconciler) createMachine(ctx context.Context, kcp *controlplanev1.KubeadmControlPlane, cluster *clusterv1.Cluster, infraRef, bootstrapRef *corev1.ObjectReference, failureDomain *string) error {
   301  	machine, err := r.computeDesiredMachine(kcp, cluster, infraRef, bootstrapRef, failureDomain, nil)
   302  	if err != nil {
   303  		return errors.Wrap(err, "failed to create Machine: failed to compute desired Machine")
   304  	}
   305  	if err := ssa.Patch(ctx, r.Client, kcpManagerName, machine); err != nil {
   306  		return errors.Wrap(err, "failed to create Machine")
   307  	}
   308  	// Remove the annotation tracking that a remediation is in progress (the remediation completed when
   309  	// the replacement machine has been created above).
   310  	delete(kcp.Annotations, controlplanev1.RemediationInProgressAnnotation)
   311  	return nil
   312  }
   313  
   314  func (r *KubeadmControlPlaneReconciler) updateMachine(ctx context.Context, machine *clusterv1.Machine, kcp *controlplanev1.KubeadmControlPlane, cluster *clusterv1.Cluster) (*clusterv1.Machine, error) {
   315  	updatedMachine, err := r.computeDesiredMachine(
   316  		kcp, cluster,
   317  		&machine.Spec.InfrastructureRef, machine.Spec.Bootstrap.ConfigRef,
   318  		machine.Spec.FailureDomain, machine,
   319  	)
   320  	if err != nil {
   321  		return nil, errors.Wrap(err, "failed to update Machine: failed to compute desired Machine")
   322  	}
   323  
   324  	err = ssa.Patch(ctx, r.Client, kcpManagerName, updatedMachine, ssa.WithCachingProxy{Cache: r.ssaCache, Original: machine})
   325  	if err != nil {
   326  		return nil, errors.Wrap(err, "failed to update Machine")
   327  	}
   328  	return updatedMachine, nil
   329  }
   330  
   331  // computeDesiredMachine computes the desired Machine.
   332  // This Machine will be used during reconciliation to:
   333  // * create a new Machine
   334  // * update an existing Machine
   335  // Because we are using Server-Side-Apply we always have to calculate the full object.
   336  // There are small differences in how we calculate the Machine depending on if it
   337  // is a create or update. Example: for a new Machine we have to calculate a new name,
   338  // while for an existing Machine we have to use the name of the existing Machine.
   339  func (r *KubeadmControlPlaneReconciler) computeDesiredMachine(kcp *controlplanev1.KubeadmControlPlane, cluster *clusterv1.Cluster, infraRef, bootstrapRef *corev1.ObjectReference, failureDomain *string, existingMachine *clusterv1.Machine) (*clusterv1.Machine, error) {
   340  	var machineName string
   341  	var machineUID types.UID
   342  	var version *string
   343  	annotations := map[string]string{}
   344  	if existingMachine == nil {
   345  		// Creating a new machine
   346  		machineName = names.SimpleNameGenerator.GenerateName(kcp.Name + "-")
   347  		version = &kcp.Spec.Version
   348  
   349  		// Machine's bootstrap config may be missing ClusterConfiguration if it is not the first machine in the control plane.
   350  		// We store ClusterConfiguration as annotation here to detect any changes in KCP ClusterConfiguration and rollout the machine if any.
   351  		// Nb. This annotation is read when comparing the KubeadmConfig to check if a machine needs to be rolled out.
   352  		clusterConfig, err := json.Marshal(kcp.Spec.KubeadmConfigSpec.ClusterConfiguration)
   353  		if err != nil {
   354  			return nil, errors.Wrap(err, "failed to marshal cluster configuration")
   355  		}
   356  		annotations[controlplanev1.KubeadmClusterConfigurationAnnotation] = string(clusterConfig)
   357  
   358  		// In case this machine is being created as a consequence of a remediation, then add an annotation
   359  		// tracking remediating data.
   360  		// NOTE: This is required in order to track remediation retries.
   361  		if remediationData, ok := kcp.Annotations[controlplanev1.RemediationInProgressAnnotation]; ok {
   362  			annotations[controlplanev1.RemediationForAnnotation] = remediationData
   363  		}
   364  	} else {
   365  		// Updating an existing machine
   366  		machineName = existingMachine.Name
   367  		machineUID = existingMachine.UID
   368  		version = existingMachine.Spec.Version
   369  
   370  		// For existing machine only set the ClusterConfiguration annotation if the machine already has it.
   371  		// We should not add the annotation if it was missing in the first place because we do not have enough
   372  		// information.
   373  		if clusterConfig, ok := existingMachine.Annotations[controlplanev1.KubeadmClusterConfigurationAnnotation]; ok {
   374  			annotations[controlplanev1.KubeadmClusterConfigurationAnnotation] = clusterConfig
   375  		}
   376  
   377  		// If the machine already has remediation data then preserve it.
   378  		// NOTE: This is required in order to track remediation retries.
   379  		if remediationData, ok := existingMachine.Annotations[controlplanev1.RemediationForAnnotation]; ok {
   380  			annotations[controlplanev1.RemediationForAnnotation] = remediationData
   381  		}
   382  	}
   383  
   384  	// Construct the basic Machine.
   385  	desiredMachine := &clusterv1.Machine{
   386  		TypeMeta: metav1.TypeMeta{
   387  			APIVersion: clusterv1.GroupVersion.String(),
   388  			Kind:       "Machine",
   389  		},
   390  		ObjectMeta: metav1.ObjectMeta{
   391  			UID:       machineUID,
   392  			Name:      machineName,
   393  			Namespace: kcp.Namespace,
   394  			// Note: by setting the ownerRef on creation we signal to the Machine controller that this is not a stand-alone Machine.
   395  			OwnerReferences: []metav1.OwnerReference{
   396  				*metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind(kubeadmControlPlaneKind)),
   397  			},
   398  			Labels:      map[string]string{},
   399  			Annotations: map[string]string{},
   400  		},
   401  		Spec: clusterv1.MachineSpec{
   402  			ClusterName:       cluster.Name,
   403  			Version:           version,
   404  			FailureDomain:     failureDomain,
   405  			InfrastructureRef: *infraRef,
   406  			Bootstrap: clusterv1.Bootstrap{
   407  				ConfigRef: bootstrapRef,
   408  			},
   409  		},
   410  	}
   411  
   412  	// Set the in-place mutable fields.
   413  	// When we create a new Machine we will just create the Machine with those fields.
   414  	// When we update an existing Machine will we update the fields on the existing Machine (in-place mutate).
   415  
   416  	// Set labels
   417  	desiredMachine.Labels = internal.ControlPlaneMachineLabelsForCluster(kcp, cluster.Name)
   418  
   419  	// Set annotations
   420  	// Add the annotations from the MachineTemplate.
   421  	// Note: we intentionally don't use the map directly to ensure we don't modify the map in KCP.
   422  	for k, v := range kcp.Spec.MachineTemplate.ObjectMeta.Annotations {
   423  		desiredMachine.Annotations[k] = v
   424  	}
   425  	for k, v := range annotations {
   426  		desiredMachine.Annotations[k] = v
   427  	}
   428  
   429  	// Set other in-place mutable fields
   430  	desiredMachine.Spec.NodeDrainTimeout = kcp.Spec.MachineTemplate.NodeDrainTimeout
   431  	desiredMachine.Spec.NodeDeletionTimeout = kcp.Spec.MachineTemplate.NodeDeletionTimeout
   432  	desiredMachine.Spec.NodeVolumeDetachTimeout = kcp.Spec.MachineTemplate.NodeVolumeDetachTimeout
   433  
   434  	return desiredMachine, nil
   435  }