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 }