kubeadm: cleanup after ControlPlaneKubeletLocalMode

This commit is contained in:
Lubomir I. Ivanov
2025-09-17 15:19:09 +02:00
parent 56a4306fed
commit 9d4b91dbd8
5 changed files with 24 additions and 89 deletions

View File

@@ -24,7 +24,6 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/errors"
@@ -158,11 +157,9 @@ func runKubeConfigFile(kubeConfigFileName string) func(workflow.RunData) error {
initConfiguration := data.Cfg().DeepCopy()
if features.Enabled(cfg.FeatureGates, features.ControlPlaneKubeletLocalMode) {
if kubeConfigFileName == kubeadmconstants.KubeletKubeConfigFileName {
// Unset the ControlPlaneEndpoint so the creation falls back to the LocalAPIEndpoint for the kubelet's kubeconfig.
initConfiguration.ControlPlaneEndpoint = ""
}
if kubeConfigFileName == kubeadmconstants.KubeletKubeConfigFileName {
// Unset the ControlPlaneEndpoint so the creation falls back to the LocalAPIEndpoint for the kubelet's kubeconfig.
initConfiguration.ControlPlaneEndpoint = ""
}
// creates the KubeConfig file (or use existing)

View File

@@ -24,7 +24,6 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
markcontrolplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markcontrolplane"
"k8s.io/kubernetes/cmd/kubeadm/app/util/errors"
@@ -83,32 +82,26 @@ func NewControlPlaneJoinPhase() workflow.Phase {
func NewEtcdJoinPhase() workflow.Phase {
return workflow.Phase{
Name: "etcd-join",
Short: fmt.Sprintf("[EXPERIMENTAL] Join etcd for control plane nodes (only used when feature gate %s is enabled)", features.ControlPlaneKubeletLocalMode),
Short: "Join etcd for control plane nodes",
Run: runEtcdPhase,
Example: etcdJoinExample,
InheritFlags: getControlPlaneJoinPhaseFlags("etcd"),
ArgsValidator: cobra.NoArgs,
// TODO: unhide this phase once ControlPlaneKubeletLocalMode goes GA:
// https://github.com/kubernetes/enhancements/issues/4471
Hidden: true,
// Only run this phase as if `ControlPlaneKubeletLocalMode` is activated.
RunIf: func(c workflow.RunData) (bool, error) {
return checkFeatureState(c, features.ControlPlaneKubeletLocalMode, true)
},
}
}
// TODO: Deprecated. Remove once ControlPlaneKubeletLocalMode is removed in 1.36.
// https://github.com/kubernetes/kubeadm/issues/2271
func newEtcdLocalSubphase() workflow.Phase {
return workflow.Phase{
Name: "etcd",
Short: "Add a new local etcd member",
Short: "[DEPRECATED] Add a new local etcd member. Deprecated in favor of 'etcd-join' and will be removed in 1.36",
Run: runEtcdPhase,
InheritFlags: getControlPlaneJoinPhaseFlags("etcd"),
ArgsValidator: cobra.NoArgs,
// Only run this phase as subphase of control-plane-join phase if
// `ControlPlaneKubeletLocalMode` is deactivated.
Hidden: true,
RunIf: func(c workflow.RunData) (bool, error) {
return checkFeatureState(c, features.ControlPlaneKubeletLocalMode, false)
return false, nil
},
}
}

View File

@@ -25,9 +25,6 @@ import (
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/kubernetes/cmd/kubeadm/app/util/errors"
)
// JoinData is the interface to use for join phases.
@@ -47,17 +44,3 @@ type JoinData interface {
ManifestDir() string
CertificateWriteDir() string
}
func checkFeatureState(c workflow.RunData, featureGate string, state bool) (bool, error) {
data, ok := c.(JoinData)
if !ok {
return false, errors.New("control-plane-join phase invoked with an invalid data struct")
}
cfg, err := data.InitCfg()
if err != nil {
return false, err
}
return state == features.Enabled(cfg.FeatureGates, featureGate), nil
}

View File

@@ -75,20 +75,13 @@ func NewKubeletStartPhase() workflow.Phase {
func NewKubeletWaitBootstrapPhase() workflow.Phase {
return workflow.Phase{
Name: "kubelet-wait-bootstrap",
Short: "[EXPERIMENTAL] Wait for the kubelet to bootstrap itself (only used when feature gate ControlPlaneKubeletLocalMode is enabled)",
Short: "Wait for the kubelet to bootstrap itself",
Run: runKubeletWaitBootstrapPhase,
InheritFlags: []string{
options.CfgPath,
options.NodeCRISocket,
options.DryRun,
},
// TODO: unhide this phase once ControlPlaneKubeletLocalMode goes GA:
// https://github.com/kubernetes/enhancements/issues/4471
Hidden: true,
// Only run this phase as if `ControlPlaneKubeletLocalMode` is activated.
RunIf: func(c workflow.RunData) (bool, error) {
return checkFeatureState(c, features.ControlPlaneKubeletLocalMode, true)
},
}
}
@@ -124,16 +117,6 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
}
bootstrapKubeConfigFile := filepath.Join(data.KubeConfigDir(), kubeadmconstants.KubeletBootstrapKubeConfigFileName)
// Do not delete the bootstrapKubeConfigFile at the end of this function when
// using ControlPlaneKubeletLocalMode. The KubeletWaitBootstrapPhase will delete
// it when the feature is enabled.
if !features.Enabled(initCfg.FeatureGates, features.ControlPlaneKubeletLocalMode) {
// Deletes the bootstrapKubeConfigFile, so the credential used for TLS bootstrap is removed from disk
defer func() {
_ = os.Remove(bootstrapKubeConfigFile)
}()
}
var client clientset.Interface
// If dry-use the client from joinData, else create a new bootstrap client
if data.DryRun() {
@@ -148,17 +131,15 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
}
}
if features.Enabled(initCfg.FeatureGates, features.ControlPlaneKubeletLocalMode) {
// Set the server url to LocalAPIEndpoint if the feature gate is enabled so the config
// which gets passed to the kubelet forces it to talk to the local kube-apiserver.
if cfg.ControlPlane != nil {
for c, conf := range tlsBootstrapCfg.Clusters {
conf.Server, err = kubeadmutil.GetLocalAPIEndpoint(&cfg.ControlPlane.LocalAPIEndpoint)
if err != nil {
return errors.Wrapf(err, "could not get LocalAPIEndpoint when %s is enabled", features.ControlPlaneKubeletLocalMode)
}
tlsBootstrapCfg.Clusters[c] = conf
// Set the server url to LocalAPIEndpoint if the feature gate is enabled so the config
// which gets passed to the kubelet forces it to talk to the local kube-apiserver.
if cfg.ControlPlane != nil {
for c, conf := range tlsBootstrapCfg.Clusters {
conf.Server, err = kubeadmutil.GetLocalAPIEndpoint(&cfg.ControlPlane.LocalAPIEndpoint)
if err != nil {
return errors.Wrapf(err, "could not get LocalAPIEndpoint")
}
tlsBootstrapCfg.Clusters[c] = conf
}
}
@@ -252,13 +233,6 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
fmt.Println("[kubelet-start] Starting the kubelet")
kubeletphase.TryStartKubelet()
// Run the same code as KubeletWaitBootstrapPhase would do if the ControlPlaneKubeletLocalMode feature gate is disabled.
if !features.Enabled(initCfg.FeatureGates, features.ControlPlaneKubeletLocalMode) {
if err := runKubeletWaitBootstrapPhase(c); err != nil {
return err
}
}
return nil
}

View File

@@ -180,10 +180,7 @@ func WriteKubeletConfigFiles(cfg *kubeadmapi.InitConfiguration, kubeletDir strin
return nil
}
// UpdateKubeletKubeconfigServer changes the Server URL in the kubelets kubeconfig if necessary depending on the
// ControlPlaneKubeletLocalMode feature gate.
// TODO: remove this function once ControlPlaneKubeletLocalMode goes GA and is hardcoded to be enabled by default:
// https://github.com/kubernetes/kubeadm/issues/2271
// UpdateKubeletKubeconfigServer changes the Server URL in the kubelet's kubeconfig if necessary.
func UpdateKubeletKubeconfigServer(cfg *kubeadmapi.InitConfiguration, dryRun bool) error {
kubeletKubeConfigFilePath := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName)
@@ -216,21 +213,12 @@ func UpdateKubeletKubeconfigServer(cfg *kubeadmapi.InitConfiguration, dryRun boo
}
var targetServer string
if features.Enabled(cfg.FeatureGates, features.ControlPlaneKubeletLocalMode) {
// Skip changing kubeconfig file if Server does not match the ControlPlaneEndpoint.
if config.Clusters[configContext.Cluster].Server != controlPlaneAPIEndpoint || controlPlaneAPIEndpoint == localAPIEndpoint {
klog.V(2).Infof("Skipping update of the Server URL in %s, because it's already not equal to %q or already matches the localAPIEndpoint", kubeletKubeConfigFilePath, controlPlaneAPIEndpoint)
return nil
}
targetServer = localAPIEndpoint
} else {
// Skip changing kubeconfig file if Server does not match the localAPIEndpoint.
if config.Clusters[configContext.Cluster].Server != localAPIEndpoint {
klog.V(2).Infof("Skipping update of the Server URL in %s, because it already matches the controlPlaneAPIEndpoint", kubeletKubeConfigFilePath)
return nil
}
targetServer = controlPlaneAPIEndpoint
// Skip changing kubeconfig file if Server does not match the ControlPlaneEndpoint.
if config.Clusters[configContext.Cluster].Server != controlPlaneAPIEndpoint || controlPlaneAPIEndpoint == localAPIEndpoint {
klog.V(2).Infof("Skipping update of the Server URL in %s, because it's already not equal to %q or already matches the localAPIEndpoint", kubeletKubeConfigFilePath, controlPlaneAPIEndpoint)
return nil
}
targetServer = localAPIEndpoint
if dryRun {
fmt.Printf("[dryrun] Would change the Server URL from %q to %q in %s and try to restart kubelet\n", config.Clusters[configContext.Cluster].Server, targetServer, kubeletKubeConfigFilePath)