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 }