Skip to content

Commit 38467d2

Browse files
authored
Merge pull request #6176 from fabriziopandini/handle-kubeadm1.24-kubelet-ConfigMap-name-change
🌱 handle kubeadm 1.24 kubelet ConfigMap name change
2 parents 61e7dcc + 9705e43 commit 38467d2

File tree

4 files changed

+178
-36
lines changed

4 files changed

+178
-36
lines changed

controlplane/kubeadm/internal/workload_cluster.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ var (
7575
// NOTE: The following assumes that kubeadm version equals to Kubernetes version.
7676
minVerKubeletSystemdDriver = semver.MustParse("1.21.0")
7777

78+
// Starting from v1.24.0 kubeadm uses "kubelet-config" a ConfigMap name for KubeletConfiguration,
79+
// Dropping the X-Y suffix.
80+
//
81+
// NOTE: The following assumes that kubeadm version equals to Kubernetes version.
82+
minVerUnversionedKubeletConfig = semver.MustParse("1.24.0")
83+
7884
// ErrControlPlaneMinNodes signals that a cluster doesn't meet the minimum required nodes
7985
// to remove an etcd member.
8086
ErrControlPlaneMinNodes = errors.New("cluster has fewer than 2 control plane nodes; removing an etcd member is not supported")
@@ -179,7 +185,7 @@ func (w *Workload) UpdateKubernetesVersionInKubeadmConfigMap(ctx context.Context
179185
// This is a necessary process for upgrades.
180186
func (w *Workload) UpdateKubeletConfigMap(ctx context.Context, version semver.Version) error {
181187
// Check if the desired configmap already exists
182-
desiredKubeletConfigMapName := fmt.Sprintf("kubelet-config-%d.%d", version.Major, version.Minor)
188+
desiredKubeletConfigMapName := generateKubeletConfigName(version)
183189
configMapKey := ctrlclient.ObjectKey{Name: desiredKubeletConfigMapName, Namespace: metav1.NamespaceSystem}
184190
_, err := w.getConfigMap(ctx, configMapKey)
185191
if err == nil {
@@ -190,7 +196,14 @@ func (w *Workload) UpdateKubeletConfigMap(ctx context.Context, version semver.Ve
190196
return errors.Wrapf(err, "error determining if kubelet configmap %s exists", desiredKubeletConfigMapName)
191197
}
192198

193-
previousMinorVersionKubeletConfigMapName := fmt.Sprintf("kubelet-config-%d.%d", version.Major, version.Minor-1)
199+
previousMinorVersionKubeletConfigMapName := generateKubeletConfigName(semver.Version{Major: version.Major, Minor: version.Minor - 1})
200+
201+
// If desired and previous ConfigMap name are the same it means we already completed the transition
202+
// to the unified KubeletConfigMap name in the previous upgrade; no additional operations are required.
203+
if desiredKubeletConfigMapName == previousMinorVersionKubeletConfigMapName {
204+
return nil
205+
}
206+
194207
configMapKey = ctrlclient.ObjectKey{Name: previousMinorVersionKubeletConfigMapName, Namespace: metav1.NamespaceSystem}
195208
// Returns a copy
196209
cm, err := w.getConfigMap(ctx, configMapKey)

controlplane/kubeadm/internal/workload_cluster_rbac.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ const (
4141
// KubeletConfigMapRolePrefix defines base kubelet configuration ConfigMap role prefix.
4242
KubeletConfigMapRolePrefix = "kubeadm:"
4343

44-
// KubeletConfigMapName defines base kubelet configuration ConfigMap name.
44+
// KubeletConfigMapName defines base kubelet configuration ConfigMap name for kubeadm < 1.24.
4545
KubeletConfigMapName = "kubelet-config-%d.%d"
46+
47+
// UnversionedKubeletConfigMapName defines base kubelet configuration ConfigMap for kubeadm >= 1.24.
48+
UnversionedKubeletConfigMapName = "kubelet-config"
4649
)
4750

4851
// EnsureResource creates a resoutce if the target resource doesn't exist. If the resource exists already, this function will ignore the resource instead.
@@ -101,6 +104,10 @@ func (w *Workload) AllowBootstrapTokensToGetNodes(ctx context.Context) error {
101104
}
102105

103106
func generateKubeletConfigName(version semver.Version) string {
107+
majorMinor := semver.Version{Major: version.Major, Minor: version.Minor}
108+
if majorMinor.GTE(minVerUnversionedKubeletConfig) {
109+
return UnversionedKubeletConfigMapName
110+
}
104111
return fmt.Sprintf(KubeletConfigMapName, version.Major, version.Minor)
105112
}
106113

controlplane/kubeadm/internal/workload_cluster_rbac_test.go

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,86 @@ import (
2424
. "github.com/onsi/gomega"
2525
rbacv1 "k8s.io/api/rbac/v1"
2626
apierrors "k8s.io/apimachinery/pkg/api/errors"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
"k8s.io/apimachinery/pkg/runtime/schema"
2829
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
30+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2931
)
3032

3133
func TestCluster_ReconcileKubeletRBACBinding_NoError(t *testing.T) {
34+
type wantRBAC struct {
35+
role ctrlclient.ObjectKey
36+
roleBinding ctrlclient.ObjectKey
37+
}
3238
tests := []struct {
33-
name string
34-
client ctrlclient.Client
39+
name string
40+
client ctrlclient.Client
41+
version semver.Version
42+
want *wantRBAC
3543
}{
3644
{
37-
name: "role binding and role already exist",
38-
client: &fakeClient{
39-
get: map[string]interface{}{
40-
"kube-system/kubeadm:kubelet-config-1.12": &rbacv1.RoleBinding{},
41-
"kube-system/kubeadm:kubelet-config-1.13": &rbacv1.Role{},
42-
},
45+
name: "creates role and role binding for Kubernetes/kubeadm < v1.24",
46+
client: fake.NewClientBuilder().Build(),
47+
version: semver.MustParse("1.23.3"),
48+
want: &wantRBAC{
49+
role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"},
50+
roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"},
4351
},
4452
},
4553
{
46-
name: "role binding and role don't exist",
47-
client: &fakeClient{},
54+
name: "tolerates existing role binding for Kubernetes/kubeadm < v1.24",
55+
client: fake.NewClientBuilder().WithObjects(
56+
&rbacv1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"}, RoleRef: rbacv1.RoleRef{
57+
Name: "kubeadm:kubelet-config-1.23",
58+
}},
59+
&rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"}, Rules: []rbacv1.PolicyRule{{
60+
Verbs: []string{"get"},
61+
APIGroups: []string{""},
62+
Resources: []string{"configmaps"},
63+
ResourceNames: []string{"kubelet-config-1.23"},
64+
}}},
65+
).Build(),
66+
version: semver.MustParse("1.23.3"),
67+
want: &wantRBAC{
68+
role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"},
69+
roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"},
70+
},
4871
},
4972
{
50-
name: "create returns an already exists error",
51-
client: &fakeClient{
52-
createErr: apierrors.NewAlreadyExists(schema.GroupResource{}, ""),
73+
name: "creates role and role binding for Kubernetes/kubeadm >= v1.24",
74+
client: fake.NewClientBuilder().Build(),
75+
version: semver.MustParse("1.24.0"),
76+
want: &wantRBAC{
77+
role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"},
78+
roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"},
79+
},
80+
},
81+
{
82+
name: "creates role and role binding for Kubernetes/kubeadm >= v1.24 ignoring pre-release and build tags",
83+
client: fake.NewClientBuilder().Build(),
84+
version: semver.MustParse("1.24.0-alpha.1+xyz.1"),
85+
want: &wantRBAC{
86+
role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"},
87+
roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"},
88+
},
89+
},
90+
{
91+
name: "tolerates existing role binding for Kubernetes/kubeadm >= v1.24",
92+
client: fake.NewClientBuilder().WithObjects(
93+
&rbacv1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, RoleRef: rbacv1.RoleRef{
94+
Name: "kubeadm:kubelet-config",
95+
}},
96+
&rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, Rules: []rbacv1.PolicyRule{{
97+
Verbs: []string{"get"},
98+
APIGroups: []string{""},
99+
Resources: []string{"configmaps"},
100+
ResourceNames: []string{"kubelet-config"},
101+
}}},
102+
).Build(),
103+
version: semver.MustParse("1.24.1"),
104+
want: &wantRBAC{
105+
role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"},
106+
roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"},
53107
},
54108
},
55109
}
@@ -61,8 +115,27 @@ func TestCluster_ReconcileKubeletRBACBinding_NoError(t *testing.T) {
61115
c := &Workload{
62116
Client: tt.client,
63117
}
64-
g.Expect(c.ReconcileKubeletRBACBinding(ctx, semver.MustParse("1.12.3"))).To(Succeed())
65-
g.Expect(c.ReconcileKubeletRBACRole(ctx, semver.MustParse("1.13.3"))).To(Succeed())
118+
g.Expect(c.ReconcileKubeletRBACBinding(ctx, tt.version)).To(Succeed())
119+
g.Expect(c.ReconcileKubeletRBACRole(ctx, tt.version)).To(Succeed())
120+
if tt.want != nil {
121+
r := &rbacv1.Role{}
122+
// Role exists
123+
g.Expect(tt.client.Get(ctx, tt.want.role, r)).To(Succeed())
124+
// Role ensure grants for the KubeletConfig config map
125+
g.Expect(r.Rules).To(Equal([]rbacv1.PolicyRule{
126+
{
127+
Verbs: []string{"get"},
128+
APIGroups: []string{""},
129+
Resources: []string{"configmaps"},
130+
ResourceNames: []string{generateKubeletConfigName(tt.version)},
131+
},
132+
}))
133+
// RoleBinding exists
134+
b := &rbacv1.RoleBinding{}
135+
// RoleBinding refers to the role
136+
g.Expect(tt.client.Get(ctx, tt.want.roleBinding, b)).To(Succeed())
137+
g.Expect(b.RoleRef.Name).To(Equal(tt.want.role.Name))
138+
}
66139
})
67140
}
68141
}

controlplane/kubeadm/internal/workload_cluster_test.go

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package internal
1919
import (
2020
"context"
2121
"errors"
22-
"fmt"
2322
"testing"
2423

2524
"github.com/blang/semver"
@@ -365,9 +364,10 @@ func TestUpdateKubeletConfigMap(t *testing.T) {
365364
objs []client.Object
366365
expectErr bool
367366
expectCgroupDriver string
367+
expectNewConfigMap bool
368368
}{
369369
{
370-
name: "create new config map",
370+
name: "create new config map for 1.19 --> 1.20 (anything < 1.24); config map for previous version is copied",
371371
version: semver.Version{Major: 1, Minor: 20},
372372
objs: []client.Object{&corev1.ConfigMap{
373373
ObjectMeta: metav1.ObjectMeta{
@@ -379,14 +379,51 @@ func TestUpdateKubeletConfigMap(t *testing.T) {
379379
kubeletConfigKey: yaml.Raw(`
380380
apiVersion: kubelet.config.k8s.io/v1beta1
381381
kind: KubeletConfiguration
382+
foo: bar
382383
`),
383384
},
384385
}},
385-
expectErr: false,
386-
expectCgroupDriver: "",
386+
expectNewConfigMap: true,
387387
},
388388
{
389-
name: "KubeletConfig 1.21 gets the cgroupDriver set if empty",
389+
name: "create new config map 1.23 --> 1.24; config map for previous version is copied",
390+
version: semver.Version{Major: 1, Minor: 24},
391+
objs: []client.Object{&corev1.ConfigMap{
392+
ObjectMeta: metav1.ObjectMeta{
393+
Name: "kubelet-config-1.23",
394+
Namespace: metav1.NamespaceSystem,
395+
ResourceVersion: "some-resource-version",
396+
},
397+
Data: map[string]string{
398+
kubeletConfigKey: yaml.Raw(`
399+
apiVersion: kubelet.config.k8s.io/v1beta1
400+
kind: KubeletConfiguration
401+
foo: bar
402+
`),
403+
},
404+
}},
405+
expectNewConfigMap: true,
406+
},
407+
{
408+
name: "create new config map >=1.24 --> next; no op",
409+
version: semver.Version{Major: 1, Minor: 25},
410+
objs: []client.Object{&corev1.ConfigMap{
411+
ObjectMeta: metav1.ObjectMeta{
412+
Name: "kubelet-config",
413+
Namespace: metav1.NamespaceSystem,
414+
ResourceVersion: "some-resource-version",
415+
},
416+
Data: map[string]string{
417+
kubeletConfigKey: yaml.Raw(`
418+
apiVersion: kubelet.config.k8s.io/v1beta1
419+
kind: KubeletConfiguration
420+
foo: bar
421+
`),
422+
},
423+
}},
424+
},
425+
{
426+
name: "1.20 --> 1.21 sets the cgroupDriver if empty",
390427
version: semver.Version{Major: 1, Minor: 21},
391428
objs: []client.Object{&corev1.ConfigMap{
392429
ObjectMeta: metav1.ObjectMeta{
@@ -397,14 +434,16 @@ func TestUpdateKubeletConfigMap(t *testing.T) {
397434
Data: map[string]string{
398435
kubeletConfigKey: yaml.Raw(`
399436
apiVersion: kubelet.config.k8s.io/v1beta1
400-
kind: KubeletConfiguration`),
437+
kind: KubeletConfiguration
438+
foo: bar
439+
`),
401440
},
402441
}},
403-
expectErr: false,
404442
expectCgroupDriver: "systemd",
443+
expectNewConfigMap: true,
405444
},
406445
{
407-
name: "KubeletConfig 1.21 preserves cgroupDriver if already set",
446+
name: "1.20 --> 1.21 preserves cgroupDriver if already set",
408447
version: semver.Version{Major: 1, Minor: 21},
409448
objs: []client.Object{&corev1.ConfigMap{
410449
ObjectMeta: metav1.ObjectMeta{
@@ -416,18 +455,18 @@ func TestUpdateKubeletConfigMap(t *testing.T) {
416455
kubeletConfigKey: yaml.Raw(`
417456
apiVersion: kubelet.config.k8s.io/v1beta1
418457
kind: KubeletConfiguration
419-
cgroupDriver: foo`),
458+
cgroupDriver: cgroupfs
459+
foo: bar
460+
`),
420461
},
421462
}},
422-
expectErr: false,
423-
expectCgroupDriver: "foo",
463+
expectCgroupDriver: "cgroupfs",
464+
expectNewConfigMap: true,
424465
},
425466
{
426-
name: "returns error if cannot find previous config map",
427-
version: semver.Version{Major: 1, Minor: 21},
428-
objs: nil,
429-
expectErr: true,
430-
expectCgroupDriver: "",
467+
name: "returns error if cannot find previous config map",
468+
version: semver.Version{Major: 1, Minor: 21},
469+
expectErr: true,
431470
},
432471
}
433472

@@ -444,14 +483,24 @@ func TestUpdateKubeletConfigMap(t *testing.T) {
444483
return
445484
}
446485
g.Expect(err).ToNot(HaveOccurred())
486+
487+
// Check if the resulting ConfigMap exists
447488
var actualConfig corev1.ConfigMap
448489
g.Expect(w.Client.Get(
449490
ctx,
450-
client.ObjectKey{Name: fmt.Sprintf("kubelet-config-%d.%d", tt.version.Major, tt.version.Minor), Namespace: metav1.NamespaceSystem},
491+
client.ObjectKey{Name: generateKubeletConfigName(tt.version), Namespace: metav1.NamespaceSystem},
451492
&actualConfig,
452493
)).To(Succeed())
453-
g.Expect(actualConfig.ResourceVersion).ToNot(Equal("some-resource-version"))
494+
// Check other values are carried over for previous config map
495+
g.Expect(actualConfig.Data[kubeletConfigKey]).To(ContainSubstring("foo"))
496+
// Check the cgroupvalue has the expected value
454497
g.Expect(actualConfig.Data[kubeletConfigKey]).To(ContainSubstring(tt.expectCgroupDriver))
498+
// check if the config map is new
499+
if tt.expectNewConfigMap {
500+
g.Expect(actualConfig.ResourceVersion).ToNot(Equal("some-resource-version"))
501+
} else {
502+
g.Expect(actualConfig.ResourceVersion).To(Equal("some-resource-version"))
503+
}
455504
})
456505
}
457506
}

0 commit comments

Comments
 (0)