diff --git a/pkg/templates/kubeadm/v1beta3/kubeadm.go b/pkg/templates/kubeadm/v1beta3/kubeadm.go index 395d5222e..4b575c660 100644 --- a/pkg/templates/kubeadm/v1beta3/kubeadm.go +++ b/pkg/templates/kubeadm/v1beta3/kubeadm.go @@ -31,6 +31,7 @@ import ( "k8c.io/kubeone/pkg/fail" "k8c.io/kubeone/pkg/features" "k8c.io/kubeone/pkg/kubeflags" + "k8c.io/kubeone/pkg/semverutil" "k8c.io/kubeone/pkg/state" "k8c.io/kubeone/pkg/templates/kubeadm/kubeadmargs" "k8c.io/kubeone/pkg/templates/kubernetesconfigs" @@ -44,6 +45,25 @@ const ( bootstrapTokenTTL = 60 * time.Minute ) +const ( + // fixedEtcdVersion is an etcd version that doesn't have known data integrity and durability bugs + // (see etcdVersionCorruptCheckExtraArgs for more details) + fixedEtcdVersion = "3.5.5-0" + + // fixedEtcd123 defines a semver constraint used to check if Kubernetes 1.23 uses fixed etcd version + fixedEtcd123 = ">= 1.23.14, < 1.24" + // fixedEtcd124 defines a semver constraint used to check if Kubernetes 1.24 uses fixed etcd version + fixedEtcd124 = ">= 1.24.8, < 1.25" + // fixedEtcd125 defines a semver constraint used to check if Kubernetes 1.25+ uses fixed etcd version + fixedEtcd125 = ">= 1.25.4" +) + +var ( + fixedEtcd123Constraint = semverutil.MustParseConstraint(fixedEtcd123) + fixedEtcd124Constraint = semverutil.MustParseConstraint(fixedEtcd124) + fixedEtcd125Constraint = semverutil.MustParseConstraint(fixedEtcd125) +) + // NewConfig returns all required configs to init a cluster via a set of v1beta3 configs func NewConfig(s *state.State, host kubeoneapi.HostConfig) ([]runtime.Object, error) { cluster := s.Cluster @@ -52,7 +72,7 @@ func NewConfig(s *state.State, host kubeoneapi.HostConfig) ([]runtime.Object, er return nil, fail.Config(err, "parsing kubernetes semver") } - etcdImageTag, etcdExtraArgs := etcdVersionCorruptCheckExtraArgs(cluster.AssetConfiguration.Etcd.ImageTag) + etcdImageTag, etcdExtraArgs := etcdVersionCorruptCheckExtraArgs(kubeSemVer, cluster.AssetConfiguration.Etcd.ImageTag) nodeRegistration := newNodeRegistration(s, host) nodeRegistration.IgnorePreflightErrors = []string{ @@ -402,18 +422,28 @@ func newNodeRegistration(s *state.State, host kubeoneapi.HostConfig) kubeadmv1be } } -func etcdVersionCorruptCheckExtraArgs(etcdImageTag string) (string, map[string]string) { - etcdExtraArgs := map[string]string{} - - // This is required because etcd v3.5-[0-2] (used for Kubernetes 1.22+) - // has an issue with the data integrity. - // See https://groups.google.com/a/kubernetes.io/g/dev/c/B7gJs88XtQc/m/rSgNOzV2BwAJ - // for more details. - if etcdImageTag == "" { - etcdImageTag = "3.5.3-0" +// etcdVersionCorruptCheckExtraArgs provides etcd version and args to be used. +// This is required because: +// - etcd v3.5.[0-2] has an issue with the data integrity +// https://groups.google.com/a/kubernetes.io/g/dev/c/B7gJs88XtQc/m/rSgNOzV2BwAJ +// - etcd v3.5.[0-4] has a durability issue affecting single-node (non-HA) etcd clusters +// https://groups.google.com/a/kubernetes.io/g/dev/c/7q4tB_Vp3Uc/m/MrHalhCIBAAJ +func etcdVersionCorruptCheckExtraArgs(kubeVersion *semver.Version, etcdImageTag string) (string, map[string]string) { + etcdExtraArgs := map[string]string{ + "experimental-initial-corrupt-check": "true", + "experimental-corrupt-check-time": "240m", } - etcdExtraArgs["experimental-initial-corrupt-check"] = "true" - etcdExtraArgs["experimental-corrupt-check-time"] = "240m" - return etcdImageTag, etcdExtraArgs + switch { + case etcdImageTag != "": + return etcdImageTag, etcdExtraArgs + case fixedEtcd123Constraint.Check(kubeVersion): + fallthrough + case fixedEtcd124Constraint.Check(kubeVersion): + fallthrough + case fixedEtcd125Constraint.Check(kubeVersion): + return "", etcdExtraArgs + default: + return fixedEtcdVersion, etcdExtraArgs + } } diff --git a/pkg/templates/kubeadm/v1beta3/kubeadm_test.go b/pkg/templates/kubeadm/v1beta3/kubeadm_test.go new file mode 100644 index 000000000..49052ea59 --- /dev/null +++ b/pkg/templates/kubeadm/v1beta3/kubeadm_test.go @@ -0,0 +1,114 @@ +/* +Copyright 2022 The KubeOne Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta3 + +import ( + "reflect" + "testing" + + "github.com/Masterminds/semver/v3" +) + +func TestEtcdVersionCorruptCheckExtraArgs(t *testing.T) { + etcdExtraArgs := map[string]string{ + "experimental-initial-corrupt-check": "true", + "experimental-corrupt-check-time": "240m", + } + + tests := []struct { + name string + kubeVersion *semver.Version + etcdImageTag string + expectedEtcdImageTag string + expectedEtcdArgs map[string]string + }{ + { + name: "unfixed 1.22", + kubeVersion: semver.MustParse("1.22.10"), + expectedEtcdImageTag: fixedEtcdVersion, + expectedEtcdArgs: etcdExtraArgs, + }, + { + name: "unfixed 1.23", + kubeVersion: semver.MustParse("1.23.13"), + expectedEtcdImageTag: fixedEtcdVersion, + expectedEtcdArgs: etcdExtraArgs, + }, + { + name: "unfixed 1.24", + kubeVersion: semver.MustParse("1.24.7"), + expectedEtcdImageTag: fixedEtcdVersion, + expectedEtcdArgs: etcdExtraArgs, + }, + { + name: "unfixed 1.25", + kubeVersion: semver.MustParse("1.25.3"), + expectedEtcdImageTag: fixedEtcdVersion, + expectedEtcdArgs: etcdExtraArgs, + }, + { + name: "fixed 1.23", + kubeVersion: semver.MustParse("1.23.14"), + expectedEtcdImageTag: "", + expectedEtcdArgs: etcdExtraArgs, + }, + { + name: "fixed 1.24", + kubeVersion: semver.MustParse("1.24.8"), + expectedEtcdImageTag: "", + expectedEtcdArgs: etcdExtraArgs, + }, + { + name: "fixed 1.25", + kubeVersion: semver.MustParse("1.25.4"), + expectedEtcdImageTag: "", + expectedEtcdArgs: etcdExtraArgs, + }, + { + name: "fixed 1.26", + kubeVersion: semver.MustParse("1.26.0"), + expectedEtcdImageTag: "", + expectedEtcdArgs: etcdExtraArgs, + }, + { + name: "unfixed 1.25, but tag is overwritten", + kubeVersion: semver.MustParse("1.25.3"), + etcdImageTag: "9.9.9-0", + expectedEtcdImageTag: "9.9.9-0", + expectedEtcdArgs: etcdExtraArgs, + }, + { + name: "fixed 1.25, but tag is overwritten", + kubeVersion: semver.MustParse("1.25.4"), + etcdImageTag: "9.9.9-0", + expectedEtcdImageTag: "9.9.9-0", + expectedEtcdArgs: etcdExtraArgs, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ver, args := etcdVersionCorruptCheckExtraArgs(tt.kubeVersion, tt.etcdImageTag) + if ver != tt.expectedEtcdImageTag { + t.Errorf("got etcd image tag %q, but expected %q", ver, tt.expectedEtcdImageTag) + } + if !reflect.DeepEqual(args, tt.expectedEtcdArgs) { + t.Errorf("got etcd tags %q, but expected %q", args, tt.expectedEtcdArgs) + } + }) + } +}