k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/upgrade/policy.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 "github.com/pkg/errors" 24 25 "k8s.io/apimachinery/pkg/util/version" 26 27 "k8s.io/kubernetes/cmd/kubeadm/app/constants" 28 ) 29 30 const ( 31 // MaximumAllowedMinorVersionUpgradeSkew describes how many minor versions kubeadm can upgrade the control plane version in one go 32 MaximumAllowedMinorVersionUpgradeSkew = 1 33 34 // MaximumAllowedMinorVersionDowngradeSkew describes how many minor versions kubeadm can upgrade the control plane version in one go 35 MaximumAllowedMinorVersionDowngradeSkew = 1 36 37 // MaximumAllowedMinorVersionKubeletSkew describes how many minor versions the control plane version and the kubelet can skew in a kubeadm cluster 38 MaximumAllowedMinorVersionKubeletSkew = 3 39 ) 40 41 // VersionSkewPolicyErrors describes version skew errors that might be seen during the validation process in EnforceVersionPolicies 42 type VersionSkewPolicyErrors struct { 43 Mandatory []error 44 Skippable []error 45 } 46 47 // EnforceVersionPolicies enforces that the proposed new version is compatible with all the different version skew policies 48 func EnforceVersionPolicies(versionGetter VersionGetter, newK8sVersionStr string, newK8sVersion *version.Version, allowExperimentalUpgrades, allowRCUpgrades bool) *VersionSkewPolicyErrors { 49 50 skewErrors := &VersionSkewPolicyErrors{ 51 Mandatory: []error{}, 52 Skippable: []error{}, 53 } 54 55 clusterVersionStr, clusterVersion, err := versionGetter.ClusterVersion() 56 if err != nil { 57 // This case can't be forced: kubeadm has to be able to lookup cluster version for upgrades to work 58 skewErrors.Mandatory = append(skewErrors.Mandatory, errors.Wrap(err, "Unable to fetch cluster version")) 59 return skewErrors 60 } 61 fmt.Printf("[upgrade/versions] Cluster version: %s\n", clusterVersionStr) 62 63 kubeadmVersionStr, kubeadmVersion, err := versionGetter.KubeadmVersion() 64 if err != nil { 65 // This case can't be forced: kubeadm has to be able to lookup its version for upgrades to work 66 skewErrors.Mandatory = append(skewErrors.Mandatory, errors.Wrap(err, "Unable to fetch kubeadm version")) 67 return skewErrors 68 } 69 fmt.Printf("[upgrade/versions] kubeadm version: %s\n", kubeadmVersionStr) 70 71 kubeletVersions, err := versionGetter.KubeletVersions() 72 if err != nil { 73 // This is a non-critical error; continue although kubeadm couldn't look this up 74 skewErrors.Skippable = append(skewErrors.Skippable, errors.Wrap(err, "Unable to fetch kubelet version")) 75 } 76 77 // Make sure the new version is a supported version (higher than the minimum one supported) 78 if !newK8sVersion.AtLeast(constants.MinimumControlPlaneVersion) { 79 // This must not happen, kubeadm always supports a minimum version; and we can't go below that 80 skewErrors.Mandatory = append(skewErrors.Mandatory, errors.Errorf("Specified version to upgrade to %q is equal to or lower than the minimum supported version %q. Please specify a higher version to upgrade to", newK8sVersionStr, clusterVersionStr)) 81 } 82 83 // kubeadm doesn't support upgrades between two minor versions; e.g. a v1.7 -> v1.9 upgrade is not supported right away 84 if newK8sVersion.Minor() > clusterVersion.Minor()+MaximumAllowedMinorVersionUpgradeSkew { 85 tooLargeUpgradeSkewErr := errors.Errorf("Specified version to upgrade to %q is too high; kubeadm can upgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionUpgradeSkew) 86 // If the version that we're about to upgrade to is a released version, we should fully enforce this policy 87 // If the version is a CI/dev/experimental version, it's okay to jump two minor version steps, but then require the -f flag 88 if len(newK8sVersion.PreRelease()) == 0 { 89 skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeUpgradeSkewErr) 90 } else { 91 skewErrors.Skippable = append(skewErrors.Skippable, tooLargeUpgradeSkewErr) 92 } 93 } 94 95 // kubeadm doesn't support downgrades between two minor versions; e.g. a v1.9 -> v1.7 downgrade is not supported right away 96 if newK8sVersion.Minor() < clusterVersion.Minor()-MaximumAllowedMinorVersionDowngradeSkew { 97 tooLargeDowngradeSkewErr := errors.Errorf("Specified version to downgrade to %q is too low; kubeadm can downgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionDowngradeSkew) 98 // If the version that we're about to downgrade to is a released version, we should fully enforce this policy 99 // If the version is a CI/dev/experimental version, it's okay to jump two minor version steps, but then require the -f flag 100 if len(newK8sVersion.PreRelease()) == 0 { 101 skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeDowngradeSkewErr) 102 } else { 103 skewErrors.Skippable = append(skewErrors.Skippable, tooLargeDowngradeSkewErr) 104 } 105 } 106 107 // If the kubeadm version is lower than what we want to upgrade to; error 108 if kubeadmVersion.LessThan(newK8sVersion) { 109 if newK8sVersion.Minor() > kubeadmVersion.Minor() { 110 tooLargeKubeadmSkew := errors.Errorf("Specified version to upgrade to %q is at least one minor release higher than the kubeadm minor release (%d > %d). Such an upgrade is not supported", newK8sVersionStr, newK8sVersion.Minor(), kubeadmVersion.Minor()) 111 // This is unsupported; kubeadm has no idea how it should handle a newer minor release than itself 112 // If the version is a CI/dev/experimental version though, lower the severity of this check, but then require the -f flag 113 if len(newK8sVersion.PreRelease()) == 0 { 114 skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeKubeadmSkew) 115 } else { 116 skewErrors.Skippable = append(skewErrors.Skippable, tooLargeKubeadmSkew) 117 } 118 } else { 119 // Upgrading to a higher patch version than kubeadm is ok if the user specifies --force. Not recommended, but possible. 120 skewErrors.Skippable = append(skewErrors.Skippable, errors.Errorf("Specified version to upgrade to %q is higher than the kubeadm version %q. Upgrade kubeadm first using the tool you used to install kubeadm", newK8sVersionStr, kubeadmVersionStr)) 121 } 122 } 123 124 if kubeadmVersion.Major() > newK8sVersion.Major() || 125 kubeadmVersion.Minor() > newK8sVersion.Minor() { 126 skewErrors.Skippable = append(skewErrors.Skippable, errors.Errorf("Kubeadm version %s can only be used to upgrade to Kubernetes version %d.%d", kubeadmVersionStr, kubeadmVersion.Major(), kubeadmVersion.Minor())) 127 } 128 129 // Detect if the version is unstable and the user didn't allow that 130 if err = detectUnstableVersionError(newK8sVersion, newK8sVersionStr, allowExperimentalUpgrades, allowRCUpgrades); err != nil { 131 skewErrors.Skippable = append(skewErrors.Skippable, err) 132 } 133 134 // Detect if there are too old kubelets in the cluster 135 // Check for nil here since this is the only case where kubeletVersions can be nil; if KubeletVersions() returned an error 136 // However, it's okay to skip that check 137 if kubeletVersions != nil { 138 if err = detectTooOldKubelets(newK8sVersion, kubeletVersions); err != nil { 139 skewErrors.Skippable = append(skewErrors.Skippable, err) 140 } 141 } 142 143 // If we did not see any errors, return nil 144 if len(skewErrors.Skippable) == 0 && len(skewErrors.Mandatory) == 0 { 145 return nil 146 } 147 148 // Uh oh, we encountered one or more errors, return them 149 return skewErrors 150 } 151 152 // detectUnstableVersionError is a helper function for detecting if the unstable version (if specified) is allowed to be used 153 func detectUnstableVersionError(newK8sVersion *version.Version, newK8sVersionStr string, allowExperimentalUpgrades, allowRCUpgrades bool) error { 154 // Short-circuit quickly if this is not an unstable version 155 if len(newK8sVersion.PreRelease()) == 0 { 156 return nil 157 } 158 // If the user has specified that unstable versions are fine, then no error should be returned 159 if allowExperimentalUpgrades { 160 return nil 161 } 162 // If this is a release candidate and we allow such ones, everything's fine 163 if strings.HasPrefix(newK8sVersion.PreRelease(), "rc") && allowRCUpgrades { 164 return nil 165 } 166 167 return errors.Errorf("Specified version to upgrade to %q is an unstable version and such upgrades weren't allowed via setting the --allow-*-upgrades flags", newK8sVersionStr) 168 } 169 170 // detectTooOldKubelets errors out if the kubelet versions are so old that an unsupported skew would happen if the cluster was upgraded 171 func detectTooOldKubelets(newK8sVersion *version.Version, kubeletVersions map[string][]string) error { 172 var tooOldKubeletVersions []string 173 for versionStr := range kubeletVersions { 174 175 kubeletVersion, err := version.ParseSemantic(versionStr) 176 if err != nil { 177 return errors.Errorf("couldn't parse kubelet version %s", versionStr) 178 } 179 180 if newK8sVersion.Minor() > kubeletVersion.Minor()+MaximumAllowedMinorVersionKubeletSkew { 181 tooOldKubeletVersions = append(tooOldKubeletVersions, versionStr) 182 } 183 } 184 if len(tooOldKubeletVersions) == 0 { 185 return nil 186 } 187 188 return errors.Errorf("There are kubelets in this cluster that are too old that have these versions %v", tooOldKubeletVersions) 189 }