diff --git a/bootstrap/internal/controllers/rke2config_controller.go b/bootstrap/internal/controllers/rke2config_controller.go index 6c45886c..b980a404 100644 --- a/bootstrap/internal/controllers/rke2config_controller.go +++ b/bootstrap/internal/controllers/rke2config_controller.go @@ -369,6 +369,10 @@ func (r *RKE2ConfigReconciler) handleClusterNotInitialized(ctx context.Context, }() certificates := secret.NewCertificatesForInitialControlPlane() + if _, found := scope.ControlPlane.Annotations[controlplanev1.LegacyRKE2ControlPlane]; found { + certificates = secret.NewCertificatesForLegacyControlPlane() + } + if err := certificates.LookupOrGenerate( ctx, r.Client, diff --git a/controlplane/api/v1beta1/rke2controlplane_types.go b/controlplane/api/v1beta1/rke2controlplane_types.go index 6f2b3076..b2c3b7aa 100644 --- a/controlplane/api/v1beta1/rke2controlplane_types.go +++ b/controlplane/api/v1beta1/rke2controlplane_types.go @@ -36,6 +36,10 @@ const ( // RKE2ServerConfigurationAnnotation is a machine annotation that stores the json-marshalled string of RKE2Config // This annotation is used to detect any changes in RKE2Config and trigger machine rollout. RKE2ServerConfigurationAnnotation = "controlplane.cluster.x-k8s.io/rke2-server-configuration" + + // LegacyRKE2ControlPlane is a controlplane annotation that marks the CP as legacy. This CP will not provide + // etcd certificate management or etcd membership management. + LegacyRKE2ControlPlane = "controlplane.cluster.x-k8s.io/legacy" ) // RKE2ControlPlaneSpec defines the desired state of RKE2ControlPlane. diff --git a/controlplane/internal/controllers/rke2controlplane_controller.go b/controlplane/internal/controllers/rke2controlplane_controller.go index 10a13ed5..c9713b36 100644 --- a/controlplane/internal/controllers/rke2controlplane_controller.go +++ b/controlplane/internal/controllers/rke2controlplane_controller.go @@ -439,6 +439,10 @@ func (r *RKE2ControlPlaneReconciler) reconcileNormal( } certificates := secret.NewCertificatesForInitialControlPlane() + if _, found := rcp.Annotations[controlplanev1.LegacyRKE2ControlPlane]; found { + certificates = secret.NewCertificatesForLegacyControlPlane() + } + controllerRef := metav1.NewControllerRef(rcp, controlplanev1.GroupVersion.WithKind("RKE2ControlPlane")) if err := certificates.LookupOrGenerate(ctx, r.Client, util.ObjectKey(cluster), *controllerRef); err != nil { @@ -546,6 +550,7 @@ func (r *RKE2ControlPlaneReconciler) reconcileNormal( // If we've made it this far, we can assume that all ownedMachines are up to date numMachines := len(ownedMachines) + desiredReplicas := int(*rcp.Spec.Replicas) switch { @@ -601,6 +606,12 @@ func (r *RKE2ControlPlaneReconciler) reconcileEtcdMembers(ctx context.Context, c return nil } + if _, found := controlPlane.RCP.Annotations[controlplanev1.LegacyRKE2ControlPlane]; found { + log.Info("Etcd membership disabled, found controlplane.cluster.x-k8s.io/legacy annotation") + + return nil + } + // Collect all the node names. nodeNames := []string{} @@ -992,6 +1003,10 @@ func (r *RKE2ControlPlaneReconciler) reconcilePreTerminateHook(ctx context.Conte // If we have more than 1 Machine and etcd is managed we forward etcd leadership and remove the member // to keep the etcd cluster healthy. if controlPlane.Machines.Len() > 1 { + if _, found := controlPlane.RCP.Annotations[controlplanev1.LegacyRKE2ControlPlane]; found { + return ctrl.Result{}, nil + } + workloadCluster, err := r.GetWorkloadCluster(ctx, controlPlane) if err != nil { return ctrl.Result{}, errors.Wrapf(err, diff --git a/controlplane/internal/controllers/scale.go b/controlplane/internal/controllers/scale.go index d4eaff05..c0a2fa9e 100644 --- a/controlplane/internal/controllers/scale.go +++ b/controlplane/internal/controllers/scale.go @@ -154,11 +154,13 @@ func (r *RKE2ControlPlaneReconciler) scaleDownControlPlane( } // If etcd leadership is on machine that is about to be deleted, move it to the newest member available. - etcdLeaderCandidate := controlPlane.Machines.Newest() - if err := r.workloadCluster.ForwardEtcdLeadership(ctx, machineToDelete, etcdLeaderCandidate); err != nil { - logger.Error(err, "Failed to move leadership to candidate machine", "candidate", etcdLeaderCandidate.Name) + if _, found := controlPlane.RCP.Annotations[controlplanev1.LegacyRKE2ControlPlane]; !found { + etcdLeaderCandidate := controlPlane.Machines.Newest() + if err := r.workloadCluster.ForwardEtcdLeadership(ctx, machineToDelete, etcdLeaderCandidate); err != nil { + logger.Error(err, "Failed to move leadership to candidate machine", "candidate", etcdLeaderCandidate.Name) - return ctrl.Result{}, err + return ctrl.Result{}, err + } } // NOTE: etcd member removal will be performed by the rke2-cleanup hook after machine completes drain & all volumes are detached. diff --git a/pkg/secret/certificates.go b/pkg/secret/certificates.go index 1dd843e5..ea9a2f93 100644 --- a/pkg/secret/certificates.go +++ b/pkg/secret/certificates.go @@ -194,6 +194,26 @@ func NewCertificatesForInitialControlPlane() Certificates { return certificates } +// NewCertificatesForLegacyControlPlane returns a list of certificates configured for a control plane node, excluding etcd certificates set. +func NewCertificatesForLegacyControlPlane() Certificates { + certificatesDir := DefaultCertificatesDir + + certificates := Certificates{ + &ManagedCertificate{ + Purpose: ClusterCA, + CertFile: filepath.Join(certificatesDir, "server-ca.crt"), + KeyFile: filepath.Join(certificatesDir, "server-ca.key"), + }, + &ManagedCertificate{ + Purpose: ClientClusterCA, + CertFile: filepath.Join(certificatesDir, "client-ca.crt"), + KeyFile: filepath.Join(certificatesDir, "client-ca.key"), + }, + } + + return certificates +} + // GetByPurpose returns a certificate by the given name. // This could be removed if we use a map instead of a slice to hold certificates, however other code becomes more complex. func (c Certificates) GetByPurpose(purpose Purpose) Certificate {