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