From 3ffc54a9e963b51c78cd9f5c22f74700766b0203 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Sun, 28 Sep 2025 13:34:37 +0200 Subject: [PATCH] kubeadm: rework the FetchInitConfigurationFromCluster node flags The newControlPlane flag has been historically problematic, since it implies that the function FetchInitConfigurationFromCluster cannot handle the cases where a node is worker node but we still want to fetch its NodeRegistrationOptions conditionally, in cases such as "upgrade node" for workers. To fix this issue, replace the flag newControlPlaneNode with two new flags getNodeRegistration and getAPIEndpoint. If getNodeRegistration is true, we fetch the NRO, and if getAPIEndpoint is true, we fetch the API endpoint for that node. Additionally, rename skipComponentConfigs to getComponentConfigs for consistency and flip its value accordingly everywhere. --- cmd/kubeadm/app/cmd/certs.go | 6 ++++- cmd/kubeadm/app/cmd/join.go | 5 +++- cmd/kubeadm/app/cmd/reset.go | 6 ++++- cmd/kubeadm/app/cmd/upgrade/apply.go | 5 +++- cmd/kubeadm/app/cmd/upgrade/common.go | 6 ++++- cmd/kubeadm/app/cmd/upgrade/diff.go | 8 ++++-- cmd/kubeadm/app/cmd/upgrade/diff_test.go | 2 +- cmd/kubeadm/app/cmd/upgrade/node.go | 19 +++++--------- cmd/kubeadm/app/util/config/cluster.go | 23 +++++++++------- cmd/kubeadm/app/util/config/cluster_test.go | 29 ++++++++++++--------- cmd/kubeadm/app/util/staticpod/utils.go | 15 +++++++++++ 11 files changed, 82 insertions(+), 42 deletions(-) diff --git a/cmd/kubeadm/app/cmd/certs.go b/cmd/kubeadm/app/cmd/certs.go index f4e7d92454f..1e5115a558d 100644 --- a/cmd/kubeadm/app/cmd/certs.go +++ b/cmd/kubeadm/app/cmd/certs.go @@ -49,6 +49,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/output" + staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" ) var ( @@ -346,7 +347,10 @@ func getInternalCfg(cfgPath string, client kubernetes.Interface, cfg kubeadmapiv // In case the user is not providing a custom config, try to get current config from the cluster. // NB. this operation should not block, because we want to allow certificate renewal also in case of not-working clusters if cfgPath == "" && client != nil { - internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, logPrefix, false, false) + getNodeRegistration := true + getAPIEndpoint := staticpodutil.IsControlPlaneNode() + getComponentConfigs := true + internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, logPrefix, getNodeRegistration, getAPIEndpoint, getComponentConfigs) if err == nil { printer.Println() // add empty line to separate the FetchInitConfigurationFromCluster output from the command output // certificate renewal or expiration checking doesn't depend on a running cluster, which means the CertificatesDir diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index c43b1e903b7..7bcbe82f278 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -709,7 +709,10 @@ func fetchInitConfigurationFromJoinConfiguration(cfg *kubeadmapi.JoinConfigurati // fetchInitConfiguration reads the cluster configuration from the kubeadm-admin configMap func fetchInitConfiguration(client clientset.Interface) (*kubeadmapi.InitConfiguration, error) { - initConfiguration, err := configutil.FetchInitConfigurationFromCluster(client, nil, "preflight", true, false) + getNodeRegistration := false + getAPIEndpoint := false + getComponentConfigs := true + initConfiguration, err := configutil.FetchInitConfigurationFromCluster(client, nil, "preflight", getNodeRegistration, getAPIEndpoint, getComponentConfigs) if err != nil { return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap") } diff --git a/cmd/kubeadm/app/cmd/reset.go b/cmd/kubeadm/app/cmd/reset.go index 818b6eb2768..23f2bf62a1c 100644 --- a/cmd/kubeadm/app/cmd/reset.go +++ b/cmd/kubeadm/app/cmd/reset.go @@ -44,6 +44,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime" + staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" ) var ( @@ -132,7 +133,10 @@ func newResetData(cmd *cobra.Command, opts *resetOptions, in io.Reader, out io.W if err == nil { klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", opts.kubeconfigPath) - initCfg, err = configutil.FetchInitConfigurationFromCluster(client, nil, "reset", false, false) + getNodeRegistration := true + getAPIEndpoint := staticpodutil.IsControlPlaneNode() + getComponentConfigs := true + initCfg, err = configutil.FetchInitConfigurationFromCluster(client, nil, "reset", getNodeRegistration, getAPIEndpoint, getComponentConfigs) if err != nil { klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err) } diff --git a/cmd/kubeadm/app/cmd/upgrade/apply.go b/cmd/kubeadm/app/cmd/upgrade/apply.go index a7bae51dd03..c6c61ed3ac6 100644 --- a/cmd/kubeadm/app/cmd/upgrade/apply.go +++ b/cmd/kubeadm/app/cmd/upgrade/apply.go @@ -229,7 +229,10 @@ func newApplyData(cmd *cobra.Command, args []string, applyFlags *applyFlags) (*a // Fetches the cluster configuration. klog.V(1).Infoln("[upgrade] retrieving configuration from cluster") - initCfg, err := configutil.FetchInitConfigurationFromCluster(client, nil, "upgrade", false, false) + getNodeRegistration := true + isControlPlaneNode := true + getComponentConfigs := true + initCfg, err := configutil.FetchInitConfigurationFromCluster(client, nil, "upgrade", getNodeRegistration, isControlPlaneNode, getComponentConfigs) if err != nil { if apierrors.IsNotFound(err) { _, _ = printer.Printf("[upgrade] In order to upgrade, a ConfigMap called %q in the %q namespace must exist.\n", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem) diff --git a/cmd/kubeadm/app/cmd/upgrade/common.go b/cmd/kubeadm/app/cmd/upgrade/common.go index a3579e53254..0fee21e2ecb 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common.go +++ b/cmd/kubeadm/app/cmd/upgrade/common.go @@ -45,6 +45,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/output" + staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" ) // enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure @@ -92,7 +93,10 @@ func enforceRequirements(flagSet *pflag.FlagSet, flags *applyPlanFlags, args []s return nil, nil, nil, nil, err } - initCfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, "upgrade/config", false, false) + getNodeRegistration := true + getAPIEndpoint := staticpodutil.IsControlPlaneNode() + getComponentConfigs := true + initCfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, "upgrade/config", getNodeRegistration, getAPIEndpoint, getComponentConfigs) if err != nil { return nil, nil, nil, nil, errors.Wrap(err, "[upgrade/init config] FATAL") } diff --git a/cmd/kubeadm/app/cmd/upgrade/diff.go b/cmd/kubeadm/app/cmd/upgrade/diff.go index 947d763a57b..3d5bf468e9b 100644 --- a/cmd/kubeadm/app/cmd/upgrade/diff.go +++ b/cmd/kubeadm/app/cmd/upgrade/diff.go @@ -41,6 +41,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/output" + staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" ) type diffFlags struct { @@ -106,7 +107,7 @@ func validateManifestsPath(manifests ...string) (err error) { } // FetchInitConfigurationFunc defines the signature of the function which will fetch InitConfiguration from cluster. -type FetchInitConfigurationFunc func(client clientset.Interface, printer output.Printer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) +type FetchInitConfigurationFunc func(client clientset.Interface, printer output.Printer, logPrefix string, getNodeRegistration, getAPIEndpoint, getComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) func runDiff(fs *pflag.FlagSet, flags *diffFlags, args []string, fetchInitConfigurationFromCluster FetchInitConfigurationFunc) error { externalCfg := &v1beta4.UpgradeConfiguration{} @@ -119,7 +120,10 @@ func runDiff(fs *pflag.FlagSet, flags *diffFlags, args []string, fetchInitConfig if err != nil { return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath) } - initCfg, err := fetchInitConfigurationFromCluster(client, &output.TextPrinter{}, "upgrade/diff", false, true) + getNodeRegistration := true + getAPIEndpoint := staticpodutil.IsControlPlaneNode() + getComponentConfigs := false + initCfg, err := fetchInitConfigurationFromCluster(client, &output.TextPrinter{}, "upgrade/diff", getNodeRegistration, getAPIEndpoint, getComponentConfigs) if err != nil { return err } diff --git a/cmd/kubeadm/app/cmd/upgrade/diff_test.go b/cmd/kubeadm/app/cmd/upgrade/diff_test.go index f2304128592..7e65ee1a18b 100644 --- a/cmd/kubeadm/app/cmd/upgrade/diff_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/diff_test.go @@ -44,7 +44,7 @@ func createTestRunDiffFile(contents []byte) (string, error) { return file.Name(), nil } -func fakeFetchInitConfig(client clientset.Interface, printer output.Printer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) { +func fakeFetchInitConfig(client clientset.Interface, printer output.Printer, logPrefix string, getNodeRegistration, getAPIEndpoint, getComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) { return &kubeadmapi.InitConfiguration{ ClusterConfiguration: kubeadmapi.ClusterConfiguration{ KubernetesVersion: "v1.0.1", diff --git a/cmd/kubeadm/app/cmd/upgrade/node.go b/cmd/kubeadm/app/cmd/upgrade/node.go index b0f57d0b1e2..1e3cd71041e 100644 --- a/cmd/kubeadm/app/cmd/upgrade/node.go +++ b/cmd/kubeadm/app/cmd/upgrade/node.go @@ -19,14 +19,12 @@ package upgrade import ( "fmt" "io" - "os" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" - "k8s.io/klog/v2" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta4" @@ -40,6 +38,7 @@ import ( configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" "k8s.io/kubernetes/cmd/kubeadm/app/util/errors" "k8s.io/kubernetes/cmd/kubeadm/app/util/output" + staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" ) // nodeOptions defines all the options exposed via flags by kubeadm upgrade node. @@ -164,13 +163,8 @@ func addUpgradeNodeFlags(flagSet *flag.FlagSet, nodeOptions *nodeOptions) { // 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, nodeOptions *nodeOptions, out io.Writer) (*nodeData, error) { - // Checks if a node is a control-plane node by looking up the kube-apiserver manifest file - isControlPlaneNode := true - filepath := constants.GetStaticPodFilepath(constants.KubeAPIServer, constants.GetStaticPodDirectory()) - if _, err := os.Stat(filepath); os.IsNotExist(err) { - klog.V(1).Infof("assuming this is not a control plane node because %q is missing", filepath) - isControlPlaneNode = false - } + isControlPlaneNode := staticpodutil.IsControlPlaneNode() + if len(nodeOptions.kubeConfigPath) == 0 { // Update the kubeconfig path depending on whether this is a control plane node or not. nodeOptions.kubeConfigPath = constants.GetKubeletKubeConfigPath() @@ -198,9 +192,10 @@ func newNodeData(cmd *cobra.Command, nodeOptions *nodeOptions, out io.Writer) (* } // 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 - initCfg, err := configutil.FetchInitConfigurationFromCluster(client, nil, "upgrade", !isControlPlaneNode, false) + getNodeRegistration := true + getAPIEndpoint := isControlPlaneNode + getComponentConfigs := true + initCfg, err := configutil.FetchInitConfigurationFromCluster(client, nil, "upgrade", getNodeRegistration, getAPIEndpoint, getComponentConfigs) if err != nil { return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap") } diff --git a/cmd/kubeadm/app/util/config/cluster.go b/cmd/kubeadm/app/util/config/cluster.go index 724530f692a..40ba9b2dbd1 100644 --- a/cmd/kubeadm/app/util/config/cluster.go +++ b/cmd/kubeadm/app/util/config/cluster.go @@ -52,7 +52,7 @@ import ( ) // FetchInitConfigurationFromCluster fetches configuration from a ConfigMap in the cluster -func FetchInitConfigurationFromCluster(client clientset.Interface, printer output.Printer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) { +func FetchInitConfigurationFromCluster(client clientset.Interface, printer output.Printer, logPrefix string, getNodeRegistration, getAPIEndpoint, getComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) { if printer == nil { printer = &output.TextPrinter{} } @@ -61,7 +61,7 @@ func FetchInitConfigurationFromCluster(client clientset.Interface, printer outpu _, _ = printer.Printf("[%s] Use 'kubeadm init phase upload-config kubeadm --config your-config-file' to re-upload it.\n", logPrefix) // Fetch the actual config from cluster - cfg, err := getInitConfigurationFromCluster(constants.KubernetesDir, client, newControlPlane, skipComponentConfigs) + cfg, err := getInitConfigurationFromCluster(constants.KubernetesDir, client, getNodeRegistration, getAPIEndpoint, getComponentConfigs) if err != nil { return nil, err } @@ -76,7 +76,7 @@ func FetchInitConfigurationFromCluster(client clientset.Interface, printer outpu } // getInitConfigurationFromCluster is separate only for testing purposes, don't call it directly, use FetchInitConfigurationFromCluster instead -func getInitConfigurationFromCluster(kubeconfigDir string, client clientset.Interface, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) { +func getInitConfigurationFromCluster(kubeconfigDir string, client clientset.Interface, getNodeRegistration, getAPIEndpoint, getComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) { // Also, the config map really should be KubeadmConfigConfigMap... configMap, err := apiclient.GetConfigMapWithShortRetry(client, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap) if err != nil { @@ -106,26 +106,28 @@ func getInitConfigurationFromCluster(kubeconfigDir string, client clientset.Inte return nil, errors.Wrap(err, "failed to decode cluster configuration data") } - if !skipComponentConfigs { + if getComponentConfigs { // get the component configs from the corresponding config maps if err := componentconfigs.FetchFromCluster(&initcfg.ClusterConfiguration, client); err != nil { return nil, errors.Wrap(err, "failed to get component configs") } } - // if this isn't a new controlplane instance (e.g. in case of kubeadm upgrades) - // get nodes specific information as well - if !newControlPlane { + if getNodeRegistration { // gets the nodeRegistration for the current from the node object kubeconfigFile := filepath.Join(kubeconfigDir, constants.KubeletKubeConfigFileName) if err := GetNodeRegistration(kubeconfigFile, client, &initcfg.NodeRegistration, &initcfg.ClusterConfiguration); err != nil { return nil, errors.Wrap(err, "failed to get node registration") } - // gets the APIEndpoint for the current node - if err := getAPIEndpoint(client, initcfg.NodeRegistration.Name, &initcfg.LocalAPIEndpoint); err != nil { + } + + if getAPIEndpoint { + // gets the APIEndpoint for the current control plane node + if err := GetAPIEndpoint(client, initcfg.NodeRegistration.Name, &initcfg.LocalAPIEndpoint); err != nil { return nil, errors.Wrap(err, "failed to getAPIEndpoint") } } + return initcfg, nil } @@ -258,7 +260,8 @@ func getNodeNameFromSSR(client clientset.Interface) (string, error) { return strings.TrimPrefix(user, constants.NodesUserPrefix), nil } -func getAPIEndpoint(client clientset.Interface, nodeName string, apiEndpoint *kubeadmapi.APIEndpoint) error { +// GetAPIEndpoint gets the API endpoint for a given node. +func GetAPIEndpoint(client clientset.Interface, nodeName string, apiEndpoint *kubeadmapi.APIEndpoint) error { return getAPIEndpointWithRetry(client, nodeName, apiEndpoint, constants.KubernetesAPICallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration) } diff --git a/cmd/kubeadm/app/util/config/cluster_test.go b/cmd/kubeadm/app/util/config/cluster_test.go index 94aa9917c93..41329eee458 100644 --- a/cmd/kubeadm/app/util/config/cluster_test.go +++ b/cmd/kubeadm/app/util/config/cluster_test.go @@ -491,13 +491,14 @@ func TestGetInitConfigurationFromCluster(t *testing.T) { defer os.RemoveAll(tmpdir) var tests = []struct { - name string - fileContents []byte - node *v1.Node - staticPods []testresources.FakeStaticPod - configMaps []testresources.FakeConfigMap - newControlPlane bool - expectedError bool + name string + fileContents []byte + node *v1.Node + staticPods []testresources.FakeStaticPod + configMaps []testresources.FakeConfigMap + getNodeRegistration bool + getAPIEndpoint bool + expectedError bool }{ { name: "invalid - No kubeadm-config ConfigMap", @@ -556,6 +557,8 @@ func TestGetInitConfigurationFromCluster(t *testing.T) { Taints: []v1.Taint{kubeadmconstants.ControlPlaneTaint}, }, }, + getNodeRegistration: true, + getAPIEndpoint: true, }, { name: "valid v1beta3 - new control plane == true", // InitConfiguration composed with data from different places, without node specific information @@ -588,7 +591,8 @@ func TestGetInitConfigurationFromCluster(t *testing.T) { }, }, }, - newControlPlane: true, + getNodeRegistration: false, + getAPIEndpoint: false, }, } @@ -629,7 +633,8 @@ func TestGetInitConfigurationFromCluster(t *testing.T) { } } - cfg, err := getInitConfigurationFromCluster(tmpdir, client, rt.newControlPlane, false) + getComponentConfigs := true + cfg, err := getInitConfigurationFromCluster(tmpdir, client, rt.getNodeRegistration, rt.getAPIEndpoint, getComponentConfigs) if rt.expectedError != (err != nil) { t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err) return @@ -649,13 +654,13 @@ func TestGetInitConfigurationFromCluster(t *testing.T) { if cfg.NodeRegistration.ImagePullPolicy != kubeadmapiv1.DefaultImagePullPolicy { t.Errorf("invalid cfg.NodeRegistration.ImagePullPolicy %v", cfg.NodeRegistration.ImagePullPolicy) } - if !rt.newControlPlane && (cfg.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" || cfg.LocalAPIEndpoint.BindPort != 1234) { + if rt.getNodeRegistration && rt.getAPIEndpoint && (cfg.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" || cfg.LocalAPIEndpoint.BindPort != 1234) { t.Errorf("invalid cfg.LocalAPIEndpoint: %v", cfg.LocalAPIEndpoint) } - if !rt.newControlPlane && (cfg.NodeRegistration.Name != nodeName || cfg.NodeRegistration.CRISocket != "myCRIsocket" || len(cfg.NodeRegistration.Taints) != 1) { + if rt.getNodeRegistration && (cfg.NodeRegistration.Name != nodeName || cfg.NodeRegistration.CRISocket != "myCRIsocket" || len(cfg.NodeRegistration.Taints) != 1) { t.Errorf("invalid cfg.NodeRegistration: %v", cfg.NodeRegistration) } - if rt.newControlPlane && len(cfg.NodeRegistration.CRISocket) > 0 { + if !rt.getNodeRegistration && len(cfg.NodeRegistration.CRISocket) > 0 { t.Errorf("invalid cfg.NodeRegistration.CRISocket: expected empty CRISocket, but got %v", cfg.NodeRegistration.CRISocket) } if _, ok := cfg.ComponentConfigs[componentconfigs.KubeletGroup]; !ok { diff --git a/cmd/kubeadm/app/util/staticpod/utils.go b/cmd/kubeadm/app/util/staticpod/utils.go index 5b70cb89c35..b49940d6c9d 100644 --- a/cmd/kubeadm/app/util/staticpod/utils.go +++ b/cmd/kubeadm/app/util/staticpod/utils.go @@ -436,3 +436,18 @@ func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) { hasher.Reset() fmt.Fprintf(hasher, "%v", dump.ForHash(objectToWrite)) } + +// IsControlPlaneNode returns true if the kube-apiserver static pod manifest is present +// on the host. +func IsControlPlaneNode() bool { + isControlPlaneNode := true + + filepath := kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeAPIServer, + kubeadmconstants.GetStaticPodDirectory()) + + if _, err := os.Stat(filepath); os.IsNotExist(err) { + isControlPlaneNode = false + } + + return isControlPlaneNode +}