123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- /*
- Copyright 2017 The Kubernetes 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 upgrade
- import (
- "fmt"
- "strings"
- versionutil "k8s.io/apimachinery/pkg/util/version"
- 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/phases/addons/dns"
- etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
- )
- // Upgrade defines an upgrade possibility to upgrade from a current version to a new one
- type Upgrade struct {
- Description string
- Before ClusterState
- After ClusterState
- }
- // CanUpgradeKubelets returns whether an upgrade of any kubelet in the cluster is possible
- func (u *Upgrade) CanUpgradeKubelets() bool {
- // If there are multiple different versions now, an upgrade is possible (even if only for a subset of the nodes)
- if len(u.Before.KubeletVersions) > 1 {
- return true
- }
- // Don't report something available for upgrade if we don't know the current state
- if len(u.Before.KubeletVersions) == 0 {
- return false
- }
- // if the same version number existed both before and after, we don't have to upgrade it
- _, sameVersionFound := u.Before.KubeletVersions[u.After.KubeVersion]
- return !sameVersionFound
- }
- // CanUpgradeEtcd returns whether an upgrade of etcd is possible
- func (u *Upgrade) CanUpgradeEtcd() bool {
- return u.Before.EtcdVersion != u.After.EtcdVersion
- }
- // ClusterState describes the state of certain versions for a cluster
- type ClusterState struct {
- // KubeVersion describes the version of the Kubernetes API Server, Controller Manager, Scheduler and Proxy.
- KubeVersion string
- // DNSType describes the type of DNS add-on used in the cluster.
- DNSType kubeadmapi.DNSAddOnType
- // DNSVersion describes the version of the DNS add-on.
- DNSVersion string
- // KubeadmVersion describes the version of the kubeadm CLI
- KubeadmVersion string
- // KubeletVersions is a map with a version number linked to the amount of kubelets running that version in the cluster
- KubeletVersions map[string]uint16
- // EtcdVersion represents the version of etcd used in the cluster
- EtcdVersion string
- }
- // GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which
- // kinds of upgrades can be performed
- func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesAllowed, rcUpgradesAllowed bool, etcdClient etcdutil.ClusterInterrogator, dnsType kubeadmapi.DNSAddOnType, client clientset.Interface) ([]Upgrade, error) {
- fmt.Println("[upgrade] Fetching available versions to upgrade to")
- // Collect the upgrades kubeadm can do in this list
- upgrades := []Upgrade{}
- // Get the cluster version
- clusterVersionStr, clusterVersion, err := versionGetterImpl.ClusterVersion()
- if err != nil {
- return upgrades, err
- }
- // Get current kubeadm CLI version
- kubeadmVersionStr, kubeadmVersion, err := versionGetterImpl.KubeadmVersion()
- if err != nil {
- return upgrades, err
- }
- // Get and output the current latest stable version
- stableVersionStr, stableVersion, err := versionGetterImpl.VersionFromCILabel("stable", "stable version")
- if err != nil {
- fmt.Printf("[upgrade/versions] WARNING: %v\n", err)
- fmt.Println("[upgrade/versions] WARNING: Falling back to current kubeadm version as latest stable version")
- stableVersionStr, stableVersion = kubeadmVersionStr, kubeadmVersion
- }
- // Get the kubelet versions in the cluster
- kubeletVersions, err := versionGetterImpl.KubeletVersions()
- if err != nil {
- return upgrades, err
- }
- // Get current etcd version
- etcdVersion, err := etcdClient.GetVersion()
- if err != nil {
- return upgrades, err
- }
- currentDNSType, dnsVersion, err := dns.DeployedDNSAddon(client)
- if err != nil {
- return nil, err
- }
- // Construct a descriptor for the current state of the world
- beforeState := ClusterState{
- KubeVersion: clusterVersionStr,
- DNSType: currentDNSType,
- DNSVersion: dnsVersion,
- KubeadmVersion: kubeadmVersionStr,
- KubeletVersions: kubeletVersions,
- EtcdVersion: etcdVersion,
- }
- // Do a "dumb guess" that a new minor upgrade is available just because the latest stable version is higher than the cluster version
- // This guess will be corrected once we know if there is a patch version available
- canDoMinorUpgrade := clusterVersion.LessThan(stableVersion)
- // A patch version doesn't exist if the cluster version is higher than or equal to the current stable version
- // 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)
- // a stable-1.8 branch doesn't exist yet. Hence this check.
- if patchVersionBranchExists(clusterVersion, stableVersion) {
- currentBranch := getBranchFromVersion(clusterVersionStr)
- versionLabel := fmt.Sprintf("stable-%s", currentBranch)
- description := fmt.Sprintf("version in the v%s series", currentBranch)
- // Get and output the latest patch version for the cluster branch
- patchVersionStr, patchVersion, err := versionGetterImpl.VersionFromCILabel(versionLabel, description)
- if err != nil {
- fmt.Printf("[upgrade/versions] WARNING: %v\n", err)
- } else {
- // Check if a minor version upgrade is possible when a patch release exists
- // It's only possible if the latest patch version is higher than the current patch version
- // If that's the case, they must be on different branches => a newer minor version can be upgraded to
- canDoMinorUpgrade = minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion)
- // If the cluster version is lower than the newest patch version, we should inform about the possible upgrade
- if patchUpgradePossible(clusterVersion, patchVersion) {
- // The kubeadm version has to be upgraded to the latest patch version
- newKubeadmVer := patchVersionStr
- if kubeadmVersion.AtLeast(patchVersion) {
- // In this case, the kubeadm CLI version is new enough. Don't display an update suggestion for kubeadm by making .NewKubeadmVersion equal .CurrentKubeadmVersion
- newKubeadmVer = kubeadmVersionStr
- }
- upgrades = append(upgrades, Upgrade{
- Description: description,
- Before: beforeState,
- After: ClusterState{
- KubeVersion: patchVersionStr,
- DNSType: dnsType,
- DNSVersion: kubeadmconstants.GetDNSVersion(dnsType),
- KubeadmVersion: newKubeadmVer,
- EtcdVersion: getSuggestedEtcdVersion(patchVersionStr),
- // KubeletVersions is unset here as it is not used anywhere in .After
- },
- })
- }
- }
- }
- if canDoMinorUpgrade {
- upgrades = append(upgrades, Upgrade{
- Description: "stable version",
- Before: beforeState,
- After: ClusterState{
- KubeVersion: stableVersionStr,
- DNSType: dnsType,
- DNSVersion: kubeadmconstants.GetDNSVersion(dnsType),
- KubeadmVersion: stableVersionStr,
- EtcdVersion: getSuggestedEtcdVersion(stableVersionStr),
- // KubeletVersions is unset here as it is not used anywhere in .After
- },
- })
- }
- if experimentalUpgradesAllowed || rcUpgradesAllowed {
- // dl.k8s.io/release/latest.txt is ALWAYS an alpha.X version
- // 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.
- // 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
- // latest-1.7 version is v1.7.4-beta.0
- // 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
- // 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
- // 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
- // 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.
- // The flow looks like this (with time on the X axis):
- // 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
- // v1.9.0-alpha.0 -> v1.9.0-alpha.1 -> v1.9.0-alpha.2
- // Get and output the current latest unstable version
- latestVersionStr, latestVersion, err := versionGetterImpl.VersionFromCILabel("latest", "experimental version")
- if err != nil {
- return upgrades, err
- }
- minorUnstable := latestVersion.Components()[1]
- // Get and output the current latest unstable version
- previousBranch := fmt.Sprintf("latest-1.%d", minorUnstable-1)
- previousBranchLatestVersionStr, previousBranchLatestVersion, err := versionGetterImpl.VersionFromCILabel(previousBranch, "")
- if err != nil {
- return upgrades, err
- }
- // If that previous latest version is an RC, RCs are allowed and the cluster version is lower than the RC version, show the upgrade
- if rcUpgradesAllowed && rcUpgradePossible(clusterVersion, previousBranchLatestVersion) {
- upgrades = append(upgrades, Upgrade{
- Description: "release candidate version",
- Before: beforeState,
- After: ClusterState{
- KubeVersion: previousBranchLatestVersionStr,
- DNSType: dnsType,
- DNSVersion: kubeadmconstants.GetDNSVersion(dnsType),
- KubeadmVersion: previousBranchLatestVersionStr,
- EtcdVersion: getSuggestedEtcdVersion(previousBranchLatestVersionStr),
- // KubeletVersions is unset here as it is not used anywhere in .After
- },
- })
- }
- // Show the possibility if experimental upgrades are allowed
- if experimentalUpgradesAllowed && clusterVersion.LessThan(latestVersion) {
- // Default to assume that the experimental version to show is the unstable one
- unstableKubeVersion := latestVersionStr
- unstableKubeDNSVersion := kubeadmconstants.GetDNSVersion(dnsType)
- // Ẃ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 = kubeadmconstants.GetDNSVersion(dnsType)
- }
- upgrades = append(upgrades, Upgrade{
- Description: "experimental version",
- Before: beforeState,
- After: ClusterState{
- KubeVersion: unstableKubeVersion,
- DNSType: dnsType,
- DNSVersion: unstableKubeDNSVersion,
- KubeadmVersion: unstableKubeVersion,
- EtcdVersion: getSuggestedEtcdVersion(unstableKubeVersion),
- // KubeletVersions is unset here as it is not used anywhere in .After
- },
- })
- }
- }
- // Add a newline in the end of this output to leave some space to the next output section
- fmt.Println("")
- return upgrades, nil
- }
- func getBranchFromVersion(version string) string {
- v := versionutil.MustParseGeneric(version)
- return fmt.Sprintf("%d.%d", v.Major(), v.Minor())
- }
- func patchVersionBranchExists(clusterVersion, stableVersion *versionutil.Version) bool {
- return stableVersion.AtLeast(clusterVersion)
- }
- func patchUpgradePossible(clusterVersion, patchVersion *versionutil.Version) bool {
- return clusterVersion.LessThan(patchVersion)
- }
- func rcUpgradePossible(clusterVersion, previousBranchLatestVersion *versionutil.Version) bool {
- return strings.HasPrefix(previousBranchLatestVersion.PreRelease(), "rc") && clusterVersion.LessThan(previousBranchLatestVersion)
- }
- func minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion *versionutil.Version) bool {
- return patchVersion.LessThan(stableVersion)
- }
- func getSuggestedEtcdVersion(kubernetesVersion string) string {
- etcdVersion, err := kubeadmconstants.EtcdSupportedVersion(kubernetesVersion)
- if err != nil {
- fmt.Printf("[upgrade/versions] WARNING: No recommended etcd for requested Kubernetes version (%s)\n", kubernetesVersion)
- return "N/A"
- }
- return etcdVersion.String()
- }
|