1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package upgrade 18 19import ( 20 "fmt" 21 "strings" 22 23 kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" 24 "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns" 25 26 versionutil "k8s.io/apimachinery/pkg/util/version" 27 clientset "k8s.io/client-go/kubernetes" 28 "k8s.io/klog/v2" 29) 30 31// Upgrade defines an upgrade possibility to upgrade from a current version to a new one 32type Upgrade struct { 33 Description string 34 Before ClusterState 35 After ClusterState 36} 37 38// CanUpgradeKubelets returns whether an upgrade of any kubelet in the cluster is possible 39func (u *Upgrade) CanUpgradeKubelets() bool { 40 // If there are multiple different versions now, an upgrade is possible (even if only for a subset of the nodes) 41 if len(u.Before.KubeletVersions) > 1 { 42 return true 43 } 44 // Don't report something available for upgrade if we don't know the current state 45 if len(u.Before.KubeletVersions) == 0 { 46 return false 47 } 48 49 // if the same version number existed both before and after, we don't have to upgrade it 50 _, sameVersionFound := u.Before.KubeletVersions[u.After.KubeVersion] 51 return !sameVersionFound 52} 53 54// CanUpgradeEtcd returns whether an upgrade of etcd is possible 55func (u *Upgrade) CanUpgradeEtcd() bool { 56 return u.Before.EtcdVersion != u.After.EtcdVersion 57} 58 59// ClusterState describes the state of certain versions for a cluster 60type ClusterState struct { 61 // KubeVersion describes the version of the Kubernetes API Server, Controller Manager, Scheduler and Proxy. 62 KubeVersion string 63 // DNSVersion describes the version of the DNS add-on. 64 DNSVersion string 65 // KubeadmVersion describes the version of the kubeadm CLI 66 KubeadmVersion string 67 // KubeletVersions is a map with a version number linked to the amount of kubelets running that version in the cluster 68 KubeletVersions map[string]uint16 69 // EtcdVersion represents the version of etcd used in the cluster 70 EtcdVersion string 71} 72 73// GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which 74// kinds of upgrades can be performed 75func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesAllowed, rcUpgradesAllowed, externalEtcd bool, client clientset.Interface, manifestsDir string) ([]Upgrade, error) { 76 fmt.Println("[upgrade] Fetching available versions to upgrade to") 77 78 // Collect the upgrades kubeadm can do in this list 79 upgrades := []Upgrade{} 80 81 // Get the cluster version 82 clusterVersionStr, clusterVersion, err := versionGetterImpl.ClusterVersion() 83 if err != nil { 84 return upgrades, err 85 } 86 fmt.Printf("[upgrade/versions] Cluster version: %s\n", clusterVersionStr) 87 88 // Get current kubeadm CLI version 89 kubeadmVersionStr, kubeadmVersion, err := versionGetterImpl.KubeadmVersion() 90 if err != nil { 91 return upgrades, err 92 } 93 fmt.Printf("[upgrade/versions] kubeadm version: %s\n", kubeadmVersionStr) 94 95 // Get and output the current latest stable version 96 stableVersionStr, stableVersion, err := versionGetterImpl.VersionFromCILabel("stable", "stable version") 97 if err != nil { 98 fmt.Printf("[upgrade/versions] WARNING: %v\n", err) 99 fmt.Println("[upgrade/versions] WARNING: Falling back to current kubeadm version as latest stable version") 100 stableVersionStr, stableVersion = kubeadmVersionStr, kubeadmVersion 101 } else { 102 fmt.Printf("[upgrade/versions] Target version: %s\n", stableVersionStr) 103 } 104 105 // Get the kubelet versions in the cluster 106 kubeletVersions, err := versionGetterImpl.KubeletVersions() 107 if err != nil { 108 return upgrades, err 109 } 110 111 // Get current stacked etcd version on the local node 112 var etcdVersion string 113 if !externalEtcd { 114 etcdVersion, err = GetEtcdImageTagFromStaticPod(manifestsDir) 115 if err != nil { 116 return upgrades, err 117 } 118 } 119 120 dnsVersion, err := dns.DeployedDNSAddon(client) 121 if err != nil { 122 return nil, err 123 } 124 125 // Construct a descriptor for the current state of the world 126 beforeState := ClusterState{ 127 KubeVersion: clusterVersionStr, 128 DNSVersion: dnsVersion, 129 KubeadmVersion: kubeadmVersionStr, 130 KubeletVersions: kubeletVersions, 131 EtcdVersion: etcdVersion, 132 } 133 134 // Do a "dumb guess" that a new minor upgrade is available just because the latest stable version is higher than the cluster version 135 // This guess will be corrected once we know if there is a patch version available 136 canDoMinorUpgrade := clusterVersion.LessThan(stableVersion) 137 138 // A patch version doesn't exist if the cluster version is higher than or equal to the current stable version 139 // in the case that a user is trying to upgrade from, let's say, v1.8.0-beta.2 to v1.8.0-rc.1 (given we support such upgrades experimentally) 140 // a stable-1.8 branch doesn't exist yet. Hence this check. 141 if patchVersionBranchExists(clusterVersion, stableVersion) { 142 currentBranch := getBranchFromVersion(clusterVersionStr) 143 versionLabel := fmt.Sprintf("stable-%s", currentBranch) 144 description := fmt.Sprintf("version in the v%s series", currentBranch) 145 146 // Get and output the latest patch version for the cluster branch 147 patchVersionStr, patchVersion, err := versionGetterImpl.VersionFromCILabel(versionLabel, description) 148 if err != nil { 149 fmt.Printf("[upgrade/versions] WARNING: %v\n", err) 150 } else { 151 fmt.Printf("[upgrade/versions] Latest %s: %s\n", description, patchVersionStr) 152 153 // Check if a minor version upgrade is possible when a patch release exists 154 // It's only possible if the latest patch version is higher than the current patch version 155 // If that's the case, they must be on different branches => a newer minor version can be upgraded to 156 canDoMinorUpgrade = minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion) 157 158 // If the cluster version is lower than the newest patch version, we should inform about the possible upgrade 159 if patchUpgradePossible(clusterVersion, patchVersion) { 160 161 // The kubeadm version has to be upgraded to the latest patch version 162 newKubeadmVer := patchVersionStr 163 if kubeadmVersion.AtLeast(patchVersion) { 164 // In this case, the kubeadm CLI version is new enough. Don't display an update suggestion for kubeadm by making .NewKubeadmVersion equal .CurrentKubeadmVersion 165 newKubeadmVer = kubeadmVersionStr 166 } 167 168 upgrades = append(upgrades, Upgrade{ 169 Description: description, 170 Before: beforeState, 171 After: ClusterState{ 172 KubeVersion: patchVersionStr, 173 DNSVersion: kubeadmconstants.CoreDNSVersion, 174 KubeadmVersion: newKubeadmVer, 175 EtcdVersion: getSuggestedEtcdVersion(externalEtcd, patchVersionStr), 176 // KubeletVersions is unset here as it is not used anywhere in .After 177 }, 178 }) 179 } 180 } 181 } 182 183 if canDoMinorUpgrade { 184 upgrades = append(upgrades, Upgrade{ 185 Description: "stable version", 186 Before: beforeState, 187 After: ClusterState{ 188 KubeVersion: stableVersionStr, 189 DNSVersion: kubeadmconstants.CoreDNSVersion, 190 KubeadmVersion: stableVersionStr, 191 EtcdVersion: getSuggestedEtcdVersion(externalEtcd, stableVersionStr), 192 // KubeletVersions is unset here as it is not used anywhere in .After 193 }, 194 }) 195 } 196 197 if experimentalUpgradesAllowed || rcUpgradesAllowed { 198 // dl.k8s.io/release/latest.txt is ALWAYS an alpha.X version 199 // dl.k8s.io/release/latest-1.X.txt is first v1.X.0-alpha.0 -> v1.X.0-alpha.Y, then v1.X.0-beta.0 to v1.X.0-beta.Z, then v1.X.0-rc.1 to v1.X.0-rc.W. 200 // After the v1.X.0 release, latest-1.X.txt is always a beta.0 version. Let's say the latest stable version on the v1.7 branch is v1.7.3, then the 201 // latest-1.7 version is v1.7.4-beta.0 202 203 // Worth noticing is that when the release-1.X branch is cut; there are two versions tagged: v1.X.0-beta.0 AND v1.(X+1).alpha.0 204 // The v1.(X+1).alpha.0 is pretty much useless and should just be ignored, as more betas may be released that have more features than the initial v1.(X+1).alpha.0 205 206 // So what we do below is getting the latest overall version, always an v1.X.0-alpha.Y version. Then we get latest-1.(X-1) version. This version may be anything 207 // between v1.(X-1).0-beta.0 and v1.(X-1).Z-beta.0. At some point in time, latest-1.(X-1) will point to v1.(X-1).0-rc.1. Then we should show it. 208 209 // The flow looks like this (with time on the X axis): 210 // v1.8.0-alpha.1 -> v1.8.0-alpha.2 -> v1.8.0-alpha.3 | release-1.8 branch | v1.8.0-beta.0 -> v1.8.0-beta.1 -> v1.8.0-beta.2 -> v1.8.0-rc.1 -> v1.8.0 -> v1.8.1 211 // v1.9.0-alpha.0 -> v1.9.0-alpha.1 -> v1.9.0-alpha.2 212 213 // Get and output the current latest unstable version 214 latestVersionStr, latestVersion, err := versionGetterImpl.VersionFromCILabel("latest", "experimental version") 215 if err != nil { 216 return upgrades, err 217 } 218 fmt.Printf("[upgrade/versions] Latest %s: %s\n", "experimental version", latestVersionStr) 219 220 minorUnstable := latestVersion.Components()[1] 221 // Get and output the current latest unstable version 222 previousBranch := fmt.Sprintf("latest-1.%d", minorUnstable-1) 223 previousBranchLatestVersionStr, previousBranchLatestVersion, err := versionGetterImpl.VersionFromCILabel(previousBranch, "previous version") 224 if err != nil { 225 return upgrades, err 226 } 227 fmt.Printf("[upgrade/versions] Latest %s: %s\n", "previous version", previousBranchLatestVersionStr) 228 229 // If that previous latest version is an RC, RCs are allowed and the cluster version is lower than the RC version, show the upgrade 230 if rcUpgradesAllowed && rcUpgradePossible(clusterVersion, previousBranchLatestVersion) { 231 upgrades = append(upgrades, Upgrade{ 232 Description: "release candidate version", 233 Before: beforeState, 234 After: ClusterState{ 235 KubeVersion: previousBranchLatestVersionStr, 236 DNSVersion: kubeadmconstants.CoreDNSVersion, 237 KubeadmVersion: previousBranchLatestVersionStr, 238 EtcdVersion: getSuggestedEtcdVersion(externalEtcd, previousBranchLatestVersionStr), 239 // KubeletVersions is unset here as it is not used anywhere in .After 240 }, 241 }) 242 } 243 244 // Show the possibility if experimental upgrades are allowed 245 if experimentalUpgradesAllowed && clusterVersion.LessThan(latestVersion) { 246 247 // Default to assume that the experimental version to show is the unstable one 248 unstableKubeVersion := latestVersionStr 249 250 // Ẃe should not display alpha.0. The previous branch's beta/rc versions are more relevant due how the kube branching process works. 251 if latestVersion.PreRelease() == "alpha.0" { 252 unstableKubeVersion = previousBranchLatestVersionStr 253 } 254 255 upgrades = append(upgrades, Upgrade{ 256 Description: "experimental version", 257 Before: beforeState, 258 After: ClusterState{ 259 KubeVersion: unstableKubeVersion, 260 DNSVersion: kubeadmconstants.CoreDNSVersion, 261 KubeadmVersion: unstableKubeVersion, 262 EtcdVersion: getSuggestedEtcdVersion(externalEtcd, unstableKubeVersion), 263 // KubeletVersions is unset here as it is not used anywhere in .After 264 }, 265 }) 266 } 267 } 268 269 // Add a newline in the end of this output to leave some space to the next output section 270 fmt.Println("") 271 272 return upgrades, nil 273} 274 275func getBranchFromVersion(version string) string { 276 v := versionutil.MustParseGeneric(version) 277 return fmt.Sprintf("%d.%d", v.Major(), v.Minor()) 278} 279 280func patchVersionBranchExists(clusterVersion, stableVersion *versionutil.Version) bool { 281 return stableVersion.AtLeast(clusterVersion) 282} 283 284func patchUpgradePossible(clusterVersion, patchVersion *versionutil.Version) bool { 285 return clusterVersion.LessThan(patchVersion) 286} 287 288func rcUpgradePossible(clusterVersion, previousBranchLatestVersion *versionutil.Version) bool { 289 return strings.HasPrefix(previousBranchLatestVersion.PreRelease(), "rc") && clusterVersion.LessThan(previousBranchLatestVersion) 290} 291 292func minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion *versionutil.Version) bool { 293 return patchVersion.LessThan(stableVersion) 294} 295 296func getSuggestedEtcdVersion(externalEtcd bool, kubernetesVersion string) string { 297 if externalEtcd { 298 return "" 299 } 300 etcdVersion, warning, err := kubeadmconstants.EtcdSupportedVersion(kubeadmconstants.SupportedEtcdVersion, kubernetesVersion) 301 if err != nil { 302 klog.Warningf("[upgrade/versions] could not retrieve an etcd version for the target Kubernetes version: %v", err) 303 return "N/A" 304 } 305 if warning != nil { 306 klog.Warningf("[upgrade/versions] %v", warning) 307 } 308 return etcdVersion.String() 309} 310