From f3dc622032ed19e8a6612567cf7608c09e79424e Mon Sep 17 00:00:00 2001 From: Sandeep Rajan Date: Wed, 13 Sep 2017 16:07:34 -0400 Subject: [PATCH] adding coredns as a featuregate --- cmd/kubeadm/app/constants/constants.go | 5 + cmd/kubeadm/app/features/features.go | 4 + cmd/kubeadm/app/phases/addons/dns/BUILD | 3 + cmd/kubeadm/app/phases/addons/dns/dns.go | 123 +++++++++++++++-- cmd/kubeadm/app/phases/addons/dns/dns_test.go | 23 ++++ .../app/phases/addons/dns/manifests.go | 129 ++++++++++++++++++ cmd/kubeadm/app/phases/addons/dns/versions.go | 36 +++-- .../app/phases/addons/dns/versions_test.go | 5 +- cmd/kubeadm/app/phases/upgrade/compute.go | 14 +- 9 files changed, 318 insertions(+), 24 deletions(-) diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index 20f35ea2f14..c7068179a2b 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -171,6 +171,11 @@ const ( // DefaultCIImageRepository points to image registry where CI uploads images from ci-cross build job DefaultCIImageRepository = "gcr.io/kubernetes-ci-images" + + // CoreDNS defines a variable used internally when referring to the CoreDNS addon for a cluster + CoreDNS = "CoreDNS" + // KubeDNS defines a variable used internally when referring to the kube-dns addon for a cluster + KubeDNS = "kube-dns" ) var ( diff --git a/cmd/kubeadm/app/features/features.go b/cmd/kubeadm/app/features/features.go index 3203a8028e9..1f39baaa8f6 100644 --- a/cmd/kubeadm/app/features/features.go +++ b/cmd/kubeadm/app/features/features.go @@ -30,6 +30,9 @@ const ( // HighAvailability is alpha in v1.9 HighAvailability = "HighAvailability" + // CoreDNS is alpha in v1.9 + CoreDNS = "CoreDNS" + // SelfHosting is beta in v1.8 SelfHosting = "SelfHosting" @@ -48,6 +51,7 @@ var InitFeatureGates = FeatureList{ StoreCertsInSecrets: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}}, HighAvailability: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, SupportIPVSProxyMode: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, + CoreDNS: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, } // Feature represents a feature being gated diff --git a/cmd/kubeadm/app/phases/addons/dns/BUILD b/cmd/kubeadm/app/phases/addons/dns/BUILD index 9a5ab6e2962..d52de184cc8 100644 --- a/cmd/kubeadm/app/phases/addons/dns/BUILD +++ b/cmd/kubeadm/app/phases/addons/dns/BUILD @@ -15,6 +15,7 @@ go_test( importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns", library = ":go_default_library", deps = [ + "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//pkg/api:go_default_library", "//pkg/util/version:go_default_library", @@ -36,12 +37,14 @@ go_library( deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", "//pkg/api/legacyscheme:go_default_library", "//pkg/util/version:go_default_library", "//vendor/k8s.io/api/apps/v1beta2:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/cmd/kubeadm/app/phases/addons/dns/dns.go b/cmd/kubeadm/app/phases/addons/dns/dns.go index 2786bb4914b..0be3a4ed740 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns.go @@ -23,12 +23,14 @@ import ( apps "k8s.io/api/apps/v1beta2" "k8s.io/api/core/v1" + rbac "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kuberuntime "k8s.io/apimachinery/pkg/runtime" clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -40,13 +42,19 @@ const ( KubeDNSServiceAccountName = "kube-dns" ) -// EnsureDNSAddon creates the kube-dns addon +// EnsureDNSAddon creates the kube-dns or CoreDNS addon func EnsureDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error { k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion) if err != nil { return fmt.Errorf("couldn't parse kubernetes version %q: %v", cfg.KubernetesVersion, err) } + if features.Enabled(cfg.FeatureGates, features.CoreDNS) { + return coreDNSAddon(cfg, client, k8sVersion) + } + return kubeDNSAddon(cfg, client, k8sVersion) +} +func kubeDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface, k8sVersion *version.Version) error { if err := CreateServiceAccount(client); err != nil { return err } @@ -70,7 +78,7 @@ func EnsureDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interf ImageRepository: cfg.ImageRepository, Arch: runtime.GOARCH, // Get the kube-dns version conditionally based on the k8s version - Version: GetKubeDNSVersion(k8sVersion), + Version: GetDNSVersion(k8sVersion, kubeadmconstants.KubeDNS), DNSBindAddr: dnsBindAddr, DNSDomain: cfg.Networking.DNSDomain, DNSProbeType: GetKubeDNSProbeType(k8sVersion), @@ -117,21 +125,120 @@ func createKubeDNSAddon(deploymentBytes, serviceBytes []byte, client clientset.I } kubednsService := &v1.Service{} - if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), serviceBytes, kubednsService); err != nil { - return fmt.Errorf("unable to decode kube-dns service %v", err) + return createDNSService(kubednsService, serviceBytes, client) +} + +func coreDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface, k8sVersion *version.Version) error { + // Get the YAML manifest conditionally based on the k8s version + dnsDeploymentBytes := GetCoreDNSManifest(k8sVersion) + coreDNSDeploymentBytes, err := kubeadmutil.ParseTemplate(dnsDeploymentBytes, struct{ MasterTaintKey, Version string }{ + MasterTaintKey: kubeadmconstants.LabelNodeRoleMaster, + Version: GetDNSVersion(k8sVersion, kubeadmconstants.CoreDNS), + }) + if err != nil { + return fmt.Errorf("error when parsing CoreDNS deployment template: %v", err) + } + + // Get the config file for CoreDNS + coreDNSConfigMapBytes, err := kubeadmutil.ParseTemplate(CoreDNSConfigMap, struct{ DNSDomain, ServiceCIDR string }{ + ServiceCIDR: cfg.Networking.ServiceSubnet, + DNSDomain: cfg.Networking.DNSDomain, + }) + if err != nil { + return fmt.Errorf("error when parsing CoreDNS configMap template: %v", err) + } + + dnsip, err := getDNSIP(client) + if err != nil { + return err + } + + coreDNSServiceBytes, err := kubeadmutil.ParseTemplate(KubeDNSService, struct{ DNSIP string }{ + DNSIP: dnsip.String(), + }) + + if err != nil { + return fmt.Errorf("error when parsing CoreDNS service template: %v", err) + } + + if err := createCoreDNSAddon(coreDNSDeploymentBytes, coreDNSServiceBytes, coreDNSConfigMapBytes, client); err != nil { + return err + } + fmt.Println("[addons] Applied essential addon: CoreDNS") + return nil +} + +func createCoreDNSAddon(deploymentBytes, serviceBytes, configBytes []byte, client clientset.Interface) error { + coreDNSConfigMap := &v1.ConfigMap{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), configBytes, coreDNSConfigMap); err != nil { + return fmt.Errorf("unable to decode CoreDNS configmap %v", err) + } + + // Create the ConfigMap for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateConfigMap(client, coreDNSConfigMap); err != nil { + return err + } + + coreDNSClusterRoles := &rbac.ClusterRole{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), []byte(CoreDNSClusterRole), coreDNSClusterRoles); err != nil { + return fmt.Errorf("unable to decode CoreDNS clusterroles %v", err) + } + + // Create the Clusterroles for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateClusterRole(client, coreDNSClusterRoles); err != nil { + return err + } + + coreDNSClusterRolesBinding := &rbac.ClusterRoleBinding{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), []byte(CoreDNSClusterRoleBinding), coreDNSClusterRolesBinding); err != nil { + return fmt.Errorf("unable to decode CoreDNS clusterrolebindings %v", err) + } + + // Create the Clusterrolebindings for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateClusterRoleBinding(client, coreDNSClusterRolesBinding); err != nil { + return err + } + + coreDNSServiceAccount := &v1.ServiceAccount{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), []byte(CoreDNSServiceAccount), coreDNSServiceAccount); err != nil { + return fmt.Errorf("unable to decode CoreDNS configmap %v", err) + } + + // Create the ConfigMap for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateServiceAccount(client, coreDNSServiceAccount); err != nil { + return err + } + + coreDNSDeployment := &apps.Deployment{} + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), deploymentBytes, coreDNSDeployment); err != nil { + return fmt.Errorf("unable to decode CoreDNS deployment %v", err) + } + + // Create the Deployment for CoreDNS or update it in case it already exists + if err := apiclient.CreateOrUpdateDeployment(client, coreDNSDeployment); err != nil { + return err + } + + coreDNSService := &v1.Service{} + return createDNSService(coreDNSService, serviceBytes, client) +} + +func createDNSService(dnsService *v1.Service, serviceBytes []byte, client clientset.Interface) error { + if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), serviceBytes, dnsService); err != nil { + return fmt.Errorf("unable to decode the DNS service %v", err) } // Can't use a generic apiclient helper func here as we have to tolerate more than AlreadyExists. - if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Create(kubednsService); err != nil { + if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Create(dnsService); err != nil { // Ignore if the Service is invalid with this error message: // Service "kube-dns" is invalid: spec.clusterIP: Invalid value: "10.96.0.10": provided IP is already allocated if !apierrors.IsAlreadyExists(err) && !apierrors.IsInvalid(err) { - return fmt.Errorf("unable to create a new kube-dns service: %v", err) + return fmt.Errorf("unable to create a new DNS service: %v", err) } - if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Update(kubednsService); err != nil { - return fmt.Errorf("unable to create/update the kube-dns service: %v", err) + if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Update(dnsService); err != nil { + return fmt.Errorf("unable to create/update the DNS service: %v", err) } } return nil diff --git a/cmd/kubeadm/app/phases/addons/dns/dns_test.go b/cmd/kubeadm/app/phases/addons/dns/dns_test.go index d707f8760a1..7e671f20c40 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns_test.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns_test.go @@ -109,6 +109,29 @@ func TestCompileManifests(t *testing.T) { }, expected: true, }, + { + manifest: CoreDNSDeployment, + data: struct{ MasterTaintKey, Version string }{ + MasterTaintKey: "foo", + Version: "foo", + }, + expected: true, + }, + { + manifest: KubeDNSService, + data: struct{ DNSIP string }{ + DNSIP: "foo", + }, + expected: true, + }, + { + manifest: CoreDNSConfigMap, + data: struct{ DNSDomain, ServiceCIDR string }{ + DNSDomain: "foo", + ServiceCIDR: "foo", + }, + expected: true, + }, } for _, rt := range tests { _, actual := kubeadmutil.ParseTemplate(rt.manifest, rt.data) diff --git a/cmd/kubeadm/app/phases/addons/dns/manifests.go b/cmd/kubeadm/app/phases/addons/dns/manifests.go index d291e337ab5..d06498cb626 100644 --- a/cmd/kubeadm/app/phases/addons/dns/manifests.go +++ b/cmd/kubeadm/app/phases/addons/dns/manifests.go @@ -213,4 +213,133 @@ spec: selector: k8s-app: kube-dns ` + + // CoreDNSDeployment is the CoreDNS Deployment manifest + CoreDNSDeployment = ` +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: coredns + namespace: kube-system + labels: + k8s-app: kube-dns +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: kube-dns + template: + metadata: + labels: + k8s-app: kube-dns + spec: + serviceAccountName: coredns + tolerations: + - key: CriticalAddonsOnly + operator: Exists + - key: {{ .MasterTaintKey }} + effect: NoSchedule + containers: + - name: coredns + image: coredns/coredns:{{ .Version }} + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 170Mi + requests: + cpu: 100m + memory: 70Mi + args: [ "-conf", "/etc/coredns/Corefile" ] + volumeMounts: + - name: config-volume + mountPath: /etc/coredns + ports: + - containerPort: 53 + name: dns + protocol: UDP + - containerPort: 53 + name: dns-tcp + protocol: TCP + - containerPort: 9153 + name: metrics + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + dnsPolicy: Default + volumes: + - name: config-volume + configMap: + name: coredns + items: + - key: Corefile + path: Corefile +` + + // CoreDNSConfigMap is the CoreDNS ConfigMap manifest + CoreDNSConfigMap = ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns + namespace: kube-system +data: + Corefile: | + .:53 { + errors + log stdout + health + kubernetes {{ .DNSDomain }} {{ .ServiceCIDR }} + prometheus + proxy . /etc/resolv.conf + cache 30 + } +` + // CoreDNSClusterRole is the CoreDNS ClusterRole manifest + CoreDNSClusterRole = ` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system:coredns +rules: +- apiGroups: + - "" + resources: + - endpoints + - services + - pods + - namespaces + verbs: + - list + - watch +` + // CoreDNSClusterRoleBinding is the CoreDNS Clusterrolebinding manifest + CoreDNSClusterRoleBinding = ` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: system:coredns +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:coredns +subjects: +- kind: ServiceAccount + name: coredns + namespace: kube-system +` + // CoreDNSServiceAccount is the CoreDNS ServiceAccount manifest + CoreDNSServiceAccount = ` +apiVersion: v1 +kind: ServiceAccount +metadata: + name: coredns + namespace: kube-system +` ) diff --git a/cmd/kubeadm/app/phases/addons/dns/versions.go b/cmd/kubeadm/app/phases/addons/dns/versions.go index 9130f4a8def..ed3fcf19609 100644 --- a/cmd/kubeadm/app/phases/addons/dns/versions.go +++ b/cmd/kubeadm/app/phases/addons/dns/versions.go @@ -17,6 +17,7 @@ limitations under the License. package dns import ( + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/pkg/util/version" ) @@ -26,18 +27,30 @@ const ( kubeDNSProbeSRV = "SRV" kubeDNSProbeA = "A" + coreDNSVersion = "0.9.10" ) -// GetKubeDNSVersion returns the right kube-dns version for a specific k8s version -func GetKubeDNSVersion(kubeVersion *version.Version) string { - // v1.8.0+ uses 1.14.5 - // v1.9.0+ uses 1.14.7 - // In the future when the kube-dns version is bumped at HEAD; add conditional logic to return the right versions +// GetDNSVersion returns the right kube-dns version for a specific k8s version +func GetDNSVersion(kubeVersion *version.Version, dns string) string { + // v1.8.0+ uses kube-dns 1.14.5 + // v1.9.0+ uses kube-dns 1.14.7 + // v1.9.0+ uses CoreDNS 0.9.10 + + // In the future when the version is bumped at HEAD; add conditional logic to return the right versions // Also, the version might be bumped for different k8s releases on the same branch - if kubeVersion.Major() == 1 && kubeVersion.Minor() >= 9 { - return kubeDNSv190AndAboveVersion + switch dns { + case kubeadmconstants.KubeDNS: + // return the kube-dns version + if kubeVersion.Major() == 1 && kubeVersion.Minor() >= 9 { + return kubeDNSv190AndAboveVersion + } + return kubeDNSv180AndAboveVersion + case kubeadmconstants.CoreDNS: + // return the CoreDNS version + return coreDNSVersion + default: + return kubeDNSv180AndAboveVersion } - return kubeDNSv180AndAboveVersion } // GetKubeDNSProbeType returns the right kube-dns probe for a specific k8s version @@ -57,3 +70,10 @@ func GetKubeDNSManifest(kubeVersion *version.Version) string { // In the future when the kube-dns version is bumped at HEAD; add conditional logic to return the right manifest return v180AndAboveKubeDNSDeployment } + +// GetCoreDNSManifest returns the right CoreDNS YAML manifest for a specific k8s version +func GetCoreDNSManifest(kubeVersion *version.Version) string { + // v1.9.0+ has only one known YAML manifest spec, just return that here + // In the future when the CoreDNS version is bumped at HEAD; add conditional logic to return the right manifest + return CoreDNSDeployment +} diff --git a/cmd/kubeadm/app/phases/addons/dns/versions_test.go b/cmd/kubeadm/app/phases/addons/dns/versions_test.go index de1d286c398..7580ba19a31 100644 --- a/cmd/kubeadm/app/phases/addons/dns/versions_test.go +++ b/cmd/kubeadm/app/phases/addons/dns/versions_test.go @@ -19,6 +19,7 @@ package dns import ( "testing" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/pkg/util/version" ) @@ -62,10 +63,10 @@ func TestGetKubeDNSVersion(t *testing.T) { t.Fatalf("couldn't parse kubernetes version %q: %v", rt.k8sVersion, err) } - actualDNSVersion := GetKubeDNSVersion(k8sVersion) + actualDNSVersion := GetDNSVersion(k8sVersion, kubeadmconstants.KubeDNS) if actualDNSVersion != rt.expected { t.Errorf( - "failed GetKubeDNSVersion:\n\texpected: %s\n\t actual: %s", + "failed GetDNSVersion:\n\texpected: %s\n\t actual: %s", rt.expected, actualDNSVersion, ) diff --git a/cmd/kubeadm/app/phases/upgrade/compute.go b/cmd/kubeadm/app/phases/upgrade/compute.go index aeb2cad71a6..f0c66a2d269 100644 --- a/cmd/kubeadm/app/phases/upgrade/compute.go +++ b/cmd/kubeadm/app/phases/upgrade/compute.go @@ -20,6 +20,7 @@ import ( "fmt" "strings" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns" "k8s.io/kubernetes/pkg/util/version" ) @@ -94,9 +95,10 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA } // Construct a descriptor for the current state of the world + // TODO: Make CoreDNS available here. beforeState := ClusterState{ KubeVersion: clusterVersionStr, - DNSVersion: dns.GetKubeDNSVersion(clusterVersion), + DNSVersion: dns.GetDNSVersion(clusterVersion, kubeadmconstants.KubeDNS), KubeadmVersion: kubeadmVersionStr, KubeletVersions: kubeletVersions, } @@ -139,7 +141,7 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA Before: beforeState, After: ClusterState{ KubeVersion: patchVersionStr, - DNSVersion: dns.GetKubeDNSVersion(patchVersion), + DNSVersion: dns.GetDNSVersion(patchVersion, kubeadmconstants.KubeDNS), KubeadmVersion: newKubeadmVer, // KubeletVersions is unset here as it is not used anywhere in .After }, @@ -154,7 +156,7 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA Before: beforeState, After: ClusterState{ KubeVersion: stableVersionStr, - DNSVersion: dns.GetKubeDNSVersion(stableVersion), + DNSVersion: dns.GetDNSVersion(stableVersion, kubeadmconstants.KubeDNS), KubeadmVersion: stableVersionStr, // KubeletVersions is unset here as it is not used anywhere in .After }, @@ -198,7 +200,7 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA Before: beforeState, After: ClusterState{ KubeVersion: previousBranchLatestVersionStr, - DNSVersion: dns.GetKubeDNSVersion(previousBranchLatestVersion), + DNSVersion: dns.GetDNSVersion(previousBranchLatestVersion, kubeadmconstants.KubeDNS), KubeadmVersion: previousBranchLatestVersionStr, // KubeletVersions is unset here as it is not used anywhere in .After }, @@ -210,12 +212,12 @@ func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesA // Default to assume that the experimental version to show is the unstable one unstableKubeVersion := latestVersionStr - unstableKubeDNSVersion := dns.GetKubeDNSVersion(latestVersion) + unstableKubeDNSVersion := dns.GetDNSVersion(latestVersion, kubeadmconstants.KubeDNS) // Ẃe should not display alpha.0. The previous branch's beta/rc versions are more relevant due how the kube branching process works. if latestVersion.PreRelease() == "alpha.0" { unstableKubeVersion = previousBranchLatestVersionStr - unstableKubeDNSVersion = dns.GetKubeDNSVersion(previousBranchLatestVersion) + unstableKubeDNSVersion = dns.GetDNSVersion(previousBranchLatestVersion, kubeadmconstants.KubeDNS) } upgrades = append(upgrades, Upgrade{