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 <seenafallah@gmail.com>
This commit is contained in:
Seena Fallah
2026-04-29 21:19:33 +02:00
committed by Lubomir I. Ivanov
parent 32137bf43b
commit a051bd7fc8
3 changed files with 66 additions and 9 deletions

View File

@@ -68,9 +68,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
}

View File

@@ -57,16 +57,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)
}
@@ -246,7 +248,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)
@@ -401,7 +403,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
}

View File

@@ -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)
}
})
}
}