Skip to content

Commit

Permalink
Merge pull request kubernetes-sigs#10947 from sbueringer/pr-fix-kubeadm
Browse files Browse the repository at this point in the history
✨ KCP: default ControlPlaneKubeletLocalMode feature gate to true for >= 1.31.0
  • Loading branch information
k8s-ci-robot authored Jul 26, 2024
2 parents 9e9985d + 14433d5 commit 9c08773
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2351,7 +2351,7 @@ func createClusterWithControlPlane(namespace string) (*clusterv1.Cluster, *contr
},
},
Replicas: ptr.To[int32](int32(3)),
Version: "v1.16.6",
Version: "v1.31.0",
RolloutStrategy: &controlplanev1.RolloutStrategy{
Type: "RollingUpdate",
RollingUpdate: &controlplanev1.RollingUpdate{
Expand Down
19 changes: 19 additions & 0 deletions controlplane/kubeadm/internal/controllers/scale.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,22 @@ import (
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
"sigs.k8s.io/cluster-api/util/collections"
"sigs.k8s.io/cluster-api/util/conditions"
"sigs.k8s.io/cluster-api/util/version"
)

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

bootstrapSpec := controlPlane.InitialControlPlaneConfig()

// We intentionally only parse major/minor/patch so that the subsequent code
// also already applies to beta versions of new releases.
parsedVersionTolerant, err := version.ParseMajorMinorPatchTolerant(controlPlane.KCP.Spec.Version)
if err != nil {
return ctrl.Result{}, errors.Wrapf(err, "failed to parse kubernetes version %q", controlPlane.KCP.Spec.Version)
}
internal.DefaultFeatureGates(bootstrapSpec, parsedVersionTolerant)

fd, err := controlPlane.NextFailureDomainForScaleUp(ctx)
if err != nil {
return ctrl.Result{}, err
Expand All @@ -64,6 +74,15 @@ func (r *KubeadmControlPlaneReconciler) scaleUpControlPlane(ctx context.Context,

// Create the bootstrap configuration
bootstrapSpec := controlPlane.JoinControlPlaneConfig()

// We intentionally only parse major/minor/patch so that the subsequent code
// also already applies to beta versions of new releases.
parsedVersionTolerant, err := version.ParseMajorMinorPatchTolerant(controlPlane.KCP.Spec.Version)
if err != nil {
return ctrl.Result{}, errors.Wrapf(err, "failed to parse kubernetes version %q", controlPlane.KCP.Spec.Version)
}
internal.DefaultFeatureGates(bootstrapSpec, parsedVersionTolerant)

fd, err := controlPlane.NextFailureDomainForScaleUp(ctx)
if err != nil {
return ctrl.Result{}, err
Expand Down
10 changes: 10 additions & 0 deletions controlplane/kubeadm/internal/controllers/scale_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ func TestKubeadmControlPlaneReconciler_initializeControlPlane(t *testing.T) {
g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.Name).To(Equal(machineList.Items[0].Name))
g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.APIVersion).To(Equal(bootstrapv1.GroupVersion.String()))
g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.Kind).To(Equal("KubeadmConfig"))

kubeadmConfig := &bootstrapv1.KubeadmConfig{}
bootstrapRef := machineList.Items[0].Spec.Bootstrap.ConfigRef
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKey{Namespace: bootstrapRef.Namespace, Name: bootstrapRef.Name}, kubeadmConfig)).To(Succeed())
g.Expect(kubeadmConfig.Spec.ClusterConfiguration.FeatureGates).To(BeComparableTo(map[string]bool{internal.ControlPlaneKubeletLocalMode: true}))
}

func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
Expand Down Expand Up @@ -165,6 +170,11 @@ func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
// Note: expected length is 1 because only the newly created machine is on API server. Other machines are
// in-memory only during the test.
g.Expect(controlPlaneMachines.Items).To(HaveLen(1))

kubeadmConfig := &bootstrapv1.KubeadmConfig{}
bootstrapRef := controlPlaneMachines.Items[0].Spec.Bootstrap.ConfigRef
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKey{Namespace: bootstrapRef.Namespace, Name: bootstrapRef.Name}, kubeadmConfig)).To(Succeed())
g.Expect(kubeadmConfig.Spec.ClusterConfiguration.FeatureGates).To(BeComparableTo(map[string]bool{internal.ControlPlaneKubeletLocalMode: true}))
})
t.Run("does not create a control plane Machine if preflight checks fail", func(t *testing.T) {
setup := func(t *testing.T, g *WithT) *corev1.Namespace {
Expand Down
2 changes: 1 addition & 1 deletion controlplane/kubeadm/internal/controllers/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (r *KubeadmControlPlaneReconciler) upgradeControlPlane(

kubeadmCMMutators = append(kubeadmCMMutators,
workloadCluster.UpdateImageRepositoryInKubeadmConfigMap(imageRepository),
workloadCluster.UpdateFeatureGatesInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.FeatureGates),
workloadCluster.UpdateFeatureGatesInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec, parsedVersionTolerant),
workloadCluster.UpdateAPIServerInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer),
workloadCluster.UpdateControllerManagerInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.ControllerManager),
workloadCluster.UpdateSchedulerInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.Scheduler))
Expand Down
42 changes: 39 additions & 3 deletions controlplane/kubeadm/internal/workload_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ var (
// NOTE: The following assumes that kubeadm version equals to Kubernetes version.
minVerUnversionedKubeletConfig = semver.MustParse("1.24.0")

// minKubernetesVersionControlPlaneKubeletLocalMode is the min version from which
// we will enable the ControlPlaneKubeletLocalMode kubeadm feature gate.
// Note: We have to do this with Kubernetes 1.31. Because with that version we encountered
// a case where it's not okay anymore to ignore the Kubernetes version skew (kubelet 1.31 uses
// the spec.clusterIP field selector that is only implemented in kube-apiserver >= 1.31.0).
minKubernetesVersionControlPlaneKubeletLocalMode = semver.MustParse("1.31.0")

// ErrControlPlaneMinNodes signals that a cluster doesn't meet the minimum required nodes
// to remove an etcd member.
ErrControlPlaneMinNodes = errors.New("cluster has fewer than 2 control plane nodes; removing an etcd member is not supported")
Expand All @@ -107,7 +114,7 @@ type WorkloadCluster interface {
ReconcileKubeletRBACRole(ctx context.Context, version semver.Version) error
UpdateKubernetesVersionInKubeadmConfigMap(version semver.Version) func(*bootstrapv1.ClusterConfiguration)
UpdateImageRepositoryInKubeadmConfigMap(imageRepository string) func(*bootstrapv1.ClusterConfiguration)
UpdateFeatureGatesInKubeadmConfigMap(featureGates map[string]bool) func(*bootstrapv1.ClusterConfiguration)
UpdateFeatureGatesInKubeadmConfigMap(kubeadmConfigSpec bootstrapv1.KubeadmConfigSpec, kubernetesVersion semver.Version) func(*bootstrapv1.ClusterConfiguration)
UpdateEtcdLocalInKubeadmConfigMap(localEtcd *bootstrapv1.LocalEtcd) func(*bootstrapv1.ClusterConfiguration)
UpdateEtcdExternalInKubeadmConfigMap(externalEtcd *bootstrapv1.ExternalEtcd) func(*bootstrapv1.ClusterConfiguration)
UpdateAPIServerInKubeadmConfigMap(apiServer bootstrapv1.APIServer) func(*bootstrapv1.ClusterConfiguration)
Expand Down Expand Up @@ -186,11 +193,40 @@ func (w *Workload) UpdateImageRepositoryInKubeadmConfigMap(imageRepository strin
}

// UpdateFeatureGatesInKubeadmConfigMap updates the feature gates in the kubeadm config map.
func (w *Workload) UpdateFeatureGatesInKubeadmConfigMap(featureGates map[string]bool) func(*bootstrapv1.ClusterConfiguration) {
func (w *Workload) UpdateFeatureGatesInKubeadmConfigMap(kubeadmConfigSpec bootstrapv1.KubeadmConfigSpec, kubernetesVersion semver.Version) func(*bootstrapv1.ClusterConfiguration) {
return func(c *bootstrapv1.ClusterConfiguration) {
// We use DeepCopy here to avoid modifying the KCP object in the apiserver.
kubeadmConfigSpec := kubeadmConfigSpec.DeepCopy()
DefaultFeatureGates(kubeadmConfigSpec, kubernetesVersion)

// Even if featureGates is nil, reset it to ClusterConfiguration
// to override any previously set feature gates.
c.FeatureGates = featureGates
c.FeatureGates = kubeadmConfigSpec.ClusterConfiguration.FeatureGates
}
}

const (
// ControlPlaneKubeletLocalMode is a feature gate of kubeadm that ensures
// kubelets only communicate with the local apiserver.
ControlPlaneKubeletLocalMode = "ControlPlaneKubeletLocalMode"
)

// DefaultFeatureGates defaults the feature gates field.
func DefaultFeatureGates(kubeadmConfigSpec *bootstrapv1.KubeadmConfigSpec, kubernetesVersion semver.Version) {
if kubernetesVersion.LT(minKubernetesVersionControlPlaneKubeletLocalMode) {
return
}

if kubeadmConfigSpec.ClusterConfiguration == nil {
kubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{}
}

if kubeadmConfigSpec.ClusterConfiguration.FeatureGates == nil {
kubeadmConfigSpec.ClusterConfiguration.FeatureGates = map[string]bool{}
}

if _, ok := kubeadmConfigSpec.ClusterConfiguration.FeatureGates[ControlPlaneKubeletLocalMode]; !ok {
kubeadmConfigSpec.ClusterConfiguration.FeatureGates[ControlPlaneKubeletLocalMode] = true
}
}

Expand Down
Loading

0 comments on commit 9c08773

Please sign in to comment.