policy.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package upgrade
  14. import (
  15. "strings"
  16. "github.com/pkg/errors"
  17. "k8s.io/apimachinery/pkg/util/version"
  18. "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  19. )
  20. const (
  21. // MaximumAllowedMinorVersionUpgradeSkew describes how many minor versions kubeadm can upgrade the control plane version in one go
  22. MaximumAllowedMinorVersionUpgradeSkew = 1
  23. // MaximumAllowedMinorVersionDowngradeSkew describes how many minor versions kubeadm can upgrade the control plane version in one go
  24. MaximumAllowedMinorVersionDowngradeSkew = 1
  25. // MaximumAllowedMinorVersionKubeletSkew describes how many minor versions the control plane version and the kubelet can skew in a kubeadm cluster
  26. MaximumAllowedMinorVersionKubeletSkew = 1
  27. )
  28. // VersionSkewPolicyErrors describes version skew errors that might be seen during the validation process in EnforceVersionPolicies
  29. type VersionSkewPolicyErrors struct {
  30. Mandatory []error
  31. Skippable []error
  32. }
  33. // EnforceVersionPolicies enforces that the proposed new version is compatible with all the different version skew policies
  34. func EnforceVersionPolicies(versionGetter VersionGetter, newK8sVersionStr string, newK8sVersion *version.Version, allowExperimentalUpgrades, allowRCUpgrades bool) *VersionSkewPolicyErrors {
  35. skewErrors := &VersionSkewPolicyErrors{
  36. Mandatory: []error{},
  37. Skippable: []error{},
  38. }
  39. clusterVersionStr, clusterVersion, err := versionGetter.ClusterVersion()
  40. if err != nil {
  41. // This case can't be forced: kubeadm has to be able to lookup cluster version for upgrades to work
  42. skewErrors.Mandatory = append(skewErrors.Mandatory, errors.Wrap(err, "Unable to fetch cluster version"))
  43. return skewErrors
  44. }
  45. kubeadmVersionStr, kubeadmVersion, err := versionGetter.KubeadmVersion()
  46. if err != nil {
  47. // This case can't be forced: kubeadm has to be able to lookup its version for upgrades to work
  48. skewErrors.Mandatory = append(skewErrors.Mandatory, errors.Wrap(err, "Unable to fetch kubeadm version"))
  49. return skewErrors
  50. }
  51. kubeletVersions, err := versionGetter.KubeletVersions()
  52. if err != nil {
  53. // This is a non-critical error; continue although kubeadm couldn't look this up
  54. skewErrors.Skippable = append(skewErrors.Skippable, errors.Wrap(err, "Unable to fetch kubelet version"))
  55. }
  56. // Make sure the new version is a supported version (higher than the minimum one supported)
  57. if constants.MinimumControlPlaneVersion.AtLeast(newK8sVersion) {
  58. // This must not happen, kubeadm always supports a minimum version; and we can't go below that
  59. 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))
  60. }
  61. // kubeadm doesn't support upgrades between two minor versions; e.g. a v1.7 -> v1.9 upgrade is not supported right away
  62. if newK8sVersion.Minor() > clusterVersion.Minor()+MaximumAllowedMinorVersionUpgradeSkew {
  63. tooLargeUpgradeSkewErr := errors.Errorf("Specified version to upgrade to %q is too high; kubeadm can upgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionUpgradeSkew)
  64. // If the version that we're about to upgrade to is a released version, we should fully enforce this policy
  65. // If the version is a CI/dev/experimental version, it's okay to jump two minor version steps, but then require the -f flag
  66. if len(newK8sVersion.PreRelease()) == 0 {
  67. skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeUpgradeSkewErr)
  68. } else {
  69. skewErrors.Skippable = append(skewErrors.Skippable, tooLargeUpgradeSkewErr)
  70. }
  71. }
  72. // kubeadm doesn't support downgrades between two minor versions; e.g. a v1.9 -> v1.7 downgrade is not supported right away
  73. if newK8sVersion.Minor() < clusterVersion.Minor()-MaximumAllowedMinorVersionDowngradeSkew {
  74. tooLargeDowngradeSkewErr := errors.Errorf("Specified version to downgrade to %q is too low; kubeadm can downgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionDowngradeSkew)
  75. // If the version that we're about to downgrade to is a released version, we should fully enforce this policy
  76. // If the version is a CI/dev/experimental version, it's okay to jump two minor version steps, but then require the -f flag
  77. if len(newK8sVersion.PreRelease()) == 0 {
  78. skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeDowngradeSkewErr)
  79. } else {
  80. skewErrors.Skippable = append(skewErrors.Skippable, tooLargeDowngradeSkewErr)
  81. }
  82. }
  83. // If the kubeadm version is lower than what we want to upgrade to; error
  84. if kubeadmVersion.LessThan(newK8sVersion) {
  85. if newK8sVersion.Minor() > kubeadmVersion.Minor() {
  86. 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())
  87. // This is unsupported; kubeadm has no idea how it should handle a newer minor release than itself
  88. // If the version is a CI/dev/experimental version though, lower the severity of this check, but then require the -f flag
  89. if len(newK8sVersion.PreRelease()) == 0 {
  90. skewErrors.Mandatory = append(skewErrors.Mandatory, tooLargeKubeadmSkew)
  91. } else {
  92. skewErrors.Skippable = append(skewErrors.Skippable, tooLargeKubeadmSkew)
  93. }
  94. } else {
  95. // Upgrading to a higher patch version than kubeadm is ok if the user specifies --force. Not recommended, but possible.
  96. 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))
  97. }
  98. }
  99. if kubeadmVersion.Major() > newK8sVersion.Major() ||
  100. kubeadmVersion.Minor() > newK8sVersion.Minor() {
  101. 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()))
  102. }
  103. // Detect if the version is unstable and the user didn't allow that
  104. if err = detectUnstableVersionError(newK8sVersion, newK8sVersionStr, allowExperimentalUpgrades, allowRCUpgrades); err != nil {
  105. skewErrors.Skippable = append(skewErrors.Skippable, err)
  106. }
  107. // Detect if there are too old kubelets in the cluster
  108. // Check for nil here since this is the only case where kubeletVersions can be nil; if KubeletVersions() returned an error
  109. // However, it's okay to skip that check
  110. if kubeletVersions != nil {
  111. if err = detectTooOldKubelets(newK8sVersion, kubeletVersions); err != nil {
  112. skewErrors.Skippable = append(skewErrors.Skippable, err)
  113. }
  114. }
  115. // If we did not see any errors, return nil
  116. if len(skewErrors.Skippable) == 0 && len(skewErrors.Mandatory) == 0 {
  117. return nil
  118. }
  119. // Uh oh, we encountered one or more errors, return them
  120. return skewErrors
  121. }
  122. // detectUnstableVersionError is a helper function for detecting if the unstable version (if specified) is allowed to be used
  123. func detectUnstableVersionError(newK8sVersion *version.Version, newK8sVersionStr string, allowExperimentalUpgrades, allowRCUpgrades bool) error {
  124. // Short-circuit quickly if this is not an unstable version
  125. if len(newK8sVersion.PreRelease()) == 0 {
  126. return nil
  127. }
  128. // If the user has specified that unstable versions are fine, then no error should be returned
  129. if allowExperimentalUpgrades {
  130. return nil
  131. }
  132. // If this is a release candidate and we allow such ones, everything's fine
  133. if strings.HasPrefix(newK8sVersion.PreRelease(), "rc") && allowRCUpgrades {
  134. return nil
  135. }
  136. 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)
  137. }
  138. // detectTooOldKubelets errors out if the kubelet versions are so old that an unsupported skew would happen if the cluster was upgraded
  139. func detectTooOldKubelets(newK8sVersion *version.Version, kubeletVersions map[string]uint16) error {
  140. tooOldKubeletVersions := []string{}
  141. for versionStr := range kubeletVersions {
  142. kubeletVersion, err := version.ParseSemantic(versionStr)
  143. if err != nil {
  144. return errors.Errorf("couldn't parse kubelet version %s", versionStr)
  145. }
  146. if newK8sVersion.Minor() > kubeletVersion.Minor()+MaximumAllowedMinorVersionKubeletSkew {
  147. tooOldKubeletVersions = append(tooOldKubeletVersions, versionStr)
  148. }
  149. }
  150. if len(tooOldKubeletVersions) == 0 {
  151. return nil
  152. }
  153. return errors.Errorf("There are kubelets in this cluster that are too old that have these versions %v", tooOldKubeletVersions)
  154. }