@@ -33,6 +33,7 @@ import (
33
33
kerrors "k8s.io/apimachinery/pkg/util/errors"
34
34
"k8s.io/apiserver/pkg/storage/names"
35
35
"k8s.io/client-go/tools/record"
36
+ "k8s.io/utils/pointer"
36
37
ctrl "sigs.k8s.io/controller-runtime"
37
38
"sigs.k8s.io/controller-runtime/pkg/client"
38
39
"sigs.k8s.io/controller-runtime/pkg/controller"
@@ -69,7 +70,7 @@ const (
69
70
)
70
71
71
72
// +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;patch
72
- // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;patch
73
+ // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update; patch
73
74
// +kubebuilder:rbac:groups=core,resources=configmaps,namespace=kube-system,verbs=get;list;watch;create
74
75
// +kubebuilder:rbac:groups=rbac,resources=roles,namespace=kube-system,verbs=get;list;watch;create
75
76
// +kubebuilder:rbac:groups=rbac,resources=rolebindings,namespace=kube-system,verbs=get;list;watch;create
@@ -86,6 +87,8 @@ type KubeadmControlPlaneReconciler struct {
86
87
recorder record.EventRecorder
87
88
88
89
managementCluster internal.ManagementCluster
90
+
91
+ uncachedClient client.Reader
89
92
}
90
93
91
94
func (r * KubeadmControlPlaneReconciler ) SetupWithManager (mgr ctrl.Manager , options controller.Options ) error {
@@ -110,6 +113,7 @@ func (r *KubeadmControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager, optio
110
113
if r .managementCluster == nil {
111
114
r .managementCluster = & internal.Management {Client : r .Client }
112
115
}
116
+ r .uncachedClient = mgr .GetAPIReader ()
113
117
114
118
return nil
115
119
}
@@ -227,13 +231,25 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, cluster *
227
231
return ctrl.Result {}, err
228
232
}
229
233
230
- // TODO: handle proper adoption of Machines
231
- ownedMachines , err := r .managementCluster .GetMachinesForCluster (ctx , util .ObjectKey (cluster ), internal .OwnedControlPlaneMachines (kcp .Name ))
234
+ controlPlaneMachines , err := r .managementCluster .GetMachinesForCluster (ctx , util .ObjectKey (cluster ), internal .ControlPlaneMachines (cluster .Name ))
232
235
if err != nil {
233
236
logger .Error (err , "failed to retrieve control plane machines for cluster" )
234
237
return ctrl.Result {}, err
235
238
}
236
239
240
+ adoptableMachines := controlPlaneMachines .Filter (internal .AdoptableControlPlaneMachines (cluster .Name ))
241
+ if len (adoptableMachines ) > 0 {
242
+ // We adopt the Machines and then wait for the update event for the ownership reference to re-queue them so the cache is up-to-date
243
+ err = r .adoptMachines (ctx , kcp , adoptableMachines )
244
+ return ctrl.Result {}, err
245
+ }
246
+
247
+ ownedMachines := controlPlaneMachines .Filter (internal .OwnedMachines (kcp ))
248
+ if len (ownedMachines ) != len (controlPlaneMachines ) {
249
+ logger .Info ("Not all control plane machines are owned by this KubeadmControlPlane, refusing to operate in mixed management mode" )
250
+ return ctrl.Result {}, nil
251
+ }
252
+
237
253
now := metav1 .Now ()
238
254
var requireUpgrade internal.FilterableMachineCollection
239
255
if kcp .Spec .UpgradeAfter != nil && kcp .Spec .UpgradeAfter .Before (& now ) {
@@ -276,17 +292,12 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, cluster *
276
292
}
277
293
278
294
func (r * KubeadmControlPlaneReconciler ) updateStatus (ctx context.Context , kcp * controlplanev1.KubeadmControlPlane , cluster * clusterv1.Cluster ) error {
279
- labelSelector := internal .ControlPlaneSelectorForCluster (cluster .Name )
280
- selector , err := metav1 .LabelSelectorAsSelector (labelSelector )
281
- if err != nil {
282
- // Since we are building up the LabelSelector above, this should not fail
283
- return errors .Wrap (err , "failed to parse label selector" )
284
- }
295
+ selector := internal .ControlPlaneSelectorForCluster (cluster .Name )
285
296
// Copy label selector to its status counterpart in string format.
286
297
// This is necessary for CRDs including scale subresources.
287
298
kcp .Status .Selector = selector .String ()
288
299
289
- ownedMachines , err := r .managementCluster .GetMachinesForCluster (ctx , util .ObjectKey (cluster ), internal .OwnedControlPlaneMachines (kcp . Name ))
300
+ ownedMachines , err := r .managementCluster .GetMachinesForCluster (ctx , util .ObjectKey (cluster ), internal .OwnedMachines (kcp ))
290
301
if err != nil {
291
302
return errors .Wrap (err , "failed to get list of owned machines" )
292
303
}
@@ -444,13 +455,13 @@ func (r *KubeadmControlPlaneReconciler) initializeControlPlane(ctx context.Conte
444
455
445
456
func (r * KubeadmControlPlaneReconciler ) scaleUpControlPlane (ctx context.Context , cluster * clusterv1.Cluster , kcp * controlplanev1.KubeadmControlPlane , machines internal.FilterableMachineCollection ) (ctrl.Result , error ) {
446
457
logger := r .Log .WithValues ("namespace" , kcp .Namespace , "kubeadmControlPlane" , kcp .Name , "cluster" , cluster .Name )
447
- if err := r .managementCluster .TargetClusterControlPlaneIsHealthy (ctx , util .ObjectKey (cluster ), kcp . Name ); err != nil {
458
+ if err := r .managementCluster .TargetClusterControlPlaneIsHealthy (ctx , util .ObjectKey (cluster )); err != nil {
448
459
logger .Error (err , "waiting for control plane to pass control plane health check before adding an additional control plane machine" )
449
460
r .recorder .Eventf (kcp , corev1 .EventTypeWarning , "ControlPlaneUnhealthy" , "Waiting for control plane to pass control plane health check before adding additional control plane machine: %v" , err )
450
461
return ctrl.Result {}, & capierrors.RequeueAfterError {RequeueAfter : HealthCheckFailedRequeueAfter }
451
462
}
452
463
453
- if err := r .managementCluster .TargetClusterEtcdIsHealthy (ctx , util .ObjectKey (cluster ), kcp . Name ); err != nil {
464
+ if err := r .managementCluster .TargetClusterEtcdIsHealthy (ctx , util .ObjectKey (cluster )); err != nil {
454
465
logger .Error (err , "waiting for control plane to pass etcd health check before adding an additional control plane machine" )
455
466
r .recorder .Eventf (kcp , corev1 .EventTypeWarning , "ControlPlaneUnhealthy" , "Waiting for control plane to pass etcd health check before adding additional control plane machine: %v" , err )
456
467
return ctrl.Result {}, & capierrors.RequeueAfterError {RequeueAfter : HealthCheckFailedRequeueAfter }
@@ -511,7 +522,7 @@ func (r *KubeadmControlPlaneReconciler) scaleDownControlPlane(ctx context.Contex
511
522
512
523
if ! internal .HasAnnotationKey (controlplanev1 .ScaleDownEtcdMemberRemovedAnnotation )(machineToDelete ) {
513
524
// Ensure etcd is healthy prior to attempting to remove the member
514
- if err := r .managementCluster .TargetClusterEtcdIsHealthy (ctx , util .ObjectKey (cluster ), kcp . Name ); err != nil {
525
+ if err := r .managementCluster .TargetClusterEtcdIsHealthy (ctx , util .ObjectKey (cluster )); err != nil {
515
526
logger .Error (err , "waiting for control plane to pass etcd health check before removing a control plane machine" )
516
527
r .recorder .Eventf (kcp , corev1 .EventTypeWarning , "ControlPlaneUnhealthy" , "Waiting for control plane to pass etcd health check before removing a control plane machine: %v" , err )
517
528
return ctrl.Result {}, & capierrors.RequeueAfterError {RequeueAfter : HealthCheckFailedRequeueAfter }
@@ -526,7 +537,7 @@ func (r *KubeadmControlPlaneReconciler) scaleDownControlPlane(ctx context.Contex
526
537
}
527
538
528
539
if ! internal .HasAnnotationKey (controlplanev1 .ScaleDownConfigMapEntryRemovedAnnotation )(machineToDelete ) {
529
- if err := r .managementCluster .TargetClusterControlPlaneIsHealthy (ctx , util .ObjectKey (cluster ), kcp . Name ); err != nil {
540
+ if err := r .managementCluster .TargetClusterControlPlaneIsHealthy (ctx , util .ObjectKey (cluster )); err != nil {
530
541
logger .Error (err , "waiting for control plane to pass control plane health check before removing a control plane machine" )
531
542
r .recorder .Eventf (kcp , corev1 .EventTypeWarning , "ControlPlaneUnhealthy" , "Waiting for control plane to pass control plane health check before removing a control plane machine: %v" , err )
532
543
return ctrl.Result {}, & capierrors.RequeueAfterError {RequeueAfter : HealthCheckFailedRequeueAfter }
@@ -542,7 +553,7 @@ func (r *KubeadmControlPlaneReconciler) scaleDownControlPlane(ctx context.Contex
542
553
}
543
554
544
555
// Do a final health check of the Control Plane components prior to actually deleting the machine
545
- if err := r .managementCluster .TargetClusterControlPlaneIsHealthy (ctx , util .ObjectKey (cluster ), kcp . Name ); err != nil {
556
+ if err := r .managementCluster .TargetClusterControlPlaneIsHealthy (ctx , util .ObjectKey (cluster )); err != nil {
546
557
logger .Error (err , "waiting for control plane to pass control plane health check before removing a control plane machine" )
547
558
r .recorder .Eventf (kcp , corev1 .EventTypeWarning , "ControlPlaneUnhealthy" , "Waiting for control plane to pass control plane health check before removing a control plane machine: %v" , err )
548
559
return ctrl.Result {}, & capierrors.RequeueAfterError {RequeueAfter : HealthCheckFailedRequeueAfter }
@@ -724,7 +735,7 @@ func (r *KubeadmControlPlaneReconciler) reconcileDelete(ctx context.Context, clu
724
735
logger .Error (err , "failed to retrieve machines for cluster" )
725
736
return ctrl.Result {}, err
726
737
}
727
- ownedMachines := allMachines .Filter (internal .OwnedControlPlaneMachines (kcp . Name ))
738
+ ownedMachines := allMachines .Filter (internal .OwnedMachines (kcp ))
728
739
729
740
// If no control plane machines remain, remove the finalizer
730
741
if len (ownedMachines ) == 0 {
@@ -834,3 +845,130 @@ func (r *KubeadmControlPlaneReconciler) ClusterToKubeadmControlPlane(o handler.M
834
845
835
846
return nil
836
847
}
848
+
849
+ func (r * KubeadmControlPlaneReconciler ) adoptMachines (ctx context.Context , kcp * controlplanev1.KubeadmControlPlane , machines internal.FilterableMachineCollection ) error {
850
+ // We do an uncached full quorum read against the KCP to avoid re-adopting Machines the garbage collector just intentionally orphaned
851
+ // See https://github.com/kubernetes/kubernetes/issues/42639
852
+ uncached := controlplanev1.KubeadmControlPlane {}
853
+ err := r .uncachedClient .Get (ctx , client.ObjectKey {Namespace : kcp .Namespace , Name : kcp .Name }, & uncached )
854
+ if err != nil {
855
+ return errors .Wrapf (err , "failed to check whether %v/%v was deleted before adoption" , kcp .GetNamespace (), kcp .GetName ())
856
+ }
857
+ if ! uncached .DeletionTimestamp .IsZero () {
858
+ return errors .Errorf ("%v/%v has just been deleted at %v" , kcp .GetNamespace (), kcp .GetName (), kcp .GetDeletionTimestamp ())
859
+ }
860
+
861
+ kcpVersion , err := semver .ParseTolerant (kcp .Spec .Version )
862
+ if err != nil {
863
+ return errors .Wrapf (err , "failed to parse kubernetes version %q" , kcp .Spec .Version )
864
+ }
865
+
866
+ for _ , m := range machines {
867
+ ref := m .Spec .Bootstrap .ConfigRef
868
+
869
+ // TODO instead of returning error here, we should instead Event and add a watch on potentially adoptable Machines
870
+ if ref == nil || ref .Kind != "KubeadmConfig" {
871
+ return errors .Errorf ("unable to adopt Machine %v/%v: expected a ConfigRef of kind KubeadmConfig but instead found %v" , m .Namespace , m .Name , ref )
872
+ }
873
+
874
+ // TODO instead of returning error here, we should instead Event and add a watch on potentially adoptable Machines
875
+ if ref .Namespace != "" && ref .Namespace != kcp .Namespace {
876
+ return errors .Errorf ("could not adopt resources from KubeadmConfig %v/%v: cannot adopt across namespaces" , ref .Namespace , ref .Name )
877
+ }
878
+
879
+ if m .Spec .Version == nil {
880
+ // if the machine's version is not immediately apparent, assume the operator knows what they're doing
881
+ continue
882
+ }
883
+
884
+ machineVersion , err := semver .ParseTolerant (* m .Spec .Version )
885
+ if err != nil {
886
+ return errors .Wrapf (err , "failed to parse kubernetes version %q" , * m .Spec .Version )
887
+ }
888
+
889
+ if ! util .IsSupportedVersionSkew (kcpVersion , machineVersion ) {
890
+ r .recorder .Eventf (kcp , corev1 .EventTypeWarning , "AdoptionFailed" , "Could not adopt Machine %s/%s: its version (%q) is outside supported +/- one minor version skew from KCP's (%q)" , m .Namespace , m .Name , * m .Spec .Version , kcp .Spec .Version )
891
+ // avoid returning an error here so we don't cause the KCP controller to spin until the operator clarifies their intent
892
+ return nil
893
+ }
894
+ }
895
+
896
+ for _ , m := range machines {
897
+ ref := m .Spec .Bootstrap .ConfigRef
898
+ obj := & bootstrapv1.KubeadmConfig {}
899
+ err := r .Client .Get (ctx , client.ObjectKey {Name : ref .Name , Namespace : kcp .Namespace }, obj )
900
+ if err != nil {
901
+ return err
902
+ }
903
+
904
+ err = r .adoptOwnedSecrets (ctx , kcp , obj )
905
+ if err != nil {
906
+ return err
907
+ }
908
+
909
+ patchHelper , err := patch .NewHelper (m , r .Client )
910
+ if err != nil {
911
+ return err
912
+ }
913
+
914
+ if err = controllerutil .SetControllerReference (m , kcp , r .scheme ); err != nil {
915
+ return err
916
+ }
917
+
918
+ // 0. get machine.Spec.Version - the easy answer
919
+ machineKubernetesVersion := ""
920
+ if m .Spec .Version != nil {
921
+ machineKubernetesVersion = * m .Spec .Version
922
+ }
923
+
924
+ // 1. hash the version (kubernetes version) and kubeadm_controlplane's Spec.infrastructureTemplate
925
+ asIfSpec := controlplanev1.KubeadmControlPlaneSpec {
926
+ Version : machineKubernetesVersion ,
927
+ InfrastructureTemplate : kcp .Spec .InfrastructureTemplate ,
928
+ }
929
+ newConfigurationHash := hash .Compute (& asIfSpec )
930
+ // 2. add kubeadm.controlplane.cluster.x-k8s.io/hash as a label in each machine
931
+ m .Labels ["kubeadm.controlplane.cluster.x-k8s.io/hash" ] = newConfigurationHash
932
+
933
+ // Note that ValidateOwnerReferences() will reject this patch if another
934
+ // OwnerReference exists with controller=true.
935
+ if err := patchHelper .Patch (ctx , m ); err != nil {
936
+ return err
937
+ }
938
+ }
939
+ return nil
940
+ }
941
+
942
+ func (r * KubeadmControlPlaneReconciler ) adoptOwnedSecrets (ctx context.Context , kcp * controlplanev1.KubeadmControlPlane , currentOwner metav1.Object ) error {
943
+ secrets := corev1.SecretList {}
944
+ if err := r .Client .List (ctx , & secrets , client .InNamespace (kcp .Namespace )); err != nil {
945
+ return errors .Wrap (err , "error finding secrets for adoption" )
946
+ }
947
+
948
+ for _ , s := range secrets .Items {
949
+ if ! util .PointsTo (s .GetOwnerReferences (), currentOwner ) {
950
+ continue
951
+ }
952
+ // avoid taking ownership of the bootstrap data secret
953
+ if s .Name == currentOwner .GetName () {
954
+ continue
955
+ }
956
+
957
+ ss := s .DeepCopy ()
958
+
959
+ ss .SetOwnerReferences (util .ReplaceOwnerRef (ss .GetOwnerReferences (), metav1.OwnerReference {
960
+ APIVersion : controlplanev1 .GroupVersion .String (),
961
+ Kind : "KubeadmControlPlane" ,
962
+ Name : kcp .Name ,
963
+ UID : kcp .UID ,
964
+ Controller : pointer .BoolPtr (true ),
965
+ BlockOwnerDeletion : pointer .BoolPtr (true ),
966
+ }, currentOwner ))
967
+
968
+ if err := r .Client .Update (ctx , ss ); err != nil {
969
+ return errors .Wrapf (err , "error changing secret %v ownership from KubeadmConfig/%v to KubeadmControlPlane/%v" , s .Name , currentOwner .GetName (), kcp .Name )
970
+ }
971
+ }
972
+
973
+ return nil
974
+ }
0 commit comments