mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
kubeadm: dynamically populate the current/minimum k8s versions
Kubeadm requires manual version updates of its current supported k8s control plane version and minimally supported k8s control plane and kubelet versions every release cycle. To avoid that, in constants.go: - Add the helper function getSkewedKubernetesVersion() that can be used to retrieve a MAJOR.(MINOR+n).0 version of k8s. It currently uses the kubeadm version populated in "component-base/version" during the kubeadm build process. - Use the function to set existing version constants (variables). Update util/config/common.go#NormalizeKubernetesVersion() to tolerate the case where a k8s version in the ClusterConfiguration is too old for the kubeadm binary to use during code freeze. Include unit tests for the new utilities.
This commit is contained in:
parent
9ff3b7e744
commit
207ffa7bdc
@ -29,7 +29,9 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
|
||||
componentversion "k8s.io/component-base/version"
|
||||
utilnet "k8s.io/utils/net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@ -448,13 +450,13 @@ var (
|
||||
ControlPlaneComponents = []string{KubeAPIServer, KubeControllerManager, KubeScheduler}
|
||||
|
||||
// MinimumControlPlaneVersion specifies the minimum control plane version kubeadm can deploy
|
||||
MinimumControlPlaneVersion = version.MustParseSemantic("v1.21.0")
|
||||
MinimumControlPlaneVersion = getSkewedKubernetesVersion(-1)
|
||||
|
||||
// MinimumKubeletVersion specifies the minimum version of kubelet which kubeadm supports
|
||||
MinimumKubeletVersion = version.MustParseSemantic("v1.21.0")
|
||||
MinimumKubeletVersion = getSkewedKubernetesVersion(-1)
|
||||
|
||||
// CurrentKubernetesVersion specifies current Kubernetes version supported by kubeadm
|
||||
CurrentKubernetesVersion = version.MustParseSemantic("v1.22.0")
|
||||
CurrentKubernetesVersion = getSkewedKubernetesVersion(0)
|
||||
|
||||
// SupportedEtcdVersion lists officially supported etcd versions with corresponding Kubernetes releases
|
||||
SupportedEtcdVersion = map[uint8]string{
|
||||
@ -483,8 +485,52 @@ var (
|
||||
Factor: 1.0,
|
||||
Jitter: 0.1,
|
||||
}
|
||||
|
||||
// defaultKubernetesVersionForTests is the default version used for unit tests.
|
||||
// The MINOR should be at least 3 as some tests subtract 3 from the MINOR version.
|
||||
defaultKubernetesVersionForTests = version.MustParseSemantic("v1.3.0")
|
||||
)
|
||||
|
||||
// isRunningInTest can be used to determine if the code in this file is being run in a test.
|
||||
func isRunningInTest() bool {
|
||||
return strings.HasSuffix(os.Args[0], ".test")
|
||||
}
|
||||
|
||||
// getSkewedKubernetesVersion returns the current MAJOR.(MINOR+n).0 Kubernetes version with a skew of 'n'
|
||||
// It uses the kubeadm version provided by the 'component-base/version' package. This version must be populated
|
||||
// by passing linker flags during the kubeadm build process. If the version is empty, assume that kubeadm
|
||||
// was either build incorrectly or this code is running in unit tests.
|
||||
func getSkewedKubernetesVersion(n int) *version.Version {
|
||||
versionInfo := componentversion.Get()
|
||||
ver := getSkewedKubernetesVersionImpl(&versionInfo, n, isRunningInTest)
|
||||
if ver == nil {
|
||||
panic("kubeadm is not build properly using 'make ...': missing component version information")
|
||||
}
|
||||
return ver
|
||||
}
|
||||
|
||||
func getSkewedKubernetesVersionImpl(versionInfo *apimachineryversion.Info, n int, isRunningInTestFunc func() bool) *version.Version {
|
||||
// TODO: update if the kubeadm version gets decoupled from the Kubernetes version.
|
||||
// This would require keeping track of the supported skew in a table.
|
||||
// More changes would be required if the kubelet version one day decouples from that of Kubernetes.
|
||||
var ver *version.Version
|
||||
if len(versionInfo.Major) == 0 {
|
||||
if isRunningInTestFunc() {
|
||||
ver = defaultKubernetesVersionForTests // An arbitrary version for testing purposes
|
||||
} else {
|
||||
// If this is not running in tests assume that the kubeadm binary is not build properly
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
ver = version.MustParseSemantic(versionInfo.GitVersion)
|
||||
}
|
||||
// Append the MINOR version skew.
|
||||
// TODO: handle the case of Kubernetes moving to v2.0 or having MAJOR version updates in the future.
|
||||
// This would require keeping track (in a table) of the last MINOR for a particular MAJOR.
|
||||
minor := uint(int(ver.Minor()) + n)
|
||||
return version.MustParseSemantic(fmt.Sprintf("v%d.%d.0", ver.Major(), minor))
|
||||
}
|
||||
|
||||
// EtcdSupportedVersion returns officially supported version of etcd for a specific Kubernetes release
|
||||
// If passed version is not in the given list, the function returns the nearest version with a warning
|
||||
func EtcdSupportedVersion(supportedEtcdVersion map[uint8]string, versionString string) (etcdVersion *version.Version, warning, err error) {
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
func TestGetStaticPodDirectory(t *testing.T) {
|
||||
@ -237,3 +238,61 @@ func TestGetKubernetesServiceCIDR(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSkewedKubernetesVersionImpl(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
versionInfo *apimachineryversion.Info
|
||||
n int
|
||||
isRunningInTestFunc func() bool
|
||||
expectedResult *version.Version
|
||||
}{
|
||||
{
|
||||
name: "invalid versionInfo; running in test",
|
||||
versionInfo: &apimachineryversion.Info{},
|
||||
expectedResult: defaultKubernetesVersionForTests,
|
||||
},
|
||||
{
|
||||
name: "invalid versionInfo; not running in test",
|
||||
versionInfo: &apimachineryversion.Info{},
|
||||
isRunningInTestFunc: func() bool { return false },
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
name: "valid skew of -1",
|
||||
versionInfo: &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0"},
|
||||
n: -1,
|
||||
expectedResult: version.MustParseSemantic("v1.22.0"),
|
||||
},
|
||||
{
|
||||
name: "valid skew of 0",
|
||||
versionInfo: &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0"},
|
||||
n: 0,
|
||||
expectedResult: version.MustParseSemantic("v1.23.0"),
|
||||
},
|
||||
{
|
||||
name: "valid skew of +1",
|
||||
versionInfo: &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0"},
|
||||
n: 1,
|
||||
expectedResult: version.MustParseSemantic("v1.24.0"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.isRunningInTestFunc == nil {
|
||||
tc.isRunningInTestFunc = func() bool { return true }
|
||||
}
|
||||
result := getSkewedKubernetesVersionImpl(tc.versionInfo, tc.n, tc.isRunningInTestFunc)
|
||||
if (tc.expectedResult == nil) != (result == nil) {
|
||||
t.Errorf("expected result: %v, got: %v", tc.expectedResult, result)
|
||||
}
|
||||
if result == nil {
|
||||
return
|
||||
}
|
||||
if cmp, _ := result.Compare(tc.expectedResult.String()); cmp != 0 {
|
||||
t.Errorf("expected result: %v, got %v", tc.expectedResult, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
netutil "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
componentversion "k8s.io/component-base/version"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@ -102,8 +104,23 @@ func NormalizeKubernetesVersion(cfg *kubeadmapi.ClusterConfiguration) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "couldn't parse Kubernetes version %q", cfg.KubernetesVersion)
|
||||
}
|
||||
if k8sVersion.LessThan(constants.MinimumControlPlaneVersion) {
|
||||
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)
|
||||
|
||||
// During the k8s release process, a kubeadm version in the main branch could be 1.23.0-pre,
|
||||
// while the 1.22.0 version is not released yet. The MinimumControlPlaneVersion validation
|
||||
// in such a case will not pass, since the value of MinimumControlPlaneVersion would be
|
||||
// calculated as kubeadm version - 1 (1.22) and k8sVersion would still be at 1.21.x
|
||||
// (fetched from the 'stable' marker). Handle this case by only showing a warning.
|
||||
mcpVersion := constants.MinimumControlPlaneVersion
|
||||
versionInfo := componentversion.Get()
|
||||
if isKubeadmPrereleaseVersion(&versionInfo, k8sVersion, mcpVersion) {
|
||||
klog.V(1).Infof("WARNING: tolerating control plane version %s, assuming that k8s version %s is not released yet",
|
||||
cfg.KubernetesVersion, mcpVersion)
|
||||
return nil
|
||||
}
|
||||
// If not a pre-release version, handle the validation normally.
|
||||
if k8sVersion.LessThan(mcpVersion) {
|
||||
return errors.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s",
|
||||
mcpVersion, cfg.KubernetesVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -204,3 +221,20 @@ func MigrateOldConfig(oldConfig []byte) ([]byte, error) {
|
||||
|
||||
return bytes.Join(newConfig, []byte(constants.YAMLDocumentSeparator)), nil
|
||||
}
|
||||
|
||||
// isKubeadmPrereleaseVersion returns true if the kubeadm version is a pre-release version and
|
||||
// the minimum control plane version is N+2 MINOR version of the given k8sVersion.
|
||||
func isKubeadmPrereleaseVersion(versionInfo *apimachineryversion.Info, k8sVersion, mcpVersion *version.Version) bool {
|
||||
if len(versionInfo.Major) != 0 { // Make sure the component version is populated
|
||||
kubeadmVersion := version.MustParseSemantic(versionInfo.String())
|
||||
if len(kubeadmVersion.PreRelease()) != 0 { // Only handle this if the kubeadm binary is a pre-release
|
||||
// After incrementing the k8s MINOR version by one, if this version is equal or greater than the
|
||||
// MCP version, return true.
|
||||
v := k8sVersion.WithMinor(k8sVersion.Minor() + 1)
|
||||
if comp, _ := v.Compare(mcpVersion.String()); comp != -1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import (
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
|
||||
"github.com/lithammer/dedent"
|
||||
)
|
||||
@ -397,3 +399,55 @@ func TestMigrateOldConfigFromFile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsKubeadmPrereleaseVersion(t *testing.T) {
|
||||
validVersionInfo := &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0-alpha.1"}
|
||||
tests := []struct {
|
||||
name string
|
||||
versionInfo *apimachineryversion.Info
|
||||
k8sVersion *version.Version
|
||||
mcpVersion *version.Version
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "invalid versionInfo",
|
||||
versionInfo: &apimachineryversion.Info{},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "kubeadm is not a prerelease version",
|
||||
versionInfo: &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0"},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "mcpVersion is equal to k8sVersion",
|
||||
versionInfo: validVersionInfo,
|
||||
k8sVersion: version.MustParseSemantic("v1.21.0"),
|
||||
mcpVersion: version.MustParseSemantic("v1.21.0"),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "k8sVersion is 1 MINOR version older than mcpVersion",
|
||||
versionInfo: validVersionInfo,
|
||||
k8sVersion: version.MustParseSemantic("v1.21.0"),
|
||||
mcpVersion: version.MustParseSemantic("v1.22.0"),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "k8sVersion is 2 MINOR versions older than mcpVersion",
|
||||
versionInfo: validVersionInfo,
|
||||
k8sVersion: version.MustParseSemantic("v1.21.0"),
|
||||
mcpVersion: version.MustParseSemantic("v1.23.0"),
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := isKubeadmPrereleaseVersion(tc.versionInfo, tc.k8sVersion, tc.mcpVersion)
|
||||
if result != tc.expectedResult {
|
||||
t.Errorf("expected result: %v, got %v", tc.expectedResult, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user