kubeadm upgrade node
This commit is contained in:
		| @@ -59,6 +59,9 @@ const ( | |||||||
| 	// KubernetesVersion flag sets the Kubernetes version for the control plane. | 	// KubernetesVersion flag sets the Kubernetes version for the control plane. | ||||||
| 	KubernetesVersion = "kubernetes-version" | 	KubernetesVersion = "kubernetes-version" | ||||||
|  |  | ||||||
|  | 	// KubeletVersion flag sets the version for the kubelet config. | ||||||
|  | 	KubeletVersion = "kubelet-version" | ||||||
|  |  | ||||||
| 	// NetworkingDNSDomain flag sets the domain for services, e.g. "myorg.internal". | 	// NetworkingDNSDomain flag sets the domain for services, e.g. "myorg.internal". | ||||||
| 	NetworkingDNSDomain = "service-dns-domain" | 	NetworkingDNSDomain = "service-dns-domain" | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										80
									
								
								cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2019 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 node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/pkg/errors" | ||||||
|  |  | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // NewControlPlane creates a kubeadm workflow phase that implements handling of control-plane upgrade. | ||||||
|  | func NewControlPlane() workflow.Phase { | ||||||
|  | 	phase := workflow.Phase{ | ||||||
|  | 		Name:  "control-plane", | ||||||
|  | 		Short: "Upgrade the control plane instance deployed on this node, if any", | ||||||
|  | 		Run:   runControlPlane(), | ||||||
|  | 		InheritFlags: []string{ | ||||||
|  | 			options.DryRun, | ||||||
|  | 			options.KubeconfigPath, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	return phase | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func runControlPlane() func(c workflow.RunData) error { | ||||||
|  | 	return func(c workflow.RunData) error { | ||||||
|  | 		data, ok := c.(Data) | ||||||
|  | 		if !ok { | ||||||
|  | 			return errors.New("control-plane phase invoked with an invalid data struct") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// if this is not a control-plande node, this phase should not be executed | ||||||
|  | 		if !data.IsControlPlaneNode() { | ||||||
|  | 			fmt.Printf("[upgrade] Skipping phase. Not a control plane node") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// otherwise, retrieve all the info required for control plane upgrade | ||||||
|  | 		cfg := data.Cfg() | ||||||
|  | 		client := data.Client() | ||||||
|  | 		dryRun := data.DryRun() | ||||||
|  | 		etcdUpgrade := data.EtcdUpgrade() | ||||||
|  | 		renewCerts := data.RenewCerts() | ||||||
|  |  | ||||||
|  | 		// Upgrade the control plane and etcd if installed on this node | ||||||
|  | 		fmt.Printf("[upgrade] Upgrading your Static Pod-hosted control plane instance to version %q...\n", cfg.KubernetesVersion) | ||||||
|  | 		if dryRun { | ||||||
|  | 			return upgrade.DryRunStaticPodUpgrade(cfg) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		waiter := apiclient.NewKubeWaiter(data.Client(), upgrade.UpgradeManifestTimeout, os.Stdout) | ||||||
|  |  | ||||||
|  | 		if err := upgrade.PerformStaticPodUpgrade(client, waiter, cfg, etcdUpgrade, renewCerts); err != nil { | ||||||
|  | 			return errors.Wrap(err, "couldn't complete the static pod upgrade") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fmt.Println("[upgrade] The control plane instance for this node was successfully updated!") | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								cmd/kubeadm/app/cmd/phases/upgrade/node/data.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								cmd/kubeadm/app/cmd/phases/upgrade/node/data.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2019 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 node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	clientset "k8s.io/client-go/kubernetes" | ||||||
|  | 	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Data is the interface to use for kubeadm upgrade node phases. | ||||||
|  | // The "nodeData" type from "cmd/upgrade/node.go" must satisfy this interface. | ||||||
|  | type Data interface { | ||||||
|  | 	EtcdUpgrade() bool | ||||||
|  | 	RenewCerts() bool | ||||||
|  | 	DryRun() bool | ||||||
|  | 	KubeletVersion() string | ||||||
|  | 	Cfg() *kubeadmapi.InitConfiguration | ||||||
|  | 	IsControlPlaneNode() bool | ||||||
|  | 	Client() clientset.Interface | ||||||
|  | } | ||||||
							
								
								
									
										126
									
								
								cmd/kubeadm/app/cmd/phases/upgrade/node/kubeletconfig.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								cmd/kubeadm/app/cmd/phases/upgrade/node/kubeletconfig.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2019 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 node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"github.com/pkg/errors" | ||||||
|  |  | ||||||
|  | 	"k8s.io/apimachinery/pkg/util/version" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||||
|  | 	kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" | ||||||
|  | 	dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" | ||||||
|  | 	"k8s.io/kubernetes/pkg/util/normalizer" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	kubeletConfigLongDesc = normalizer.LongDesc(` | ||||||
|  | 		Download the kubelet configuration from a ConfigMap of the form "kubelet-config-1.X" in the cluster, | ||||||
|  | 		where X is the minor version of the kubelet. kubeadm uses the KuberneteVersion field in the kubeadm-config | ||||||
|  | 		ConfigMap to determine what the _desired_ kubelet version is, but the user can override this by using the | ||||||
|  | 		--kubelet-version parameter. | ||||||
|  | 		`) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // NewKubeletConfigPhase creates a kubeadm workflow phase that implements handling of kubelet-config upgrade. | ||||||
|  | func NewKubeletConfigPhase() workflow.Phase { | ||||||
|  | 	phase := workflow.Phase{ | ||||||
|  | 		Name:  "kubelet-config", | ||||||
|  | 		Short: "Upgrade the kubelet configuration for this node", | ||||||
|  | 		Long:  kubeletConfigLongDesc, | ||||||
|  | 		Run:   runKubeletConfigPhase(), | ||||||
|  | 		InheritFlags: []string{ | ||||||
|  | 			options.DryRun, | ||||||
|  | 			options.KubeconfigPath, | ||||||
|  | 			options.KubeletVersion, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	return phase | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func runKubeletConfigPhase() func(c workflow.RunData) error { | ||||||
|  | 	return func(c workflow.RunData) error { | ||||||
|  | 		data, ok := c.(Data) | ||||||
|  | 		if !ok { | ||||||
|  | 			return errors.New("kubelet-config phase invoked with an invalid data struct") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// otherwise, retrieve all the info required for kubelet config upgrade | ||||||
|  | 		cfg := data.Cfg() | ||||||
|  | 		client := data.Client() | ||||||
|  | 		dryRun := data.DryRun() | ||||||
|  |  | ||||||
|  | 		// Set up the kubelet directory to use. If dry-running, this will return a fake directory | ||||||
|  | 		kubeletDir, err := upgrade.GetKubeletDir(dryRun) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Gets the target kubelet version. | ||||||
|  | 		// by default kubelet version is expected to be equal to ClusterConfiguration.KubernetesVersion, but | ||||||
|  | 		// users can specify a different kubelet version (this is a legacy of the original implementation | ||||||
|  | 		// of `kubeam upgrade node config` which we are preserving in order to don't break GA contract) | ||||||
|  | 		kubeletVersionStr := cfg.ClusterConfiguration.KubernetesVersion | ||||||
|  | 		if data.KubeletVersion() != "" && data.KubeletVersion() != kubeletVersionStr { | ||||||
|  | 			kubeletVersionStr = data.KubeletVersion() | ||||||
|  | 			fmt.Printf("[upgrade] Using kubelet config version %s, while kubernetes-version is %s\n", kubeletVersionStr, cfg.ClusterConfiguration.KubernetesVersion) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Parse the desired kubelet version | ||||||
|  | 		kubeletVersion, err := version.ParseSemantic(kubeletVersionStr) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// TODO: Checkpoint the current configuration first so that if something goes wrong it can be recovered | ||||||
|  | 		if err := kubeletphase.DownloadConfig(client, kubeletVersion, kubeletDir); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// If we're dry-running, print the generated manifests | ||||||
|  | 		if dryRun { | ||||||
|  | 			if err := printFilesIfDryRunning(dryRun, kubeletDir); err != nil { | ||||||
|  | 				return errors.Wrap(err, "error printing files on dryrun") | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fmt.Println("[upgrade] The configuration for this node was successfully updated!") | ||||||
|  | 		fmt.Println("[upgrade] Now you should go ahead and upgrade the kubelet package using your package manager.") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // printFilesIfDryRunning prints the Static Pod manifests to stdout and informs about the temporary directory to go and lookup | ||||||
|  | func printFilesIfDryRunning(dryRun bool, kubeletDir string) error { | ||||||
|  | 	if !dryRun { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Print the contents of the upgraded file and pretend like they were in kubeadmconstants.KubeletRunDirectory | ||||||
|  | 	fileToPrint := dryrunutil.FileToPrint{ | ||||||
|  | 		RealPath:  filepath.Join(kubeletDir, constants.KubeletConfigurationFileName), | ||||||
|  | 		PrintPath: filepath.Join(constants.KubeletRunDirectory, constants.KubeletConfigurationFileName), | ||||||
|  | 	} | ||||||
|  | 	return dryrunutil.PrintDryRunFiles([]dryrunutil.FileToPrint{fileToPrint}, os.Stdout) | ||||||
|  | } | ||||||
| @@ -18,7 +18,6 @@ package upgrade | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" |  | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| @@ -31,13 +30,10 @@ import ( | |||||||
| 	cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" | 	cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/features" | 	"k8s.io/kubernetes/cmd/kubeadm/app/features" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" |  | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" | 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" | ||||||
| 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" | 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" | ||||||
| 	configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" | 	configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" | ||||||
| 	dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" |  | ||||||
| 	etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @@ -230,50 +226,9 @@ func PerformControlPlaneUpgrade(flags *applyFlags, client clientset.Interface, w | |||||||
| 	fmt.Printf("[upgrade/apply] Upgrading your Static Pod-hosted control plane to version %q...\n", internalcfg.KubernetesVersion) | 	fmt.Printf("[upgrade/apply] Upgrading your Static Pod-hosted control plane to version %q...\n", internalcfg.KubernetesVersion) | ||||||
|  |  | ||||||
| 	if flags.dryRun { | 	if flags.dryRun { | ||||||
| 		return DryRunStaticPodUpgrade(internalcfg) | 		return upgrade.DryRunStaticPodUpgrade(internalcfg) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Don't save etcd backup directory if etcd is HA, as this could cause corruption | 	// Don't save etcd backup directory if etcd is HA, as this could cause corruption | ||||||
| 	return PerformStaticPodUpgrade(client, waiter, internalcfg, flags.etcdUpgrade, flags.renewCerts) | 	return upgrade.PerformStaticPodUpgrade(client, waiter, internalcfg, flags.etcdUpgrade, flags.renewCerts) | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetPathManagerForUpgrade returns a path manager properly configured for the given InitConfiguration. |  | ||||||
| func GetPathManagerForUpgrade(kubernetesDir string, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade bool) (upgrade.StaticPodPathManager, error) { |  | ||||||
| 	isHAEtcd := etcdutil.CheckConfigurationIsHA(&internalcfg.Etcd) |  | ||||||
| 	return upgrade.NewKubeStaticPodPathManagerUsingTempDirs(kubernetesDir, true, etcdUpgrade && !isHAEtcd) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // PerformStaticPodUpgrade performs the upgrade of the control plane components for a static pod hosted cluster |  | ||||||
| func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade, renewCerts bool) error { |  | ||||||
| 	pathManager, err := GetPathManagerForUpgrade(constants.KubernetesDir, internalcfg, etcdUpgrade) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// The arguments oldEtcdClient and newEtdClient, are uninitialized because passing in the clients allow for mocking the client during testing |  | ||||||
| 	return upgrade.StaticPodControlPlane(client, waiter, pathManager, internalcfg, etcdUpgrade, renewCerts, nil, nil) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // DryRunStaticPodUpgrade fakes an upgrade of the control plane |  | ||||||
| func DryRunStaticPodUpgrade(internalcfg *kubeadmapi.InitConfiguration) error { |  | ||||||
|  |  | ||||||
| 	dryRunManifestDir, err := constants.CreateTempDirForKubeadm("", "kubeadm-upgrade-dryrun") |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer os.RemoveAll(dryRunManifestDir) |  | ||||||
|  |  | ||||||
| 	if err := controlplane.CreateInitStaticPodManifestFiles(dryRunManifestDir, internalcfg); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Print the contents of the upgraded manifests and pretend like they were in /etc/kubernetes/manifests |  | ||||||
| 	files := []dryrunutil.FileToPrint{} |  | ||||||
| 	for _, component := range constants.ControlPlaneComponents { |  | ||||||
| 		realPath := constants.GetStaticPodFilepath(component, dryRunManifestDir) |  | ||||||
| 		outputPath := constants.GetStaticPodFilepath(component, constants.GetStaticPodDirectory()) |  | ||||||
| 		files = append(files, dryrunutil.NewFileToPrint(realPath, outputPath)) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return dryrunutil.PrintDryRunFiles(files, os.Stdout) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,11 +17,7 @@ limitations under the License. | |||||||
| package upgrade | package upgrade | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestSessionIsInteractive(t *testing.T) { | func TestSessionIsInteractive(t *testing.T) { | ||||||
| @@ -65,89 +61,3 @@ func TestSessionIsInteractive(t *testing.T) { | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestGetPathManagerForUpgrade(t *testing.T) { |  | ||||||
|  |  | ||||||
| 	haEtcd := &kubeadmapi.InitConfiguration{ |  | ||||||
| 		ClusterConfiguration: kubeadmapi.ClusterConfiguration{ |  | ||||||
| 			Etcd: kubeadmapi.Etcd{ |  | ||||||
| 				External: &kubeadmapi.ExternalEtcd{ |  | ||||||
| 					Endpoints: []string{"10.100.0.1:2379", "10.100.0.2:2379", "10.100.0.3:2379"}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	noHAEtcd := &kubeadmapi.InitConfiguration{} |  | ||||||
|  |  | ||||||
| 	tests := []struct { |  | ||||||
| 		name             string |  | ||||||
| 		cfg              *kubeadmapi.InitConfiguration |  | ||||||
| 		etcdUpgrade      bool |  | ||||||
| 		shouldDeleteEtcd bool |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			name:             "ha etcd but no etcd upgrade", |  | ||||||
| 			cfg:              haEtcd, |  | ||||||
| 			etcdUpgrade:      false, |  | ||||||
| 			shouldDeleteEtcd: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:             "non-ha etcd with etcd upgrade", |  | ||||||
| 			cfg:              noHAEtcd, |  | ||||||
| 			etcdUpgrade:      true, |  | ||||||
| 			shouldDeleteEtcd: false, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:             "ha etcd and etcd upgrade", |  | ||||||
| 			cfg:              haEtcd, |  | ||||||
| 			etcdUpgrade:      true, |  | ||||||
| 			shouldDeleteEtcd: true, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, test := range tests { |  | ||||||
| 		t.Run(test.name, func(t *testing.T) { |  | ||||||
| 			// Use a temporary directory |  | ||||||
| 			tmpdir, err := ioutil.TempDir("", "TestGetPathManagerForUpgrade") |  | ||||||
| 			if err != nil { |  | ||||||
| 				t.Fatalf("unexpected error making temporary directory: %v", err) |  | ||||||
| 			} |  | ||||||
| 			defer func() { |  | ||||||
| 				os.RemoveAll(tmpdir) |  | ||||||
| 			}() |  | ||||||
|  |  | ||||||
| 			pathmgr, err := GetPathManagerForUpgrade(tmpdir, test.cfg, test.etcdUpgrade) |  | ||||||
| 			if err != nil { |  | ||||||
| 				t.Fatalf("unexpected error creating path manager: %v", err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if _, err := os.Stat(pathmgr.BackupManifestDir()); os.IsNotExist(err) { |  | ||||||
| 				t.Errorf("expected manifest dir %s to exist, but it did not (%v)", pathmgr.BackupManifestDir(), err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if _, err := os.Stat(pathmgr.BackupEtcdDir()); os.IsNotExist(err) { |  | ||||||
| 				t.Errorf("expected etcd dir %s to exist, but it did not (%v)", pathmgr.BackupEtcdDir(), err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if err := pathmgr.CleanupDirs(); err != nil { |  | ||||||
| 				t.Fatalf("unexpected error cleaning up directories: %v", err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if _, err := os.Stat(pathmgr.BackupManifestDir()); os.IsNotExist(err) { |  | ||||||
| 				t.Errorf("expected manifest dir %s to exist, but it did not (%v)", pathmgr.BackupManifestDir(), err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if test.shouldDeleteEtcd { |  | ||||||
| 				if _, err := os.Stat(pathmgr.BackupEtcdDir()); !os.IsNotExist(err) { |  | ||||||
| 					t.Errorf("expected etcd dir %s not to exist, but it did (%v)", pathmgr.BackupEtcdDir(), err) |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				if _, err := os.Stat(pathmgr.BackupEtcdDir()); os.IsNotExist(err) { |  | ||||||
| 					t.Errorf("expected etcd dir %s to exist, but it did not", pathmgr.BackupEtcdDir()) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -17,52 +17,29 @@ limitations under the License. | |||||||
| package upgrade | package upgrade | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" |  | ||||||
|  |  | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
| 	"k8s.io/apimachinery/pkg/util/version" | 	flag "github.com/spf13/pflag" | ||||||
| 	"k8s.io/klog" |  | ||||||
|  | 	clientset "k8s.io/client-go/kubernetes" | ||||||
|  | 	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" | 	"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" | ||||||
| 	cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" | 	phases "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/upgrade/node" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||||
| 	kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" | 	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" |  | ||||||
| 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" |  | ||||||
| 	configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" | 	configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" | ||||||
| 	dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" |  | ||||||
| 	"k8s.io/kubernetes/pkg/util/node" |  | ||||||
| 	"k8s.io/kubernetes/pkg/util/normalizer" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | // nodeOptions defines all the options exposed via flags by kubeadm upgrade node. | ||||||
| 	upgradeNodeConfigLongDesc = normalizer.LongDesc(` | // Please note that this structure includes the public kubeadm config API, but only a subset of the options | ||||||
| 		Download the kubelet configuration from a ConfigMap of the form "kubelet-config-1.X" in the cluster, | // supported by this api will be exposed as a flag. | ||||||
| 		where X is the minor version of the kubelet. kubeadm uses the --kubelet-version parameter to determine | type nodeOptions struct { | ||||||
| 		what the _desired_ kubelet version is. Give |  | ||||||
| 		`) |  | ||||||
|  |  | ||||||
| 	upgradeNodeConfigExample = normalizer.Examples(fmt.Sprintf(` |  | ||||||
| 		# Download the kubelet configuration from the ConfigMap in the cluster. Use a specific desired kubelet version. |  | ||||||
| 		kubeadm upgrade node config --kubelet-version %s |  | ||||||
|  |  | ||||||
| 		# Simulate the downloading of the kubelet configuration from the ConfigMap in the cluster with a specific desired |  | ||||||
| 		# version. Do not change any state locally on the node. |  | ||||||
| 		kubeadm upgrade node config --kubelet-version %[1]s --dry-run |  | ||||||
| 		`, constants.CurrentKubernetesVersion)) |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type nodeUpgradeFlags struct { |  | ||||||
| 	kubeConfigPath    string |  | ||||||
| 	kubeletVersionStr string |  | ||||||
| 	dryRun            bool |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type controlplaneUpgradeFlags struct { |  | ||||||
| 	kubeConfigPath   string | 	kubeConfigPath   string | ||||||
|  | 	kubeletVersion   string | ||||||
| 	advertiseAddress string | 	advertiseAddress string | ||||||
| 	nodeName         string | 	nodeName         string | ||||||
| 	etcdUpgrade      bool | 	etcdUpgrade      bool | ||||||
| @@ -70,170 +47,217 @@ type controlplaneUpgradeFlags struct { | |||||||
| 	dryRun           bool | 	dryRun           bool | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // compile-time assert that the local data object satisfies the phases data interface. | ||||||
|  | var _ phases.Data = &nodeData{} | ||||||
|  |  | ||||||
|  | // nodeData defines all the runtime information used when running the kubeadm upgrade node worklow; | ||||||
|  | // this data is shared across all the phases that are included in the workflow. | ||||||
|  | type nodeData struct { | ||||||
|  | 	etcdUpgrade        bool | ||||||
|  | 	renewCerts         bool | ||||||
|  | 	dryRun             bool | ||||||
|  | 	kubeletVersion     string | ||||||
|  | 	cfg                *kubeadmapi.InitConfiguration | ||||||
|  | 	isControlPlaneNode bool | ||||||
|  | 	client             clientset.Interface | ||||||
|  | } | ||||||
|  |  | ||||||
| // NewCmdNode returns the cobra command for `kubeadm upgrade node` | // NewCmdNode returns the cobra command for `kubeadm upgrade node` | ||||||
| func NewCmdNode() *cobra.Command { | func NewCmdNode() *cobra.Command { | ||||||
|  | 	nodeOptions := newNodeOptions() | ||||||
|  | 	nodeRunner := workflow.NewRunner() | ||||||
|  |  | ||||||
| 	cmd := &cobra.Command{ | 	cmd := &cobra.Command{ | ||||||
| 		Use:   "node", | 		Use:   "node", | ||||||
| 		Short: "Upgrade commands for a node in the cluster. Currently only support upgrading the configuration, not the kubelet itself", | 		Short: "Upgrade commands for a node in the cluster", | ||||||
| 		RunE:  cmdutil.SubCmdRunE("node"), | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
|  | 			err := nodeRunner.Run(args) | ||||||
|  | 			kubeadmutil.CheckErr(err) | ||||||
|  | 		}, | ||||||
|  | 		Args: cobra.NoArgs, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// adds flags to the node command | ||||||
|  | 	// flags could be eventually inherited by the sub-commands automatically generated for phases | ||||||
|  | 	addUpgradeNodeFlags(cmd.Flags(), nodeOptions) | ||||||
|  |  | ||||||
|  | 	// initialize the workflow runner with the list of phases | ||||||
|  | 	nodeRunner.AppendPhase(phases.NewControlPlane()) | ||||||
|  | 	nodeRunner.AppendPhase(phases.NewKubeletConfigPhase()) | ||||||
|  |  | ||||||
|  | 	// sets the data builder function, that will be used by the runner | ||||||
|  | 	// both when running the entire workflow or single phases | ||||||
|  | 	nodeRunner.SetDataInitializer(func(cmd *cobra.Command, args []string) (workflow.RunData, error) { | ||||||
|  | 		return newNodeData(cmd, args, nodeOptions) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	// binds the Runner to kubeadm upgrade node command by altering | ||||||
|  | 	// command help, adding --skip-phases flag and by adding phases subcommands | ||||||
|  | 	nodeRunner.BindToCommand(cmd) | ||||||
|  |  | ||||||
|  | 	// upgrade node config command is subject to GA deprecation policy, so we should deprecate it | ||||||
|  | 	// and keep it here for one year or three releases - the longer of the two - starting from v1.15 included | ||||||
| 	cmd.AddCommand(NewCmdUpgradeNodeConfig()) | 	cmd.AddCommand(NewCmdUpgradeNodeConfig()) | ||||||
|  | 	// upgrade node experimental control plane can be removed, but we are keeping it for one more cycle | ||||||
| 	cmd.AddCommand(NewCmdUpgradeControlPlane()) | 	cmd.AddCommand(NewCmdUpgradeControlPlane()) | ||||||
|  |  | ||||||
| 	return cmd | 	return cmd | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // newNodeOptions returns a struct ready for being used for creating cmd kubeadm upgrade node flags. | ||||||
|  | func newNodeOptions() *nodeOptions { | ||||||
|  | 	return &nodeOptions{ | ||||||
|  | 		kubeConfigPath: constants.GetKubeletKubeConfigPath(), | ||||||
|  | 		dryRun:         false, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func addUpgradeNodeFlags(flagSet *flag.FlagSet, nodeOptions *nodeOptions) { | ||||||
|  | 	options.AddKubeConfigFlag(flagSet, &nodeOptions.kubeConfigPath) | ||||||
|  | 	flagSet.BoolVar(&nodeOptions.dryRun, options.DryRun, nodeOptions.dryRun, "Do not change any state, just output the actions that would be performed.") | ||||||
|  | 	flagSet.StringVar(&nodeOptions.kubeletVersion, options.KubeletVersion, nodeOptions.kubeletVersion, "The *desired* version for the kubelet config after the upgrade. If not specified, the KubernetesVersion from the kubeadm-config ConfigMap will be used") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // newNodeData returns a new nodeData struct to be used for the execution of the kubeadm upgrade node workflow. | ||||||
|  | // This func takes care of validating nodeOptions passed to the command, and then it converts | ||||||
|  | // options into the internal InitConfiguration type that is used as input all the phases in the kubeadm upgrade node workflow | ||||||
|  | func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions) (*nodeData, error) { | ||||||
|  | 	client, err := getClient(options.kubeConfigPath, options.dryRun) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", options.kubeConfigPath) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// isControlPlane checks if a node is a control-plane node by looking up | ||||||
|  | 	// the kube-apiserver manifest file | ||||||
|  | 	isControlPlaneNode := true | ||||||
|  | 	filepath := kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeAPIServer, kubeadmconstants.GetStaticPodDirectory()) | ||||||
|  | 	if _, err := os.Stat(filepath); os.IsNotExist(err) { | ||||||
|  | 		isControlPlaneNode = false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Fetches the cluster configuration | ||||||
|  | 	// NB in case of control-plane node, we are reading all the info for the node; in case of NOT control-plane node | ||||||
|  | 	//    (worker node), we are not reading local API address and the CRI socket from the node object | ||||||
|  | 	cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade", !isControlPlaneNode) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &nodeData{ | ||||||
|  | 		etcdUpgrade:        options.etcdUpgrade, | ||||||
|  | 		renewCerts:         options.renewCerts, | ||||||
|  | 		dryRun:             options.dryRun, | ||||||
|  | 		kubeletVersion:     options.kubeletVersion, | ||||||
|  | 		cfg:                cfg, | ||||||
|  | 		client:             client, | ||||||
|  | 		isControlPlaneNode: isControlPlaneNode, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DryRun returns the dryRun flag. | ||||||
|  | func (d *nodeData) DryRun() bool { | ||||||
|  | 	return d.dryRun | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EtcdUpgrade returns the etcdUpgrade flag. | ||||||
|  | func (d *nodeData) EtcdUpgrade() bool { | ||||||
|  | 	return d.etcdUpgrade | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RenewCerts returns the renewCerts flag. | ||||||
|  | func (d *nodeData) RenewCerts() bool { | ||||||
|  | 	return d.renewCerts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // KubeletVersion returns the kubeletVersion flag. | ||||||
|  | func (d *nodeData) KubeletVersion() string { | ||||||
|  | 	return d.kubeletVersion | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Cfg returns initConfiguration. | ||||||
|  | func (d *nodeData) Cfg() *kubeadmapi.InitConfiguration { | ||||||
|  | 	return d.cfg | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsControlPlaneNode returns the isControlPlaneNode flag. | ||||||
|  | func (d *nodeData) IsControlPlaneNode() bool { | ||||||
|  | 	return d.isControlPlaneNode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Client returns a Kubernetes client to be used by kubeadm. | ||||||
|  | func (d *nodeData) Client() clientset.Interface { | ||||||
|  | 	return d.client | ||||||
|  | } | ||||||
|  |  | ||||||
| // NewCmdUpgradeNodeConfig returns the cobra.Command for downloading the new/upgrading the kubelet configuration from the kubelet-config-1.X | // NewCmdUpgradeNodeConfig returns the cobra.Command for downloading the new/upgrading the kubelet configuration from the kubelet-config-1.X | ||||||
| // ConfigMap in the cluster | // ConfigMap in the cluster | ||||||
|  | // TODO: to remove when 1.18 is released | ||||||
| func NewCmdUpgradeNodeConfig() *cobra.Command { | func NewCmdUpgradeNodeConfig() *cobra.Command { | ||||||
| 	flags := &nodeUpgradeFlags{ | 	nodeOptions := newNodeOptions() | ||||||
| 		kubeConfigPath:    constants.GetKubeletKubeConfigPath(), | 	nodeRunner := workflow.NewRunner() | ||||||
| 		kubeletVersionStr: "", |  | ||||||
| 		dryRun:            false, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	cmd := &cobra.Command{ | 	cmd := &cobra.Command{ | ||||||
| 		Use:     "config", | 		Use:        "config", | ||||||
| 		Short:   "Download the kubelet configuration from the cluster ConfigMap kubelet-config-1.X, where X is the minor version of the kubelet", | 		Short:      "Download the kubelet configuration from the cluster ConfigMap kubelet-config-1.X, where X is the minor version of the kubelet", | ||||||
| 		Long:    upgradeNodeConfigLongDesc, | 		Deprecated: "use \"kubeadm upgrade node\" instead", | ||||||
| 		Example: upgradeNodeConfigExample, |  | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 			err := RunUpgradeNodeConfig(flags) | 			// This is required for preserving the old behavior of `kubeadm upgrade node config`. | ||||||
|  | 			// The new implementation exposed as a phase under `kubeadm upgrade node` infers the target | ||||||
|  | 			// kubelet config version from the kubeadm-config ConfigMap | ||||||
|  | 			if len(nodeOptions.kubeletVersion) == 0 { | ||||||
|  | 				kubeadmutil.CheckErr(errors.New("the --kubelet-version argument is required")) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			err := nodeRunner.Run(args) | ||||||
| 			kubeadmutil.CheckErr(err) | 			kubeadmutil.CheckErr(err) | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	options.AddKubeConfigFlag(cmd.Flags(), &flags.kubeConfigPath) | 	// adds flags to the node command | ||||||
| 	cmd.Flags().BoolVar(&flags.dryRun, options.DryRun, flags.dryRun, "Do not change any state, just output the actions that would be performed.") | 	addUpgradeNodeFlags(cmd.Flags(), nodeOptions) | ||||||
| 	cmd.Flags().StringVar(&flags.kubeletVersionStr, "kubelet-version", flags.kubeletVersionStr, "The *desired* version for the kubelet after the upgrade.") |  | ||||||
|  | 	// initialize the workflow runner with the list of phases | ||||||
|  | 	nodeRunner.AppendPhase(phases.NewKubeletConfigPhase()) | ||||||
|  |  | ||||||
|  | 	// sets the data builder function, that will be used by the runner | ||||||
|  | 	// both when running the entire workflow or single phases | ||||||
|  | 	nodeRunner.SetDataInitializer(func(cmd *cobra.Command, args []string) (workflow.RunData, error) { | ||||||
|  | 		return newNodeData(cmd, args, nodeOptions) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
| 	return cmd | 	return cmd | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewCmdUpgradeControlPlane returns the cobra.Command for upgrading the controlplane instance on this node | // NewCmdUpgradeControlPlane returns the cobra.Command for upgrading the controlplane instance on this node | ||||||
|  | // TODO: to remove when 1.16 is released | ||||||
| func NewCmdUpgradeControlPlane() *cobra.Command { | func NewCmdUpgradeControlPlane() *cobra.Command { | ||||||
|  | 	nodeOptions := newNodeOptions() | ||||||
| 	flags := &controlplaneUpgradeFlags{ | 	nodeRunner := workflow.NewRunner() | ||||||
| 		kubeConfigPath:   constants.GetKubeletKubeConfigPath(), |  | ||||||
| 		advertiseAddress: "", |  | ||||||
| 		etcdUpgrade:      true, |  | ||||||
| 		renewCerts:       true, |  | ||||||
| 		dryRun:           false, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	cmd := &cobra.Command{ | 	cmd := &cobra.Command{ | ||||||
| 		Use:     "experimental-control-plane", | 		Use:        "experimental-control-plane", | ||||||
| 		Short:   "Upgrade the control plane instance deployed on this node. IMPORTANT. This command should be executed after executing `kubeadm upgrade apply` on another control plane instance", | 		Short:      "Upgrade the control plane instance deployed on this node. IMPORTANT. This command should be executed after executing `kubeadm upgrade apply` on another control plane instance", | ||||||
| 		Long:    upgradeNodeConfigLongDesc, | 		Deprecated: "this command is deprecated. Use \"kubeadm upgrade node\" instead", | ||||||
| 		Example: upgradeNodeConfigExample, |  | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
|  | 			err := nodeRunner.Run(args) | ||||||
| 			if flags.nodeName == "" { |  | ||||||
| 				klog.V(1).Infoln("[upgrade] found NodeName empty; considered OS hostname as NodeName") |  | ||||||
| 			} |  | ||||||
| 			nodeName, err := node.GetHostname(flags.nodeName) |  | ||||||
| 			if err != nil { |  | ||||||
| 				kubeadmutil.CheckErr(err) |  | ||||||
| 			} |  | ||||||
| 			flags.nodeName = nodeName |  | ||||||
|  |  | ||||||
| 			if flags.advertiseAddress == "" { |  | ||||||
| 				ip, err := configutil.ChooseAPIServerBindAddress(nil) |  | ||||||
| 				if err != nil { |  | ||||||
| 					kubeadmutil.CheckErr(err) |  | ||||||
| 					return |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				flags.advertiseAddress = ip.String() |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			err = RunUpgradeControlPlane(flags) |  | ||||||
| 			kubeadmutil.CheckErr(err) | 			kubeadmutil.CheckErr(err) | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	options.AddKubeConfigFlag(cmd.Flags(), &flags.kubeConfigPath) | 	// adds flags to the node command | ||||||
| 	cmd.Flags().BoolVar(&flags.dryRun, options.DryRun, flags.dryRun, "Do not change any state, just output the actions that would be performed.") | 	options.AddKubeConfigFlag(cmd.Flags(), &nodeOptions.kubeConfigPath) | ||||||
| 	cmd.Flags().BoolVar(&flags.etcdUpgrade, "etcd-upgrade", flags.etcdUpgrade, "Perform the upgrade of etcd.") | 	cmd.Flags().BoolVar(&nodeOptions.dryRun, options.DryRun, nodeOptions.dryRun, "Do not change any state, just output the actions that would be performed.") | ||||||
| 	cmd.Flags().BoolVar(&flags.renewCerts, "certificate-renewal", flags.renewCerts, "Perform the renewal of certificates used by component changed during upgrades.") | 	cmd.Flags().BoolVar(&nodeOptions.etcdUpgrade, "etcd-upgrade", nodeOptions.etcdUpgrade, "Perform the upgrade of etcd.") | ||||||
|  | 	cmd.Flags().BoolVar(&nodeOptions.renewCerts, "certificate-renewal", nodeOptions.renewCerts, "Perform the renewal of certificates used by component changed during upgrades.") | ||||||
|  |  | ||||||
|  | 	// initialize the workflow runner with the list of phases | ||||||
|  | 	nodeRunner.AppendPhase(phases.NewControlPlane()) | ||||||
|  |  | ||||||
|  | 	// sets the data builder function, that will be used by the runner | ||||||
|  | 	// both when running the entire workflow or single phases | ||||||
|  | 	nodeRunner.SetDataInitializer(func(cmd *cobra.Command, args []string) (workflow.RunData, error) { | ||||||
|  | 		return newNodeData(cmd, args, nodeOptions) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
| 	return cmd | 	return cmd | ||||||
| } | } | ||||||
|  |  | ||||||
| // RunUpgradeNodeConfig is executed when `kubeadm upgrade node config` runs. |  | ||||||
| func RunUpgradeNodeConfig(flags *nodeUpgradeFlags) error { |  | ||||||
| 	if len(flags.kubeletVersionStr) == 0 { |  | ||||||
| 		return errors.New("the --kubelet-version argument is required") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Set up the kubelet directory to use. If dry-running, use a fake directory |  | ||||||
| 	kubeletDir, err := upgrade.GetKubeletDir(flags.dryRun) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	client, err := getClient(flags.kubeConfigPath, flags.dryRun) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Parse the desired kubelet version |  | ||||||
| 	kubeletVersion, err := version.ParseSemantic(flags.kubeletVersionStr) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	// TODO: Checkpoint the current configuration first so that if something goes wrong it can be recovered |  | ||||||
| 	if err := kubeletphase.DownloadConfig(client, kubeletVersion, kubeletDir); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// If we're dry-running, print the generated manifests, otherwise do nothing |  | ||||||
| 	if err := printFilesIfDryRunning(flags.dryRun, kubeletDir); err != nil { |  | ||||||
| 		return errors.Wrap(err, "error printing files on dryrun") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fmt.Println("[upgrade] The configuration for this node was successfully updated!") |  | ||||||
| 	fmt.Println("[upgrade] Now you should go ahead and upgrade the kubelet package using your package manager.") |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // printFilesIfDryRunning prints the Static Pod manifests to stdout and informs about the temporary directory to go and lookup |  | ||||||
| func printFilesIfDryRunning(dryRun bool, kubeletDir string) error { |  | ||||||
| 	if !dryRun { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Print the contents of the upgraded file and pretend like they were in kubeadmconstants.KubeletRunDirectory |  | ||||||
| 	fileToPrint := dryrunutil.FileToPrint{ |  | ||||||
| 		RealPath:  filepath.Join(kubeletDir, constants.KubeletConfigurationFileName), |  | ||||||
| 		PrintPath: filepath.Join(constants.KubeletRunDirectory, constants.KubeletConfigurationFileName), |  | ||||||
| 	} |  | ||||||
| 	return dryrunutil.PrintDryRunFiles([]dryrunutil.FileToPrint{fileToPrint}, os.Stdout) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // RunUpgradeControlPlane is executed when `kubeadm upgrade node controlplane` runs. |  | ||||||
| func RunUpgradeControlPlane(flags *controlplaneUpgradeFlags) error { |  | ||||||
|  |  | ||||||
| 	client, err := getClient(flags.kubeConfigPath, flags.dryRun) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	waiter := apiclient.NewKubeWaiter(client, upgrade.UpgradeManifestTimeout, os.Stdout) |  | ||||||
|  |  | ||||||
| 	// Fetches the cluster configuration |  | ||||||
| 	cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade", false) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Upgrade the control plane and etcd if installed on this node |  | ||||||
| 	fmt.Printf("[upgrade] Upgrading your Static Pod-hosted control plane instance to version %q...\n", cfg.KubernetesVersion) |  | ||||||
| 	if flags.dryRun { |  | ||||||
| 		return DryRunStaticPodUpgrade(cfg) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := PerformStaticPodUpgrade(client, waiter, cfg, flags.etcdUpgrade, flags.renewCerts); err != nil { |  | ||||||
| 		return errors.Wrap(err, "couldn't complete the static pod upgrade") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fmt.Println("[upgrade] The control plane instance for this node was successfully updated!") |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -31,10 +31,12 @@ import ( | |||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||||
| 	certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" | 	certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/renewal" | 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/renewal" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" | ||||||
| 	controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" | 	controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" | ||||||
| 	etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" | 	etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/util" | 	"k8s.io/kubernetes/cmd/kubeadm/app/util" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" | 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" | ||||||
|  | 	dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" | ||||||
| 	etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd" | 	etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" | 	"k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" | ||||||
| ) | ) | ||||||
| @@ -583,3 +585,44 @@ func renewCertsByComponent(cfg *kubeadmapi.InitConfiguration, component string, | |||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetPathManagerForUpgrade returns a path manager properly configured for the given InitConfiguration. | ||||||
|  | func GetPathManagerForUpgrade(kubernetesDir string, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade bool) (StaticPodPathManager, error) { | ||||||
|  | 	isHAEtcd := etcdutil.CheckConfigurationIsHA(&internalcfg.Etcd) | ||||||
|  | 	return NewKubeStaticPodPathManagerUsingTempDirs(kubernetesDir, true, etcdUpgrade && !isHAEtcd) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PerformStaticPodUpgrade performs the upgrade of the control plane components for a static pod hosted cluster | ||||||
|  | func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade, renewCerts bool) error { | ||||||
|  | 	pathManager, err := GetPathManagerForUpgrade(constants.KubernetesDir, internalcfg, etcdUpgrade) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// The arguments oldEtcdClient and newEtdClient, are uninitialized because passing in the clients allow for mocking the client during testing | ||||||
|  | 	return StaticPodControlPlane(client, waiter, pathManager, internalcfg, etcdUpgrade, renewCerts, nil, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DryRunStaticPodUpgrade fakes an upgrade of the control plane | ||||||
|  | func DryRunStaticPodUpgrade(internalcfg *kubeadmapi.InitConfiguration) error { | ||||||
|  |  | ||||||
|  | 	dryRunManifestDir, err := constants.CreateTempDirForKubeadm("", "kubeadm-upgrade-dryrun") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer os.RemoveAll(dryRunManifestDir) | ||||||
|  |  | ||||||
|  | 	if err := controlplane.CreateInitStaticPodManifestFiles(dryRunManifestDir, internalcfg); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Print the contents of the upgraded manifests and pretend like they were in /etc/kubernetes/manifests | ||||||
|  | 	files := []dryrunutil.FileToPrint{} | ||||||
|  | 	for _, component := range constants.ControlPlaneComponents { | ||||||
|  | 		realPath := constants.GetStaticPodFilepath(component, dryRunManifestDir) | ||||||
|  | 		outputPath := constants.GetStaticPodFilepath(component, constants.GetStaticPodDirectory()) | ||||||
|  | 		files = append(files, dryrunutil.NewFileToPrint(realPath, outputPath)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return dryrunutil.PrintDryRunFiles(files, os.Stdout) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -909,3 +909,89 @@ func getEmbeddedCerts(tmpDir, kubeConfig string) ([]*x509.Certificate, error) { | |||||||
|  |  | ||||||
| 	return certutil.ParseCertsPEM(authInfo.ClientCertificateData) | 	return certutil.ParseCertsPEM(authInfo.ClientCertificateData) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestGetPathManagerForUpgrade(t *testing.T) { | ||||||
|  |  | ||||||
|  | 	haEtcd := &kubeadmapi.InitConfiguration{ | ||||||
|  | 		ClusterConfiguration: kubeadmapi.ClusterConfiguration{ | ||||||
|  | 			Etcd: kubeadmapi.Etcd{ | ||||||
|  | 				External: &kubeadmapi.ExternalEtcd{ | ||||||
|  | 					Endpoints: []string{"10.100.0.1:2379", "10.100.0.2:2379", "10.100.0.3:2379"}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	noHAEtcd := &kubeadmapi.InitConfiguration{} | ||||||
|  |  | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name             string | ||||||
|  | 		cfg              *kubeadmapi.InitConfiguration | ||||||
|  | 		etcdUpgrade      bool | ||||||
|  | 		shouldDeleteEtcd bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:             "ha etcd but no etcd upgrade", | ||||||
|  | 			cfg:              haEtcd, | ||||||
|  | 			etcdUpgrade:      false, | ||||||
|  | 			shouldDeleteEtcd: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:             "non-ha etcd with etcd upgrade", | ||||||
|  | 			cfg:              noHAEtcd, | ||||||
|  | 			etcdUpgrade:      true, | ||||||
|  | 			shouldDeleteEtcd: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:             "ha etcd and etcd upgrade", | ||||||
|  | 			cfg:              haEtcd, | ||||||
|  | 			etcdUpgrade:      true, | ||||||
|  | 			shouldDeleteEtcd: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, test := range tests { | ||||||
|  | 		t.Run(test.name, func(t *testing.T) { | ||||||
|  | 			// Use a temporary directory | ||||||
|  | 			tmpdir, err := ioutil.TempDir("", "TestGetPathManagerForUpgrade") | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatalf("unexpected error making temporary directory: %v", err) | ||||||
|  | 			} | ||||||
|  | 			defer func() { | ||||||
|  | 				os.RemoveAll(tmpdir) | ||||||
|  | 			}() | ||||||
|  |  | ||||||
|  | 			pathmgr, err := GetPathManagerForUpgrade(tmpdir, test.cfg, test.etcdUpgrade) | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatalf("unexpected error creating path manager: %v", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if _, err := os.Stat(pathmgr.BackupManifestDir()); os.IsNotExist(err) { | ||||||
|  | 				t.Errorf("expected manifest dir %s to exist, but it did not (%v)", pathmgr.BackupManifestDir(), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if _, err := os.Stat(pathmgr.BackupEtcdDir()); os.IsNotExist(err) { | ||||||
|  | 				t.Errorf("expected etcd dir %s to exist, but it did not (%v)", pathmgr.BackupEtcdDir(), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err := pathmgr.CleanupDirs(); err != nil { | ||||||
|  | 				t.Fatalf("unexpected error cleaning up directories: %v", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if _, err := os.Stat(pathmgr.BackupManifestDir()); os.IsNotExist(err) { | ||||||
|  | 				t.Errorf("expected manifest dir %s to exist, but it did not (%v)", pathmgr.BackupManifestDir(), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if test.shouldDeleteEtcd { | ||||||
|  | 				if _, err := os.Stat(pathmgr.BackupEtcdDir()); !os.IsNotExist(err) { | ||||||
|  | 					t.Errorf("expected etcd dir %s not to exist, but it did (%v)", pathmgr.BackupEtcdDir(), err) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				if _, err := os.Stat(pathmgr.BackupEtcdDir()); os.IsNotExist(err) { | ||||||
|  | 					t.Errorf("expected etcd dir %s to exist, but it did not", pathmgr.BackupEtcdDir()) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 fabriziopandini
					fabriziopandini