package node import ( "context" "errors" "fmt" "os" "strings" "k8s.io/klog/v2" "example.com/monok8s/pkg/system" ) const kubeadmUpgradeNodeHostnameBugFixedIn = "v1.35.0" // COMPAT(kubeadm-upgrade-node-hostname) // Affects: Kubernetes/kubeadm < v1.35.0 // Upstream: kubernetes/kubeadm#3244, kubernetes/kubernetes#134319 // RemoveWhen: minimum supported Kubernetes version >= v1.35.0 // // Affected kubeadm versions can derive the target Node name for // `kubeadm upgrade node` from the local OS hostname instead of the existing // kubeadm NodeRegistration / kubelet --hostname-override state. func needsKubeadmUpgradeNodeHostnameWorkaround(kubeadmVersion string) bool { lt, err := versionLt(kubeadmVersion, kubeadmUpgradeNodeHostnameBugFixedIn) if err != nil { klog.Warningf( "could not parse kubeadm version %q; enabling kubeadm upgrade node hostname workaround: %v", kubeadmVersion, err, ) return true } return lt } // runWithTemporaryHostname works around kubernetes/kubeadm#3244, fixed by // kubernetes/kubernetes#134319 in Kubernetes v1.35.0. // // Affected kubeadm versions can derive the target Node name for // `kubeadm upgrade node` from the local OS hostname instead of the existing // kubeadm NodeRegistration / kubelet --hostname-override state. That breaks // valid setups where the machine hostname differs from the Kubernetes Node // name: kubeadm may authenticate as one node but try to get/patch another Node, // and the Node authorizer correctly rejects it. // // Keep this workaround scoped to affected kubeadm versions only. Set the // temporary hostname to the Kubernetes Node name, run kubeadm, then restore the // configured machine hostname immediately afterward. func runWithTemporaryHostname(ctx context.Context, nctx *NodeContext, fn func(context.Context) error) error { if nctx == nil { return errors.New("node context is nil") } temporaryHostname := strings.TrimSpace(nctx.Config.Spec.NodeName) if temporaryHostname == "" { return errors.New("temporary hostname is required") } originalHostname, err := os.Hostname() if err != nil { return fmt.Errorf("get current hostname: %w", err) } if originalHostname == temporaryHostname { return fn(ctx) } restoreHostname := strings.TrimSpace(nctx.Config.Spec.Network.Hostname) if restoreHostname == "" { restoreHostname = originalHostname } klog.Warningf( "temporarily changing hostname for kubeadm upgrade node: current=%q temporary=%q restore=%q", originalHostname, temporaryHostname, restoreHostname, ) if err := system.SetHostname(temporaryHostname); err != nil { return fmt.Errorf("set temporary hostname to %q: %w", temporaryHostname, err) } defer func() { if err := system.SetHostname(restoreHostname); err != nil { klog.Errorf("failed to restore hostname to %q: %v", restoreHostname, err) } }() return fn(ctx) } // COMPAT(kubeadm-upgrade-node-hostname) // RemoveWhen: minimum supported Kubernetes version >= v1.35.0 func runKubeadmUpgradeNodeWithCompat( ctx context.Context, nctx *NodeContext, kubeadmVersion string, fn func(context.Context) error, ) error { if needsKubeadmUpgradeNodeHostnameWorkaround(kubeadmVersion) { return runWithTemporaryHostname(ctx, nctx, fn) } return fn(ctx) }