kubeadm: fix WaitForAllControlPlaneComponents with anonymous auth

When the kube-apiserver has --anonymous-auth=false,
the regular http.Client.Get() that WaitForAllControlPlaneComponents
does will not work.

Always use the discovery client when checking the health status
of the kube-apiserver.

Do a minor rework of struct fields and unit tests.

Replace nil client in cmd/phases/join/waitcontrolplane.go.
This commit is contained in:
Lubomir I. Ivanov 2025-03-24 20:36:06 +02:00
parent 62555cadc7
commit 310723b21c
3 changed files with 50 additions and 33 deletions

View File

@ -67,7 +67,12 @@ func runWaitControlPlanePhase(c workflow.RunData) error {
return nil
}
waiter, err := newControlPlaneWaiter(data.DryRun(), 0, nil, data.OutputWriter())
client, err := data.Client()
if err != nil {
return err
}
waiter, err := newControlPlaneWaiter(data.DryRun(), 0, client, data.OutputWriter())
if err != nil {
return errors.Wrap(err, "error creating waiter")
}

View File

@ -118,7 +118,8 @@ func NewKubeWaiter(client clientset.Interface, timeout time.Duration, writer io.
// on which to perform health checks.
type controlPlaneComponent struct {
name string
url string
addressPort string
endpoint string
}
// getControlPlaneComponentAddressAndPort parses the command in a static Pod
@ -181,7 +182,6 @@ func getControlPlaneComponents(podMap map[string]*v1.Pod, addressAPIServer strin
type componentConfig struct {
name string
podKey string
args []string
defaultAddr string
defaultPort string
@ -190,24 +190,21 @@ func getControlPlaneComponents(podMap map[string]*v1.Pod, addressAPIServer strin
components := []componentConfig{
{
name: "kube-apiserver",
podKey: constants.KubeAPIServer,
name: constants.KubeAPIServer,
args: []string{argAdvertiseAddress, argPort},
defaultAddr: addressAPIServer,
defaultPort: portAPIServer,
endpoint: endpointLivez,
},
{
name: "kube-controller-manager",
podKey: constants.KubeControllerManager,
name: constants.KubeControllerManager,
args: []string{argBindAddress, argPort},
defaultAddr: addressKCM,
defaultPort: portKCM,
endpoint: endpointHealthz,
},
{
name: "kube-scheduler",
podKey: constants.KubeScheduler,
name: constants.KubeScheduler,
args: []string{argBindAddress, argPort},
defaultAddr: addressScheduler,
defaultPort: portScheduler,
@ -219,8 +216,8 @@ func getControlPlaneComponents(podMap map[string]*v1.Pod, addressAPIServer strin
address, port := component.defaultAddr, component.defaultPort
values, err := getControlPlaneComponentAddressAndPort(
podMap[component.podKey],
component.podKey,
podMap[component.name],
component.name,
component.args,
)
if err != nil {
@ -236,7 +233,8 @@ func getControlPlaneComponents(podMap map[string]*v1.Pod, addressAPIServer strin
result = append(result, controlPlaneComponent{
name: component.name,
url: fmt.Sprintf("https://%s/%s", net.JoinHostPort(address, port), component.endpoint),
addressPort: net.JoinHostPort(address, port),
endpoint: component.endpoint,
})
}
@ -260,7 +258,8 @@ func (w *KubeWaiter) WaitForControlPlaneComponents(podMap map[string]*v1.Pod, ap
errChan := make(chan error, len(components))
for _, comp := range components {
fmt.Printf("[control-plane-check] Checking %s at %s\n", comp.name, comp.url)
url := fmt.Sprintf("https://%s/%s", comp.addressPort, comp.endpoint)
fmt.Printf("[control-plane-check] Checking %s at %s\n", comp.name, url)
go func(comp controlPlaneComponent) {
tr := &http.Transport{
@ -268,6 +267,7 @@ func (w *KubeWaiter) WaitForControlPlaneComponents(podMap map[string]*v1.Pod, ap
}
client := &http.Client{Transport: tr}
start := time.Now()
statusCode := 0
var lastError error
err := wait.PollUntilContextTimeout(
@ -275,18 +275,30 @@ func (w *KubeWaiter) WaitForControlPlaneComponents(podMap map[string]*v1.Pod, ap
constants.KubernetesAPICallRetryInterval,
w.timeout,
true, func(ctx context.Context) (bool, error) {
resp, err := client.Get(comp.url)
if err != nil {
lastError = errors.WithMessagef(err, "%s check failed at %s", comp.name, comp.url)
// The kube-apiserver check should use the client defined in the waiter
// or otherwise the regular http client can fail when anonymous auth is enabled.
if comp.name == constants.KubeAPIServer {
result := w.client.Discovery().RESTClient().
Get().AbsPath(comp.endpoint).Do(ctx).StatusCode(&statusCode)
if err := result.Error(); err != nil {
lastError = errors.WithMessagef(err, "%s check failed at %s", comp.name, url)
return false, nil
}
} else {
resp, err := client.Get(url)
if err != nil {
lastError = errors.WithMessagef(err, "%s check failed at %s", comp.name, url)
return false, nil
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
statusCode = resp.StatusCode
}
if statusCode != http.StatusOK {
lastError = errors.Errorf("%s check failed at %s with status: %d",
comp.name, comp.url, resp.StatusCode)
comp.name, url, statusCode)
return false, nil
}

View File

@ -73,9 +73,9 @@ func TestGetControlPlaneComponents(t *testing.T) {
return podMap
},
expected: []controlPlaneComponent{
{name: "kube-apiserver", url: fmt.Sprintf("https://[fd00:1::]:1111/%s", endpointLivez)},
{name: "kube-controller-manager", url: fmt.Sprintf("https://127.0.0.1:2222/%s", endpointHealthz)},
{name: "kube-scheduler", url: fmt.Sprintf("https://127.0.0.1:3333/%s", endpointLivez)},
{name: "kube-apiserver", addressPort: "[fd00:1::]:1111", endpoint: endpointLivez},
{name: "kube-controller-manager", addressPort: "127.0.0.1:2222", endpoint: endpointHealthz},
{name: "kube-scheduler", addressPort: "127.0.0.1:3333", endpoint: endpointLivez},
},
},
{
@ -106,9 +106,9 @@ func TestGetControlPlaneComponents(t *testing.T) {
return podMap
},
expected: []controlPlaneComponent{
{name: "kube-apiserver", url: fmt.Sprintf("https://[fd00:1::]:1111/%s", endpointLivez)},
{name: "kube-controller-manager", url: fmt.Sprintf("https://127.0.0.1:2222/%s", endpointHealthz)},
{name: "kube-scheduler", url: fmt.Sprintf("https://127.0.0.1:3333/%s", endpointLivez)},
{name: "kube-apiserver", addressPort: "[fd00:1::]:1111", endpoint: endpointLivez},
{name: "kube-controller-manager", addressPort: "127.0.0.1:2222", endpoint: endpointHealthz},
{name: "kube-scheduler", addressPort: "127.0.0.1:3333", endpoint: endpointLivez},
},
},
{
@ -133,9 +133,9 @@ func TestGetControlPlaneComponents(t *testing.T) {
return podMap
},
expected: []controlPlaneComponent{
{name: "kube-apiserver", url: fmt.Sprintf("https://192.168.0.1:6443/%s", endpointLivez)},
{name: "kube-controller-manager", url: fmt.Sprintf("https://127.0.0.1:10257/%s", endpointHealthz)},
{name: "kube-scheduler", url: fmt.Sprintf("https://127.0.0.1:10259/%s", endpointLivez)},
{name: "kube-apiserver", addressPort: "192.168.0.1:6443", endpoint: endpointLivez},
{name: "kube-controller-manager", addressPort: "127.0.0.1:10257", endpoint: endpointHealthz},
{name: "kube-scheduler", addressPort: "127.0.0.1:10259", endpoint: endpointLivez},
},
},
{