1
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
33 type Upgrade struct {
34 Description string
35 Before ClusterState
36 After ClusterState
37 }
38
39
40 func (u *Upgrade) CanUpgradeKubelets() bool {
41
42 if len(u.Before.KubeletVersions) > 1 {
43 return true
44 }
45
46 if len(u.Before.KubeletVersions) == 0 {
47 return false
48 }
49
50
51 _, sameVersionFound := u.Before.KubeletVersions[u.After.KubeVersion]
52 return !sameVersionFound
53 }
54
55
56 type ClusterState struct {
57
58 KubeVersion string
59
60 DNSVersion string
61
62 KubeadmVersion string
63
64 EtcdVersion string
65
66
67
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
76
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
81 var upgrades []Upgrade
82
83
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
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
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
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
124 kubeletVersions, err := versionGetterImpl.KubeletVersions()
125 if err != nil {
126 return upgrades, err
127 }
128
129
130 kubeControllerManagerVersions, err := versionGetterImpl.ComponentVersions(kubeadmconstants.KubeControllerManager)
131 if err != nil {
132 return upgrades, err
133 }
134
135
136 kubeSchedulerVersions, err := versionGetterImpl.ComponentVersions(kubeadmconstants.KubeScheduler)
137 if err != nil {
138 return upgrades, err
139 }
140
141
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
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
166
167 canDoMinorUpgrade := clusterVersion.LessThan(stableVersion)
168
169
170
171
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
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
185
186
187 canDoMinorUpgrade = minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion)
188
189
190 if patchUpgradePossible(clusterVersion, patchVersion) {
191
192
193 newKubeadmVer := patchVersionStr
194 if kubeadmVersion.AtLeast(patchVersion) {
195
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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
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
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
273 if experimentalUpgradesAllowed && clusterVersion.LessThan(latestVersion) {
274
275
276 unstableKubeVersion := latestVersionStr
277
278
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
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 }
352
View as plain text