Skip to content

Commit 9c08773

Browse files
authored
Merge pull request #10947 from sbueringer/pr-fix-kubeadm
✨ KCP: default ControlPlaneKubeletLocalMode feature gate to true for >= 1.31.0
2 parents 9e9985d + 14433d5 commit 9c08773

File tree

7 files changed

+304
-29
lines changed

7 files changed

+304
-29
lines changed

controlplane/kubeadm/internal/controllers/controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2351,7 +2351,7 @@ func createClusterWithControlPlane(namespace string) (*clusterv1.Cluster, *contr
23512351
},
23522352
},
23532353
Replicas: ptr.To[int32](int32(3)),
2354-
Version: "v1.16.6",
2354+
Version: "v1.31.0",
23552355
RolloutStrategy: &controlplanev1.RolloutStrategy{
23562356
Type: "RollingUpdate",
23572357
RollingUpdate: &controlplanev1.RollingUpdate{

controlplane/kubeadm/internal/controllers/scale.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,22 @@ import (
3333
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
3434
"sigs.k8s.io/cluster-api/util/collections"
3535
"sigs.k8s.io/cluster-api/util/conditions"
36+
"sigs.k8s.io/cluster-api/util/version"
3637
)
3738

3839
func (r *KubeadmControlPlaneReconciler) initializeControlPlane(ctx context.Context, controlPlane *internal.ControlPlane) (ctrl.Result, error) {
3940
logger := ctrl.LoggerFrom(ctx)
4041

4142
bootstrapSpec := controlPlane.InitialControlPlaneConfig()
43+
44+
// We intentionally only parse major/minor/patch so that the subsequent code
45+
// also already applies to beta versions of new releases.
46+
parsedVersionTolerant, err := version.ParseMajorMinorPatchTolerant(controlPlane.KCP.Spec.Version)
47+
if err != nil {
48+
return ctrl.Result{}, errors.Wrapf(err, "failed to parse kubernetes version %q", controlPlane.KCP.Spec.Version)
49+
}
50+
internal.DefaultFeatureGates(bootstrapSpec, parsedVersionTolerant)
51+
4252
fd, err := controlPlane.NextFailureDomainForScaleUp(ctx)
4353
if err != nil {
4454
return ctrl.Result{}, err
@@ -64,6 +74,15 @@ func (r *KubeadmControlPlaneReconciler) scaleUpControlPlane(ctx context.Context,
6474

6575
// Create the bootstrap configuration
6676
bootstrapSpec := controlPlane.JoinControlPlaneConfig()
77+
78+
// We intentionally only parse major/minor/patch so that the subsequent code
79+
// also already applies to beta versions of new releases.
80+
parsedVersionTolerant, err := version.ParseMajorMinorPatchTolerant(controlPlane.KCP.Spec.Version)
81+
if err != nil {
82+
return ctrl.Result{}, errors.Wrapf(err, "failed to parse kubernetes version %q", controlPlane.KCP.Spec.Version)
83+
}
84+
internal.DefaultFeatureGates(bootstrapSpec, parsedVersionTolerant)
85+
6786
fd, err := controlPlane.NextFailureDomainForScaleUp(ctx)
6887
if err != nil {
6988
return ctrl.Result{}, err

controlplane/kubeadm/internal/controllers/scale_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ func TestKubeadmControlPlaneReconciler_initializeControlPlane(t *testing.T) {
102102
g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.Name).To(Equal(machineList.Items[0].Name))
103103
g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.APIVersion).To(Equal(bootstrapv1.GroupVersion.String()))
104104
g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.Kind).To(Equal("KubeadmConfig"))
105+
106+
kubeadmConfig := &bootstrapv1.KubeadmConfig{}
107+
bootstrapRef := machineList.Items[0].Spec.Bootstrap.ConfigRef
108+
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKey{Namespace: bootstrapRef.Namespace, Name: bootstrapRef.Name}, kubeadmConfig)).To(Succeed())
109+
g.Expect(kubeadmConfig.Spec.ClusterConfiguration.FeatureGates).To(BeComparableTo(map[string]bool{internal.ControlPlaneKubeletLocalMode: true}))
105110
}
106111

107112
func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
@@ -165,6 +170,11 @@ func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
165170
// Note: expected length is 1 because only the newly created machine is on API server. Other machines are
166171
// in-memory only during the test.
167172
g.Expect(controlPlaneMachines.Items).To(HaveLen(1))
173+
174+
kubeadmConfig := &bootstrapv1.KubeadmConfig{}
175+
bootstrapRef := controlPlaneMachines.Items[0].Spec.Bootstrap.ConfigRef
176+
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKey{Namespace: bootstrapRef.Namespace, Name: bootstrapRef.Name}, kubeadmConfig)).To(Succeed())
177+
g.Expect(kubeadmConfig.Spec.ClusterConfiguration.FeatureGates).To(BeComparableTo(map[string]bool{internal.ControlPlaneKubeletLocalMode: true}))
168178
})
169179
t.Run("does not create a control plane Machine if preflight checks fail", func(t *testing.T) {
170180
setup := func(t *testing.T, g *WithT) *corev1.Namespace {

controlplane/kubeadm/internal/controllers/upgrade.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func (r *KubeadmControlPlaneReconciler) upgradeControlPlane(
9090

9191
kubeadmCMMutators = append(kubeadmCMMutators,
9292
workloadCluster.UpdateImageRepositoryInKubeadmConfigMap(imageRepository),
93-
workloadCluster.UpdateFeatureGatesInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.FeatureGates),
93+
workloadCluster.UpdateFeatureGatesInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec, parsedVersionTolerant),
9494
workloadCluster.UpdateAPIServerInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer),
9595
workloadCluster.UpdateControllerManagerInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.ControllerManager),
9696
workloadCluster.UpdateSchedulerInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.Scheduler))

controlplane/kubeadm/internal/workload_cluster.go

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ var (
8686
// NOTE: The following assumes that kubeadm version equals to Kubernetes version.
8787
minVerUnversionedKubeletConfig = semver.MustParse("1.24.0")
8888

89+
// minKubernetesVersionControlPlaneKubeletLocalMode is the min version from which
90+
// we will enable the ControlPlaneKubeletLocalMode kubeadm feature gate.
91+
// Note: We have to do this with Kubernetes 1.31. Because with that version we encountered
92+
// a case where it's not okay anymore to ignore the Kubernetes version skew (kubelet 1.31 uses
93+
// the spec.clusterIP field selector that is only implemented in kube-apiserver >= 1.31.0).
94+
minKubernetesVersionControlPlaneKubeletLocalMode = semver.MustParse("1.31.0")
95+
8996
// ErrControlPlaneMinNodes signals that a cluster doesn't meet the minimum required nodes
9097
// to remove an etcd member.
9198
ErrControlPlaneMinNodes = errors.New("cluster has fewer than 2 control plane nodes; removing an etcd member is not supported")
@@ -107,7 +114,7 @@ type WorkloadCluster interface {
107114
ReconcileKubeletRBACRole(ctx context.Context, version semver.Version) error
108115
UpdateKubernetesVersionInKubeadmConfigMap(version semver.Version) func(*bootstrapv1.ClusterConfiguration)
109116
UpdateImageRepositoryInKubeadmConfigMap(imageRepository string) func(*bootstrapv1.ClusterConfiguration)
110-
UpdateFeatureGatesInKubeadmConfigMap(featureGates map[string]bool) func(*bootstrapv1.ClusterConfiguration)
117+
UpdateFeatureGatesInKubeadmConfigMap(kubeadmConfigSpec bootstrapv1.KubeadmConfigSpec, kubernetesVersion semver.Version) func(*bootstrapv1.ClusterConfiguration)
111118
UpdateEtcdLocalInKubeadmConfigMap(localEtcd *bootstrapv1.LocalEtcd) func(*bootstrapv1.ClusterConfiguration)
112119
UpdateEtcdExternalInKubeadmConfigMap(externalEtcd *bootstrapv1.ExternalEtcd) func(*bootstrapv1.ClusterConfiguration)
113120
UpdateAPIServerInKubeadmConfigMap(apiServer bootstrapv1.APIServer) func(*bootstrapv1.ClusterConfiguration)
@@ -186,11 +193,40 @@ func (w *Workload) UpdateImageRepositoryInKubeadmConfigMap(imageRepository strin
186193
}
187194

188195
// UpdateFeatureGatesInKubeadmConfigMap updates the feature gates in the kubeadm config map.
189-
func (w *Workload) UpdateFeatureGatesInKubeadmConfigMap(featureGates map[string]bool) func(*bootstrapv1.ClusterConfiguration) {
196+
func (w *Workload) UpdateFeatureGatesInKubeadmConfigMap(kubeadmConfigSpec bootstrapv1.KubeadmConfigSpec, kubernetesVersion semver.Version) func(*bootstrapv1.ClusterConfiguration) {
190197
return func(c *bootstrapv1.ClusterConfiguration) {
198+
// We use DeepCopy here to avoid modifying the KCP object in the apiserver.
199+
kubeadmConfigSpec := kubeadmConfigSpec.DeepCopy()
200+
DefaultFeatureGates(kubeadmConfigSpec, kubernetesVersion)
201+
191202
// Even if featureGates is nil, reset it to ClusterConfiguration
192203
// to override any previously set feature gates.
193-
c.FeatureGates = featureGates
204+
c.FeatureGates = kubeadmConfigSpec.ClusterConfiguration.FeatureGates
205+
}
206+
}
207+
208+
const (
209+
// ControlPlaneKubeletLocalMode is a feature gate of kubeadm that ensures
210+
// kubelets only communicate with the local apiserver.
211+
ControlPlaneKubeletLocalMode = "ControlPlaneKubeletLocalMode"
212+
)
213+
214+
// DefaultFeatureGates defaults the feature gates field.
215+
func DefaultFeatureGates(kubeadmConfigSpec *bootstrapv1.KubeadmConfigSpec, kubernetesVersion semver.Version) {
216+
if kubernetesVersion.LT(minKubernetesVersionControlPlaneKubeletLocalMode) {
217+
return
218+
}
219+
220+
if kubeadmConfigSpec.ClusterConfiguration == nil {
221+
kubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{}
222+
}
223+
224+
if kubeadmConfigSpec.ClusterConfiguration.FeatureGates == nil {
225+
kubeadmConfigSpec.ClusterConfiguration.FeatureGates = map[string]bool{}
226+
}
227+
228+
if _, ok := kubeadmConfigSpec.ClusterConfiguration.FeatureGates[ControlPlaneKubeletLocalMode]; !ok {
229+
kubeadmConfigSpec.ClusterConfiguration.FeatureGates[ControlPlaneKubeletLocalMode] = true
194230
}
195231
}
196232

0 commit comments

Comments
 (0)