Skip to content

Commit e96652e

Browse files
authored
Merge pull request kubernetes#104015 from neolit123/1.23-use-dynamic-versions
kubeadm: dynamically populate the current/minimum k8s versions
2 parents 4f50f99 + e3538ed commit e96652e

File tree

9 files changed

+224
-19
lines changed

9 files changed

+224
-19
lines changed

cmd/kubeadm/app/cmd/config_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ func TestConfigImagesListRunWithoutPath(t *testing.T) {
208208

209209
func TestConfigImagesListOutput(t *testing.T) {
210210

211-
etcdVersion, ok := constants.SupportedEtcdVersion[uint8(dummyKubernetesVersion.Minor())]
212-
if !ok {
211+
etcdVersion, _, err := constants.EtcdSupportedVersion(constants.SupportedEtcdVersion, dummyKubernetesVersionStr)
212+
if err != nil {
213213
t.Fatalf("cannot determine etcd version for Kubernetes version %s", dummyKubernetesVersionStr)
214214
}
215215
versionMapping := struct {
@@ -218,7 +218,7 @@ func TestConfigImagesListOutput(t *testing.T) {
218218
PauseVersion string
219219
CoreDNSVersion string
220220
}{
221-
EtcdVersion: etcdVersion,
221+
EtcdVersion: etcdVersion.String(),
222222
KubeVersion: "v" + dummyKubernetesVersionStr,
223223
PauseVersion: constants.PauseVersion,
224224
CoreDNSVersion: constants.CoreDNSVersion,

cmd/kubeadm/app/constants/constants.go

+49-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ import (
2929
v1 "k8s.io/api/core/v1"
3030
"k8s.io/apimachinery/pkg/util/version"
3131
"k8s.io/apimachinery/pkg/util/wait"
32+
apimachineryversion "k8s.io/apimachinery/pkg/version"
3233
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
34+
componentversion "k8s.io/component-base/version"
3335
utilnet "k8s.io/utils/net"
3436

3537
"github.com/pkg/errors"
@@ -448,13 +450,13 @@ var (
448450
ControlPlaneComponents = []string{KubeAPIServer, KubeControllerManager, KubeScheduler}
449451

450452
// MinimumControlPlaneVersion specifies the minimum control plane version kubeadm can deploy
451-
MinimumControlPlaneVersion = version.MustParseSemantic("v1.21.0")
453+
MinimumControlPlaneVersion = getSkewedKubernetesVersion(-1)
452454

453455
// MinimumKubeletVersion specifies the minimum version of kubelet which kubeadm supports
454-
MinimumKubeletVersion = version.MustParseSemantic("v1.21.0")
456+
MinimumKubeletVersion = getSkewedKubernetesVersion(-1)
455457

456458
// CurrentKubernetesVersion specifies current Kubernetes version supported by kubeadm
457-
CurrentKubernetesVersion = version.MustParseSemantic("v1.22.0")
459+
CurrentKubernetesVersion = getSkewedKubernetesVersion(0)
458460

459461
// SupportedEtcdVersion lists officially supported etcd versions with corresponding Kubernetes releases
460462
SupportedEtcdVersion = map[uint8]string{
@@ -483,8 +485,52 @@ var (
483485
Factor: 1.0,
484486
Jitter: 0.1,
485487
}
488+
489+
// defaultKubernetesVersionForTests is the default version used for unit tests.
490+
// The MINOR should be at least 3 as some tests subtract 3 from the MINOR version.
491+
defaultKubernetesVersionForTests = version.MustParseSemantic("v1.3.0")
486492
)
487493

494+
// isRunningInTest can be used to determine if the code in this file is being run in a test.
495+
func isRunningInTest() bool {
496+
return strings.HasSuffix(os.Args[0], ".test")
497+
}
498+
499+
// getSkewedKubernetesVersion returns the current MAJOR.(MINOR+n).0 Kubernetes version with a skew of 'n'
500+
// It uses the kubeadm version provided by the 'component-base/version' package. This version must be populated
501+
// by passing linker flags during the kubeadm build process. If the version is empty, assume that kubeadm
502+
// was either build incorrectly or this code is running in unit tests.
503+
func getSkewedKubernetesVersion(n int) *version.Version {
504+
versionInfo := componentversion.Get()
505+
ver := getSkewedKubernetesVersionImpl(&versionInfo, n, isRunningInTest)
506+
if ver == nil {
507+
panic("kubeadm is not build properly using 'make ...': missing component version information")
508+
}
509+
return ver
510+
}
511+
512+
func getSkewedKubernetesVersionImpl(versionInfo *apimachineryversion.Info, n int, isRunningInTestFunc func() bool) *version.Version {
513+
// TODO: update if the kubeadm version gets decoupled from the Kubernetes version.
514+
// This would require keeping track of the supported skew in a table.
515+
// More changes would be required if the kubelet version one day decouples from that of Kubernetes.
516+
var ver *version.Version
517+
if len(versionInfo.Major) == 0 {
518+
if isRunningInTestFunc() {
519+
ver = defaultKubernetesVersionForTests // An arbitrary version for testing purposes
520+
} else {
521+
// If this is not running in tests assume that the kubeadm binary is not build properly
522+
return nil
523+
}
524+
} else {
525+
ver = version.MustParseSemantic(versionInfo.GitVersion)
526+
}
527+
// Append the MINOR version skew.
528+
// TODO: handle the case of Kubernetes moving to v2.0 or having MAJOR version updates in the future.
529+
// This would require keeping track (in a table) of the last MINOR for a particular MAJOR.
530+
minor := uint(int(ver.Minor()) + n)
531+
return version.MustParseSemantic(fmt.Sprintf("v%d.%d.0", ver.Major(), minor))
532+
}
533+
488534
// EtcdSupportedVersion returns officially supported version of etcd for a specific Kubernetes release
489535
// If passed version is not in the given list, the function returns the nearest version with a warning
490536
func EtcdSupportedVersion(supportedEtcdVersion map[uint8]string, versionString string) (etcdVersion *version.Version, warning, err error) {

cmd/kubeadm/app/constants/constants_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"testing"
2222

2323
"k8s.io/apimachinery/pkg/util/version"
24+
apimachineryversion "k8s.io/apimachinery/pkg/version"
2425
)
2526

2627
func TestGetStaticPodDirectory(t *testing.T) {
@@ -237,3 +238,61 @@ func TestGetKubernetesServiceCIDR(t *testing.T) {
237238
})
238239
}
239240
}
241+
242+
func TestGetSkewedKubernetesVersionImpl(t *testing.T) {
243+
tests := []struct {
244+
name string
245+
versionInfo *apimachineryversion.Info
246+
n int
247+
isRunningInTestFunc func() bool
248+
expectedResult *version.Version
249+
}{
250+
{
251+
name: "invalid versionInfo; running in test",
252+
versionInfo: &apimachineryversion.Info{},
253+
expectedResult: defaultKubernetesVersionForTests,
254+
},
255+
{
256+
name: "invalid versionInfo; not running in test",
257+
versionInfo: &apimachineryversion.Info{},
258+
isRunningInTestFunc: func() bool { return false },
259+
expectedResult: nil,
260+
},
261+
{
262+
name: "valid skew of -1",
263+
versionInfo: &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0"},
264+
n: -1,
265+
expectedResult: version.MustParseSemantic("v1.22.0"),
266+
},
267+
{
268+
name: "valid skew of 0",
269+
versionInfo: &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0"},
270+
n: 0,
271+
expectedResult: version.MustParseSemantic("v1.23.0"),
272+
},
273+
{
274+
name: "valid skew of +1",
275+
versionInfo: &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0"},
276+
n: 1,
277+
expectedResult: version.MustParseSemantic("v1.24.0"),
278+
},
279+
}
280+
281+
for _, tc := range tests {
282+
t.Run(tc.name, func(t *testing.T) {
283+
if tc.isRunningInTestFunc == nil {
284+
tc.isRunningInTestFunc = func() bool { return true }
285+
}
286+
result := getSkewedKubernetesVersionImpl(tc.versionInfo, tc.n, tc.isRunningInTestFunc)
287+
if (tc.expectedResult == nil) != (result == nil) {
288+
t.Errorf("expected result: %v, got: %v", tc.expectedResult, result)
289+
}
290+
if result == nil {
291+
return
292+
}
293+
if cmp, _ := result.Compare(tc.expectedResult.String()); cmp != 0 {
294+
t.Errorf("expected result: %v, got %v", tc.expectedResult, result)
295+
}
296+
})
297+
}
298+
}

cmd/kubeadm/app/phases/upgrade/compute_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ spec:
8585
image: k8s.gcr.io/etcd:` + fakeCurrentEtcdVersion
8686

8787
func getEtcdVersion(v *versionutil.Version) string {
88-
return constants.SupportedEtcdVersion[uint8(v.Minor())]
88+
etcdVer, _, _ := constants.EtcdSupportedVersion(constants.SupportedEtcdVersion, v.String())
89+
return etcdVer.String()
8990
}
9091

9192
const fakeCurrentCoreDNSVersion = "1.0.6"

cmd/kubeadm/app/phases/upgrade/policy_test.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func TestEnforceVersionPolicies(t *testing.T) {
7676
kubeletVersion: "v1.12.3",
7777
kubeadmVersion: "v1.12.3",
7878
},
79-
newK8sVersion: "v1.11.10",
79+
newK8sVersion: "v1.10.10",
8080
expectedMandatoryErrs: 1, // version must be higher than v1.12.0
8181
expectedSkippableErrs: 1, // can't upgrade old k8s with newer kubeadm
8282
},
@@ -85,9 +85,9 @@ func TestEnforceVersionPolicies(t *testing.T) {
8585
vg: &fakeVersionGetter{
8686
clusterVersion: "v1.11.3",
8787
kubeletVersion: "v1.11.3",
88-
kubeadmVersion: constants.CurrentKubernetesVersion.String(),
88+
kubeadmVersion: "v1.13.0",
8989
},
90-
newK8sVersion: constants.CurrentKubernetesVersion.String(),
90+
newK8sVersion: "v1.13.0",
9191
expectedMandatoryErrs: 1, // can't upgrade two minor versions
9292
expectedSkippableErrs: 1, // kubelet <-> apiserver skew too large
9393
},
@@ -124,11 +124,11 @@ func TestEnforceVersionPolicies(t *testing.T) {
124124
{
125125
name: "the maximum skew between the cluster version and the kubelet versions should be one minor version. This may be forced through though.",
126126
vg: &fakeVersionGetter{
127-
clusterVersion: constants.MinimumControlPlaneVersion.WithPatch(3).String(),
128-
kubeletVersion: "v1.12.8",
129-
kubeadmVersion: constants.CurrentKubernetesVersion.String(),
127+
clusterVersion: "v1.12.0",
128+
kubeletVersion: "v1.10.8",
129+
kubeadmVersion: "v1.12.0",
130130
},
131-
newK8sVersion: constants.CurrentKubernetesVersion.String(),
131+
newK8sVersion: "v1.12.0",
132132
expectedSkippableErrs: 1,
133133
},
134134
{

cmd/kubeadm/app/preflight/checks_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ func TestKubeletVersionCheck(t *testing.T) {
793793
expectWarnings bool
794794
}{
795795
{"v" + constants.CurrentKubernetesVersion.WithPatch(2).String(), "", false, false}, // check minimally supported version when there is no information about control plane
796-
{"v1.11.3", "v1.11.8", true, false}, // too old kubelet (older than kubeadmconstants.MinimumKubeletVersion), should fail.
796+
{"v1.1.0", "v1.11.8", true, false}, // too old kubelet, should fail.
797797
{"v" + constants.MinimumKubeletVersion.String(), constants.MinimumControlPlaneVersion.WithPatch(5).String(), false, false}, // kubelet within same major.minor as control plane
798798
{"v" + constants.MinimumKubeletVersion.WithPatch(5).String(), constants.MinimumControlPlaneVersion.WithPatch(1).String(), false, false}, // kubelet is newer, but still within same major.minor as control plane
799799
{"v" + constants.MinimumKubeletVersion.String(), constants.CurrentKubernetesVersion.WithPatch(1).String(), false, false}, // kubelet is lower than control plane, but newer than minimally supported

cmd/kubeadm/app/util/config/common.go

+36-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import (
3232
"k8s.io/apimachinery/pkg/runtime/schema"
3333
netutil "k8s.io/apimachinery/pkg/util/net"
3434
"k8s.io/apimachinery/pkg/util/version"
35+
apimachineryversion "k8s.io/apimachinery/pkg/version"
36+
componentversion "k8s.io/component-base/version"
3537
"k8s.io/klog/v2"
3638

3739
"github.com/pkg/errors"
@@ -102,8 +104,23 @@ func NormalizeKubernetesVersion(cfg *kubeadmapi.ClusterConfiguration) error {
102104
if err != nil {
103105
return errors.Wrapf(err, "couldn't parse Kubernetes version %q", cfg.KubernetesVersion)
104106
}
105-
if k8sVersion.LessThan(constants.MinimumControlPlaneVersion) {
106-
return errors.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s", constants.MinimumControlPlaneVersion.String(), cfg.KubernetesVersion)
107+
108+
// During the k8s release process, a kubeadm version in the main branch could be 1.23.0-pre,
109+
// while the 1.22.0 version is not released yet. The MinimumControlPlaneVersion validation
110+
// in such a case will not pass, since the value of MinimumControlPlaneVersion would be
111+
// calculated as kubeadm version - 1 (1.22) and k8sVersion would still be at 1.21.x
112+
// (fetched from the 'stable' marker). Handle this case by only showing a warning.
113+
mcpVersion := constants.MinimumControlPlaneVersion
114+
versionInfo := componentversion.Get()
115+
if isKubeadmPrereleaseVersion(&versionInfo, k8sVersion, mcpVersion) {
116+
klog.V(1).Infof("WARNING: tolerating control plane version %s, assuming that k8s version %s is not released yet",
117+
cfg.KubernetesVersion, mcpVersion)
118+
return nil
119+
}
120+
// If not a pre-release version, handle the validation normally.
121+
if k8sVersion.LessThan(mcpVersion) {
122+
return errors.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s",
123+
mcpVersion, cfg.KubernetesVersion)
107124
}
108125
return nil
109126
}
@@ -204,3 +221,20 @@ func MigrateOldConfig(oldConfig []byte) ([]byte, error) {
204221

205222
return bytes.Join(newConfig, []byte(constants.YAMLDocumentSeparator)), nil
206223
}
224+
225+
// isKubeadmPrereleaseVersion returns true if the kubeadm version is a pre-release version and
226+
// the minimum control plane version is N+2 MINOR version of the given k8sVersion.
227+
func isKubeadmPrereleaseVersion(versionInfo *apimachineryversion.Info, k8sVersion, mcpVersion *version.Version) bool {
228+
if len(versionInfo.Major) != 0 { // Make sure the component version is populated
229+
kubeadmVersion := version.MustParseSemantic(versionInfo.String())
230+
if len(kubeadmVersion.PreRelease()) != 0 { // Only handle this if the kubeadm binary is a pre-release
231+
// After incrementing the k8s MINOR version by one, if this version is equal or greater than the
232+
// MCP version, return true.
233+
v := k8sVersion.WithMinor(k8sVersion.Minor() + 1)
234+
if comp, _ := v.Compare(mcpVersion.String()); comp != -1 {
235+
return true
236+
}
237+
}
238+
}
239+
return false
240+
}

cmd/kubeadm/app/util/config/common_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
2727

2828
"k8s.io/apimachinery/pkg/runtime/schema"
29+
"k8s.io/apimachinery/pkg/util/version"
30+
apimachineryversion "k8s.io/apimachinery/pkg/version"
2931

3032
"github.com/lithammer/dedent"
3133
)
@@ -397,3 +399,55 @@ func TestMigrateOldConfigFromFile(t *testing.T) {
397399
})
398400
}
399401
}
402+
403+
func TestIsKubeadmPrereleaseVersion(t *testing.T) {
404+
validVersionInfo := &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0-alpha.1"}
405+
tests := []struct {
406+
name string
407+
versionInfo *apimachineryversion.Info
408+
k8sVersion *version.Version
409+
mcpVersion *version.Version
410+
expectedResult bool
411+
}{
412+
{
413+
name: "invalid versionInfo",
414+
versionInfo: &apimachineryversion.Info{},
415+
expectedResult: false,
416+
},
417+
{
418+
name: "kubeadm is not a prerelease version",
419+
versionInfo: &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0"},
420+
expectedResult: false,
421+
},
422+
{
423+
name: "mcpVersion is equal to k8sVersion",
424+
versionInfo: validVersionInfo,
425+
k8sVersion: version.MustParseSemantic("v1.21.0"),
426+
mcpVersion: version.MustParseSemantic("v1.21.0"),
427+
expectedResult: true,
428+
},
429+
{
430+
name: "k8sVersion is 1 MINOR version older than mcpVersion",
431+
versionInfo: validVersionInfo,
432+
k8sVersion: version.MustParseSemantic("v1.21.0"),
433+
mcpVersion: version.MustParseSemantic("v1.22.0"),
434+
expectedResult: true,
435+
},
436+
{
437+
name: "k8sVersion is 2 MINOR versions older than mcpVersion",
438+
versionInfo: validVersionInfo,
439+
k8sVersion: version.MustParseSemantic("v1.21.0"),
440+
mcpVersion: version.MustParseSemantic("v1.23.0"),
441+
expectedResult: false,
442+
},
443+
}
444+
445+
for _, tc := range tests {
446+
t.Run(tc.name, func(t *testing.T) {
447+
result := isKubeadmPrereleaseVersion(tc.versionInfo, tc.k8sVersion, tc.mcpVersion)
448+
if result != tc.expectedResult {
449+
t.Errorf("expected result: %v, got %v", tc.expectedResult, result)
450+
}
451+
})
452+
}
453+
}

cmd/kubeadm/test/cmd/init_test.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ package kubeadm
1919
import (
2020
"fmt"
2121
"os"
22+
"strings"
2223
"testing"
2324

24-
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
25+
"k8s.io/apimachinery/pkg/util/version"
2526

2627
"github.com/lithammer/dedent"
2728
)
@@ -37,6 +38,16 @@ func runKubeadmInit(args ...string) (string, string, int, error) {
3738
return RunCmd(kubeadmPath, kubeadmArgs...)
3839
}
3940

41+
func getKubeadmVersion() *version.Version {
42+
kubeadmPath := getKubeadmPath()
43+
kubeadmArgs := []string{"version", "-o=short"}
44+
out, _, _, err := RunCmd(kubeadmPath, kubeadmArgs...)
45+
if err != nil {
46+
panic(fmt.Sprintf("could not run 'kubeadm version -o=short': %v", err))
47+
}
48+
return version.MustParseSemantic(strings.TrimSpace(out))
49+
}
50+
4051
func TestCmdInitToken(t *testing.T) {
4152
initTest := []struct {
4253
name string
@@ -94,7 +105,7 @@ func TestCmdInitKubernetesVersion(t *testing.T) {
94105
},
95106
{
96107
name: "valid version is accepted",
97-
args: "--kubernetes-version=" + constants.CurrentKubernetesVersion.String(),
108+
args: "--kubernetes-version=" + getKubeadmVersion().String(),
98109
expected: true,
99110
},
100111
}

0 commit comments

Comments
 (0)