diff --git a/internal/app/machined/pkg/controllers/k8s/node_labels_apply.go b/internal/app/machined/pkg/controllers/k8s/node_labels_apply.go index 9c289f3be2..f35dd154c2 100644 --- a/internal/app/machined/pkg/controllers/k8s/node_labels_apply.go +++ b/internal/app/machined/pkg/controllers/k8s/node_labels_apply.go @@ -113,18 +113,7 @@ func (ctrl *NodeLabelsApplyController) getK8sClient(ctx context.Context, r contr } if machineType.MachineType().IsControlPlane() { - k8sRoot, err := safe.ReaderGet[*secrets.KubernetesRoot](ctx, r, resource.NewMetadata(secrets.NamespaceName, secrets.KubernetesRootType, secrets.KubernetesRootID, resource.VersionUndefined)) - if err != nil { - if state.IsNotFoundError(err) { - return nil, nil - } - - return nil, fmt.Errorf("failed to get kubernetes config: %w", err) - } - - k8sRootSpec := k8sRoot.TypedSpec() - - return kubernetes.NewTemporaryClientFromPKI(k8sRootSpec.CA, k8sRootSpec.Endpoint) + return kubernetes.NewTemporaryClientControlPlane(ctx, r) } logger.Debug("waiting for kubelet client config", zap.String("file", constants.KubeletKubeconfig)) diff --git a/internal/app/machined/pkg/controllers/secrets/kubelet.go b/internal/app/machined/pkg/controllers/secrets/kubelet.go index d01a2d7b35..1ce01a76ea 100644 --- a/internal/app/machined/pkg/controllers/secrets/kubelet.go +++ b/internal/app/machined/pkg/controllers/secrets/kubelet.go @@ -7,6 +7,7 @@ package secrets import ( "context" "fmt" + "net/url" "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" @@ -84,7 +85,18 @@ func (ctrl *KubeletController) Run(ctx context.Context, r controller.Runtime, lo } func (ctrl *KubeletController) updateKubeletSecrets(cfgProvider talosconfig.Provider, kubeletSecrets *secrets.KubeletSpec) error { - kubeletSecrets.Endpoint = cfgProvider.Cluster().Endpoint() + if cfgProvider.Machine().Type().IsControlPlane() { + // use localhost endpoint for controlplane nodes + localEndpoint, err := url.Parse(fmt.Sprintf("https://localhost:%d", cfgProvider.Cluster().LocalAPIServerPort())) + if err != nil { + return err + } + + kubeletSecrets.Endpoint = localEndpoint + } else { + // use cluster endpoint for workers + kubeletSecrets.Endpoint = cfgProvider.Cluster().Endpoint() + } kubeletSecrets.CA = cfgProvider.Cluster().CA() diff --git a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go index f507f704ca..bdd142f7c5 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go @@ -1441,7 +1441,11 @@ func UncordonNode(seq runtime.Sequence, data interface{}) (runtime.TaskExecution if err = retry.Constant(5*time.Minute, retry.WithUnits(time.Second), retry.WithErrorLogging(true)).RetryWithContext(ctx, func(ctx context.Context) error { - kubeHelper, err = kubernetes.NewClientFromKubeletKubeconfig() + if r.Config().Machine().Type().IsControlPlane() { + kubeHelper, err = kubernetes.NewTemporaryClientControlPlane(ctx, r.State().V1Alpha2().Resources()) + } else { + kubeHelper, err = kubernetes.NewClientFromKubeletKubeconfig() + } return retry.ExpectedError(err) }); err != nil { @@ -1750,7 +1754,7 @@ func LabelNodeAsControlPlane(seq runtime.Sequence, data interface{}) (runtime.Ta err = retry.Constant(constants.NodeReadyTimeout, retry.WithUnits(3*time.Second), retry.WithErrorLogging(true)).RetryWithContext(ctx, func(ctx context.Context) error { var h *kubernetes.Client - h, err = kubernetes.NewTemporaryClientFromPKI(r.Config().Cluster().CA(), r.Config().Cluster().Endpoint()) + h, err = kubernetes.NewTemporaryClientControlPlane(ctx, r.State().V1Alpha2().Resources()) if err != nil { return err } diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index afa7675ead..2a93003cfa 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -14,6 +14,10 @@ import ( "net/url" "time" + "github.com/cosi-project/runtime/pkg/controller" + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" "github.com/siderolabs/crypto/x509" "github.com/siderolabs/go-retry/retry" "golang.org/x/sync/errgroup" @@ -32,6 +36,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1/machine" "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/machinery/resources/secrets" ) const ( @@ -135,6 +140,25 @@ func NewClientFromPKI(ca, crt, key []byte, endpoint *url.URL) (client *Client, e }, nil } +// NewTemporaryClientControlPlane initializes a Kubernetes client for a controlplane node +// using PKI information. +// +// The client uses "localhost" endpoint, so it doesn't depend on the loadbalancer to be ready. +func NewTemporaryClientControlPlane(ctx context.Context, r controller.Reader) (client *Client, err error) { + k8sRoot, err := safe.ReaderGet[*secrets.KubernetesRoot](ctx, r, resource.NewMetadata(secrets.NamespaceName, secrets.KubernetesRootType, secrets.KubernetesRootID, resource.VersionUndefined)) + if err != nil { + if state.IsNotFoundError(err) { + return nil, nil + } + + return nil, fmt.Errorf("failed to get kubernetes config: %w", err) + } + + k8sRootSpec := k8sRoot.TypedSpec() + + return NewTemporaryClientFromPKI(k8sRootSpec.CA, k8sRootSpec.LocalEndpoint) +} + // NewTemporaryClientFromPKI initializes a Kubernetes client using a certificate // with a TTL of 10 minutes. func NewTemporaryClientFromPKI(ca *x509.PEMEncodedCertificateAndKey, endpoint *url.URL) (client *Client, err error) {