diff --git a/cmd/kubeadm/app/cmd/phases/init/kubeconfig.go b/cmd/kubeadm/app/cmd/phases/init/kubeconfig.go index 77432a13354..7e255b29fbb 100644 --- a/cmd/kubeadm/app/cmd/phases/init/kubeconfig.go +++ b/cmd/kubeadm/app/cmd/phases/init/kubeconfig.go @@ -26,6 +26,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" ) @@ -157,7 +158,16 @@ func runKubeConfigFile(kubeConfigFileName string) func(workflow.RunData) error { cfg.CertificatesDir = data.CertificateWriteDir() defer func() { cfg.CertificatesDir = data.CertificateDir() }() + initConfiguration := data.Cfg().DeepCopy() + + if features.Enabled(cfg.FeatureGates, features.ControlPlaneKubeletLocalMode) { + if kubeConfigFileName == kubeadmconstants.KubeletKubeConfigFileName { + // Unset the ControlPlaneEndpoint so the creation falls back to the LocalAPIEndpoint for the kubelet's kubeconfig. + initConfiguration.ControlPlaneEndpoint = "" + } + } + // creates the KubeConfig file (or use existing) - return kubeconfigphase.CreateKubeConfigFile(kubeConfigFileName, data.KubeConfigDir(), data.Cfg()) + return kubeconfigphase.CreateKubeConfigFile(kubeConfigFileName, data.KubeConfigDir(), initConfiguration) } } diff --git a/cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go b/cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go index cbdca3ec1c6..330d89b9823 100644 --- a/cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go +++ b/cmd/kubeadm/app/cmd/phases/upgrade/node/controlplane.go @@ -24,6 +24,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" ) @@ -82,6 +83,12 @@ func runControlPlane() func(c workflow.RunData) error { return errors.Wrap(err, "failed to perform addons upgrade") } + if features.Enabled(cfg.FeatureGates, features.ControlPlaneKubeletLocalMode) { + if err := upgrade.UpdateKubeletLocalMode(cfg, dryRun); err != nil { + return errors.Wrap(err, "failed to update kubelet local mode") + } + } + fmt.Println("[upgrade] The control plane instance for this node was successfully updated!") return nil diff --git a/cmd/kubeadm/app/phases/upgrade/postupgrade.go b/cmd/kubeadm/app/phases/upgrade/postupgrade.go index 02e7bcca618..f6cb20d8e70 100644 --- a/cmd/kubeadm/app/phases/upgrade/postupgrade.go +++ b/cmd/kubeadm/app/phases/upgrade/postupgrade.go @@ -31,10 +31,12 @@ import ( errorsutil "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" "k8s.io/klog/v2" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns" "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy" "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" @@ -109,6 +111,12 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.InitCon errs = append(errs, err) } + if features.Enabled(cfg.FeatureGates, features.ControlPlaneKubeletLocalMode) { + if err := UpdateKubeletLocalMode(cfg, dryRun); err != nil { + return errors.Wrap(err, "failed to update kubelet local mode") + } + } + return errorsutil.NewAggregate(errs) } @@ -281,3 +289,61 @@ func GetKubeletDir(dryRun bool) (string, error) { } return kubeadmconstants.KubeletRunDirectory, nil } + +// UpdateKubeletLocalMode changes the Server URL in the kubelets kubeconfig to the local API endpoint if it is currently +// set to the ControlPlaneEndpoint. +// TODO: remove this function once kubeletKubeConfigFilePath goes GA and is hardcoded to enabled by default: +// https://github.com/kubernetes/kubeadm/issues/2271 +func UpdateKubeletLocalMode(cfg *kubeadmapi.InitConfiguration, dryRun bool) error { + kubeletKubeConfigFilePath := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName) + + if _, err := os.Stat(kubeletKubeConfigFilePath); err != nil { + if os.IsNotExist(err) { + klog.V(2).Infof("Could not mutate the Server URL in %s: %v", kubeletKubeConfigFilePath, err) + return nil + } + return err + } + + config, err := clientcmd.LoadFromFile(kubeletKubeConfigFilePath) + if err != nil { + return err + } + + configContext, ok := config.Contexts[config.CurrentContext] + if !ok { + return errors.Errorf("cannot find cluster for active context in kubeconfig %q", kubeletKubeConfigFilePath) + } + + localAPIEndpoint, err := kubeadmutil.GetLocalAPIEndpoint(&cfg.LocalAPIEndpoint) + if err != nil { + return err + } + + controlPlaneAPIEndpoint, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint) + if err != nil { + return err + } + + // Skip changing kubeconfig file if Server does not match the ControlPlaneEndoint. + if config.Clusters[configContext.Cluster].Server != controlPlaneAPIEndpoint || controlPlaneAPIEndpoint == localAPIEndpoint { + klog.V(2).Infof("Skipping update of the Server URL in %s, because it's already not equal to %q or already matches the localAPIEndpoint", kubeletKubeConfigFilePath, cfg.ControlPlaneEndpoint) + return nil + } + + if dryRun { + fmt.Printf("[dryrun] Would change the Server URL from %q to %q in %s and try to restart kubelet\n", config.Clusters[configContext.Cluster].Server, localAPIEndpoint, kubeletKubeConfigFilePath) + return nil + } + + klog.V(1).Infof("Changing the Server URL from %q to %q in %s", config.Clusters[configContext.Cluster].Server, localAPIEndpoint, kubeletKubeConfigFilePath) + config.Clusters[configContext.Cluster].Server = localAPIEndpoint + + if err := clientcmd.WriteToFile(*config, kubeletKubeConfigFilePath); err != nil { + return err + } + + kubeletphase.TryRestartKubelet() + + return nil +}