penguin/monok8s

k8s image for Mono Gateway Dev Kit

clitools/pkg/node/kubeadm_compat.go

raw ยท 3262 bytes

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