From f0d0ff95d296cbdc594f52f1d57589db525fa2d0 Mon Sep 17 00:00:00 2001 From: Seena Fallah Date: Wed, 29 Apr 2026 21:19:33 +0200 Subject: [PATCH] kubeadm: skip LocalAPIEndpoint defaulting on worker join FetchInitConfigurationFromCluster always called SetAPIEndpointDynamicDefaults, which invokes ChooseAPIServerBindAddress and requires a default route on the host. Worker nodes joining the cluster don't need a LocalAPIEndpoint and may legitimately have no default route, causing kubeadm join to fail with: ``` unable to fetch the kubeadm-config ConfigMap: unable to select an IP from default routes. ``` Add a skipAPIEndpoint parameter to SetInitDynamicDefaults and pass `!getAPIEndpoint` from FetchInitConfigurationFromCluster, so the endpoint defaulter is bypassed when the caller did not request the endpoint (worker join, non-control-plane reset/certs/upgrade paths). Signed-off-by: Seena Fallah --- cmd/kubeadm/app/util/config/cluster.go | 8 +-- .../app/util/config/initconfiguration.go | 14 ++--- .../app/util/config/initconfiguration_test.go | 53 +++++++++++++++++++ 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/cmd/kubeadm/app/util/config/cluster.go b/cmd/kubeadm/app/util/config/cluster.go index 3297c3447e5..ebdb51c2abb 100644 --- a/cmd/kubeadm/app/util/config/cluster.go +++ b/cmd/kubeadm/app/util/config/cluster.go @@ -66,9 +66,11 @@ func FetchInitConfigurationFromCluster(client clientset.Interface, printer outpu return nil, err } - // Apply dynamic defaults - // NB. skip CRI detection here because it won't be used at all and will be overridden later - if err := SetInitDynamicDefaults(cfg, true); err != nil { + // Apply dynamic defaults. + // NB. skip CRI detection here because it won't be used at all and will be overridden later. + // NB. skip LocalAPIEndpoint defaulting when the caller did not request the endpoint (e.g. a + // worker join). + if err := SetInitDynamicDefaults(cfg, true, !getAPIEndpoint); err != nil { return nil, err } diff --git a/cmd/kubeadm/app/util/config/initconfiguration.go b/cmd/kubeadm/app/util/config/initconfiguration.go index 94bb650c4dc..da52e821499 100644 --- a/cmd/kubeadm/app/util/config/initconfiguration.go +++ b/cmd/kubeadm/app/util/config/initconfiguration.go @@ -56,16 +56,18 @@ var ( } ) -// SetInitDynamicDefaults checks and sets configuration values for the InitConfiguration object -func SetInitDynamicDefaults(cfg *kubeadmapi.InitConfiguration, skipCRIDetect bool) error { +// SetInitDynamicDefaults checks and sets configuration values for the InitConfiguration object. +func SetInitDynamicDefaults(cfg *kubeadmapi.InitConfiguration, skipCRIDetect, skipAPIEndpoint bool) error { if err := SetBootstrapTokensDynamicDefaults(&cfg.BootstrapTokens); err != nil { return err } if err := SetNodeRegistrationDynamicDefaults(&cfg.NodeRegistration, true, skipCRIDetect); err != nil { return err } - if err := SetAPIEndpointDynamicDefaults(&cfg.LocalAPIEndpoint); err != nil { - return err + if !skipAPIEndpoint { + if err := SetAPIEndpointDynamicDefaults(&cfg.LocalAPIEndpoint); err != nil { + return err + } } return SetClusterDynamicDefaults(&cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, &cfg.NodeRegistration) } @@ -245,7 +247,7 @@ func DefaultedInitConfiguration(versionedInitCfg *kubeadmapiv1.InitConfiguration } // Applies dynamic defaults to settings not provided with flags - if err := SetInitDynamicDefaults(internalcfg, opts.SkipCRIDetect); err != nil { + if err := SetInitDynamicDefaults(internalcfg, opts.SkipCRIDetect, false); err != nil { return nil, err } // Validates cfg (flags/configs + defaults + dynamic defaults) @@ -400,7 +402,7 @@ func documentMapToInitConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecat } // Applies dynamic defaults to settings not provided with flags - if err := SetInitDynamicDefaults(initcfg, skipCRIDetect); err != nil { + if err := SetInitDynamicDefaults(initcfg, skipCRIDetect, false); err != nil { return nil, err } diff --git a/cmd/kubeadm/app/util/config/initconfiguration_test.go b/cmd/kubeadm/app/util/config/initconfiguration_test.go index 8b95c4260df..c2322bd461f 100644 --- a/cmd/kubeadm/app/util/config/initconfiguration_test.go +++ b/cmd/kubeadm/app/util/config/initconfiguration_test.go @@ -314,3 +314,56 @@ func TestBytesToInitConfiguration(t *testing.T) { } } } + +func TestSetInitDynamicDefaultsSkipAPIEndpoint(t *testing.T) { + // "not-an-ip" is a sentinel that would cause SetAPIEndpointDynamicDefaults to return + // an error if invoked. With skipAPIEndpoint=true, the value must be left untouched. + const sentinel = "not-an-ip" + + tests := []struct { + name string + skipAPIEndpoint bool + expectErr bool + expectAdvertise string + }{ + { + name: "skip leaves AdvertiseAddress untouched", + skipAPIEndpoint: true, + expectErr: false, + expectAdvertise: sentinel, + }, + { + name: "no skip surfaces invalid AdvertiseAddress error", + skipAPIEndpoint: false, + expectErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cfg := &kubeadmapi.InitConfiguration{ + ClusterConfiguration: kubeadmapi.ClusterConfiguration{ + KubernetesVersion: constants.CurrentKubernetesVersion.String(), + }, + LocalAPIEndpoint: kubeadmapi.APIEndpoint{ + AdvertiseAddress: sentinel, + }, + } + + err := SetInitDynamicDefaults(cfg, true /* skipCRIDetect */, tc.skipAPIEndpoint) + if tc.expectErr { + if err == nil { + t.Fatalf("expected error, got nil") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if cfg.LocalAPIEndpoint.AdvertiseAddress != tc.expectAdvertise { + t.Errorf("AdvertiseAddress = %q, want %q", + cfg.LocalAPIEndpoint.AdvertiseAddress, tc.expectAdvertise) + } + }) + } +}