penguin/monok8s

k8s image for Mono Gateway Dev Kit

commit 68e7dcd0019f3004ce548a944de4e616bc58a8e03e11b3dcf1c46319100d1878

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-03-30T10:41:18Z
subjectCan now reconcile
commit 68e7dcd0019f3004ce548a944de4e616bc58a8e03e11b3dcf1c46319100d1878
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-03-30T10:41:18Z

    Can now reconcile
---
 clitools/pkg/bootstrap/registry.go  |   5 +-
 clitools/pkg/bootstrap/runner.go    |  36 ++++-------
 clitools/pkg/node/kubeadm.go        | 121 +++++++++++-------------------------
 clitools/pkg/node/kubelet.go        |  38 +++++++++++
 clitools/pkg/node/metadata.go       |  91 ++++++++++++++++++++-------
 clitools/pkg/node/prereqs.go        |  83 ++++++++++++++++++++++++-
 clitools/pkg/templates/templates.go |   6 +-
 7 files changed, 242 insertions(+), 138 deletions(-)

diff --git a/clitools/pkg/bootstrap/registry.go b/clitools/pkg/bootstrap/registry.go
index ff84686..922f619 100644
--- a/clitools/pkg/bootstrap/registry.go
+++ b/clitools/pkg/bootstrap/registry.go
@@ -25,7 +25,6 @@ func NewRegistry(ctx *node.NodeContext) *Registry {
 			"AllowSingleNodeScheduling":              node.AllowSingleNodeScheduling,
 			"ApplyLocalNodeMetadataIfPossible":       node.ApplyLocalNodeMetadataIfPossible,
 			"CheckForVersionSkew":                    node.CheckForVersionSkew,
-			"CheckUpgradePrereqs":                    node.CheckUpgradePrereqs,
 			"ClassifyBootstrapAction":                node.ClassifyBootstrapAction,
 			"ConfigureDNS":                           node.ConfigureDNS(netCfg),
 			"ConfigureDefaultCNI":                    node.ConfigureDefaultCNI,
@@ -33,14 +32,12 @@ func NewRegistry(ctx *node.NodeContext) *Registry {
 			"ConfigureMgmtInterface":                 node.ConfigureMgmtInterface(netCfg),
 			"DetectLocalClusterState":                node.DetectLocalClusterState,
 			"EnsureIPForward":                        node.EnsureIPForward,
-			"PrintSummary":                           node.PrintSummary,
 			"ReconcileControlPlane":                  node.ReconcileControlPlane,
-			"ReconcileNode":                          node.ReconcileNode,
+			"ReconcileWorker":                        node.ReconcileWorker,
 			"RunKubeadmInit":                         node.RunKubeadmInit,
 			"RunKubeadmJoin":                         node.RunKubeadmJoin,
 			"RunKubeadmUpgradeApply":                 node.RunKubeadmUpgradeApply,
 			"RunKubeadmUpgradeNode":                  node.RunKubeadmUpgradeNode,
-			"SetHostnameIfNeeded":                    node.SetHostnameIfNeeded,
 			"StartCRIO":                              node.StartCRIO,
 			"ValidateNodeIPAndAPIServerReachability": node.ValidateNodeIPAndAPIServerReachability,
 			"ValidateRequiredImagesPresent":          node.ValidateRequiredImagesPresent,
diff --git a/clitools/pkg/bootstrap/runner.go b/clitools/pkg/bootstrap/runner.go
index 9f3d0c7..f4b30a0 100644
--- a/clitools/pkg/bootstrap/runner.go
+++ b/clitools/pkg/bootstrap/runner.go
@@ -86,16 +86,16 @@ func NewRunner(cfg *monov1alpha1.MonoKSConfig) *Runner {
 				Name:   "Classify bootstrap action",
 				Desc:   "Decide whether to init, join, upgrade, or reconcile based on local state and desired version",
 			},
-			{
-				RegKey: "CheckForVersionSkew",
-				Name:   "Check for version skew",
-				Desc:   "Validate wether version satisfy the requirements againts current cluster if any",
-			},
 			{
 				RegKey: "RunKubeadmInit",
 				Name:   "Run kubeadm init",
 				Desc:   "Initialize a new Kubernetes control plane using kubeadm",
 			},
+			{
+				RegKey: "RunKubeadmJoin",
+				Name:   "Run kubeadm join",
+				Desc:   "Join node to existing cluster as worker or control-plane",
+			},
 			{
 				RegKey: "WaitForExistingClusterIfNeeded",
 				Name:   "Wait for existing cluster",
@@ -107,25 +107,20 @@ func NewRunner(cfg *monov1alpha1.MonoKSConfig) *Runner {
 				Desc:   "Ensure control plane components match desired state without full reinitialization",
 			},
 			{
-				RegKey: "CheckUpgradePrereqs",
-				Name:   "Check upgrade prerequisites",
-				Desc:   "Validate cluster state and version compatibility before upgrade",
+				RegKey: "ReconcileWorker",
+				Name:   "Reconcile worker node",
+				Desc:   "Reconcile the worker node",
+			},
+			{
+				RegKey: "CheckForVersionSkew",
+				Name:   "Check for version skew",
+				Desc:   "Validate wether version satisfy the requirements againts current cluster if any",
 			},
 			{
 				RegKey: "RunKubeadmUpgradeApply",
 				Name:   "Run kubeadm upgrade apply",
 				Desc:   "Upgrade control plane components using kubeadm",
 			},
-			{
-				RegKey: "RunKubeadmJoin",
-				Name:   "Run kubeadm join",
-				Desc:   "Join node to existing cluster as worker or control-plane",
-			},
-			{
-				RegKey: "ReconcileNode",
-				Name:   "Reconcile node state",
-				Desc:   "Ensure node configuration matches desired state after join or upgrade",
-			},
 			{
 				RegKey: "RunKubeadmUpgradeNode",
 				Name:   "Run kubeadm upgrade node",
@@ -141,11 +136,6 @@ func NewRunner(cfg *monov1alpha1.MonoKSConfig) *Runner {
 				Name:   "Allow single-node scheduling",
 				Desc:   "Remove control-plane taints to allow workloads on single-node clusters",
 			},
-			{
-				RegKey: "PrintSummary",
-				Name:   "Print summary",
-				Desc:   "Output final bootstrap summary and detected state",
-			},
 		},
 	}
 }
diff --git a/clitools/pkg/node/kubeadm.go b/clitools/pkg/node/kubeadm.go
index fd943ce..adc9b86 100644
--- a/clitools/pkg/node/kubeadm.go
+++ b/clitools/pkg/node/kubeadm.go
@@ -114,89 +114,6 @@ func WaitForExistingClusterIfNeeded(ctx context.Context, nctx *NodeContext) erro
 	}
 }
 
-func CheckForVersionSkew(ctx context.Context, nctx *NodeContext) error {
-	if nctx.BootstrapState == nil {
-		return errors.New("BootstrapState is nil, call ClassifyBootstrapAction() first")
-	}
-
-	role := strings.TrimSpace(nctx.Config.Spec.ClusterRole)
-	wantVersion := normalizeKubeVersion(strings.TrimSpace(nctx.Config.Spec.KubernetesVersion))
-	if wantVersion == "" {
-		return errors.New("spec.kubernetesVersion is required")
-	}
-
-	switch nctx.LocalClusterState.MembershipKind {
-	case LocalMembershipFresh:
-		// Nothing to compare for fresh nodes.
-		return nil
-	case LocalMembershipPartial:
-		return fmt.Errorf("cannot check version skew with partial local cluster state")
-	}
-
-	versionKubeconfig := chooseVersionKubeconfig(nctx.LocalClusterState)
-	if versionKubeconfig == "" {
-		return fmt.Errorf("no kubeconfig available for version detection")
-	}
-
-	currentVersion, err := getServerVersion(ctx, versionKubeconfig)
-	if err != nil {
-		if role == "control-plane" {
-			return fmt.Errorf("existing control-plane state found, but cluster version could not be determined: %w", err)
-		}
-
-		// Worker path stays permissive.
-		nctx.BootstrapState.UnsupportedWorkerVersionSkew = true
-		nctx.BootstrapState.VersionSkewReason = "cluster version could not be determined"
-
-		if nctx.BootstrapState.Action == BootstrapActionManageWorker {
-			nctx.BootstrapState.Action = BootstrapActionReconcileWorker
-		}
-		return nil
-	}
-
-	nctx.BootstrapState.DetectedClusterVersion = currentVersion
-
-	switch role {
-	case "control-plane":
-		if !isSupportedControlPlaneSkew(currentVersion, wantVersion) {
-			return fmt.Errorf(
-				"unsupported control-plane version skew: cluster=%s node=%s",
-				currentVersion, wantVersion,
-			)
-		}
-
-		if nctx.BootstrapState.Action == BootstrapActionManageControlPlane {
-			if versionEq(currentVersion, wantVersion) {
-				nctx.BootstrapState.Action = BootstrapActionReconcileControlPlane
-			} else {
-				nctx.BootstrapState.Action = BootstrapActionUpgradeControlPlane
-			}
-		}
-
-	case "worker":
-		if !isSupportedWorkerSkew(currentVersion, wantVersion) {
-			nctx.BootstrapState.UnsupportedWorkerVersionSkew = true
-			nctx.BootstrapState.VersionSkewReason = fmt.Sprintf(
-				"unsupported worker version skew: cluster=%s node=%s",
-				currentVersion, wantVersion,
-			)
-		}
-
-		if nctx.BootstrapState.Action == BootstrapActionManageWorker {
-			if versionEq(currentVersion, wantVersion) {
-				nctx.BootstrapState.Action = BootstrapActionReconcileWorker
-			} else {
-				nctx.BootstrapState.Action = BootstrapActionUpgradeWorker
-			}
-		}
-
-	default:
-		return fmt.Errorf("unsupported cluster role %q", role)
-	}
-
-	return nil
-}
-
 func ClassifyBootstrapAction(ctx context.Context, nctx *NodeContext) error {
 	_ = ctx
 
@@ -863,3 +780,41 @@ func RunKubeadmUpgradeNode(context.Context, *NodeContext) error {
 	klog.Info("run_kubeadm_upgrade_node: TODO implement kubeadm upgrade node")
 	return nil
 }
+
+func ReconcileControlPlane(ctx context.Context, nctx *NodeContext) error {
+	if nctx.BootstrapState == nil {
+		return errors.New("BootstrapState is nil, call ClassifyBootstrapAction() first")
+	}
+
+	if nctx.BootstrapState.Action != BootstrapActionReconcileControlPlane {
+		klog.V(4).Infof("ReconcileControlPlane skipped for action %q", nctx.BootstrapState.Action)
+		return nil
+	}
+	if err := StartKubelet(ctx, nctx); err != nil {
+		return fmt.Errorf("start kubelet: %w", err)
+	}
+	if err := waitForLocalAPIServer(ctx, nctx, 2*time.Minute); err != nil {
+		return fmt.Errorf("wait for local apiserver: %w", err)
+	}
+	if err := waitForAPIViaKubeconfig(ctx, adminKubeconfigPath, 2*time.Minute); err != nil {
+		return fmt.Errorf("wait for admin api: %w", err)
+	}
+	return nil
+}
+
+func ReconcileWorker(ctx context.Context, nctx *NodeContext) error {
+	if nctx.BootstrapState == nil {
+		return errors.New("BootstrapState is nil, call ClassifyBootstrapAction() first")
+	}
+	if nctx.BootstrapState.Action != BootstrapActionReconcileWorker {
+		klog.V(4).Infof("ReconcileWorker skipped for action %q", nctx.BootstrapState.Action)
+		return nil
+	}
+	if err := StartKubelet(ctx, nctx); err != nil {
+		return fmt.Errorf("start kubelet: %w", err)
+	}
+	if err := waitForKubeletHealthy(ctx, 2*time.Minute); err != nil {
+		return fmt.Errorf("wait for kubelet healthy: %w", err)
+	}
+	return nil
+}
diff --git a/clitools/pkg/node/kubelet.go b/clitools/pkg/node/kubelet.go
index 6096976..6227bfc 100644
--- a/clitools/pkg/node/kubelet.go
+++ b/clitools/pkg/node/kubelet.go
@@ -2,6 +2,11 @@ package node
 
 import (
 	"context"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+	"time"
 
 	system "undecided.project/monok8s/pkg/system"
 )
@@ -9,3 +14,36 @@ import (
 func StartKubelet(ctx context.Context, n *NodeContext) error {
 	return system.EnsureServiceRunning(ctx, n.SystemRunner, "kubelet")
 }
+
+func waitForKubeletHealthy(ctx context.Context, timeout time.Duration) error {
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	defer cancel()
+
+	url := "http://127.0.0.1:10248/healthz"
+	client := &http.Client{
+		Timeout: 2 * time.Second,
+	}
+
+	ticker := time.NewTicker(2 * time.Second)
+	defer ticker.Stop()
+
+	for {
+		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+		if err == nil {
+			resp, err := client.Do(req)
+			if err == nil {
+				body, _ := io.ReadAll(resp.Body)
+				resp.Body.Close()
+				if resp.StatusCode == http.StatusOK && strings.TrimSpace(string(body)) == "ok" {
+					return nil
+				}
+			}
+		}
+
+		select {
+		case <-ctx.Done():
+			return fmt.Errorf("kubelet health endpoint did not become ready: %w", ctx.Err())
+		case <-ticker.C:
+		}
+	}
+}
diff --git a/clitools/pkg/node/metadata.go b/clitools/pkg/node/metadata.go
index cbd869b..125d470 100644
--- a/clitools/pkg/node/metadata.go
+++ b/clitools/pkg/node/metadata.go
@@ -1,37 +1,80 @@
 package node
 
 import (
-    "context"
+	"context"
+	"fmt"
+	"os"
+	"strings"
 
-    "k8s.io/klog/v2"
+	"k8s.io/client-go/tools/clientcmd"
+	"k8s.io/klog/v2"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kubernetes "k8s.io/client-go/kubernetes"
 )
 
-func ApplyLocalNodeMetadataIfPossible(context.Context, *NodeContext) error {
-    klog.Info("apply_local_node_metadata_if_possible: TODO implement node labels/annotations")
-    return nil
-}
+func ApplyLocalNodeMetadataIfPossible(ctx context.Context, nctx *NodeContext) error {
+	spec := nctx.Config.Spec
 
-func AllowSingleNodeScheduling(context.Context, *NodeContext) error {
-    klog.Info("allow_single_node_scheduling: TODO implement control-plane taint removal")
-    return nil
-}
+	if len(spec.NodeAnnotations) == 0 && len(spec.NodeLabels) == 0 {
+		return nil // nothing to do
+	}
 
-func SetHostnameIfNeeded(context.Context, *NodeContext) error {
-    klog.Info("set_hostname_if_needed: TODO implement hostname and /etc/hostname reconciliation")
-    return nil
-}
+	nodeName := strings.TrimSpace(spec.NodeName)
+	if nodeName == "" {
+		return fmt.Errorf("spec.nodeName is required")
+	}
 
-func PrintSummary(context.Context, *NodeContext) error {
-    klog.Info("print_summary: TODO emit final summary")
-    return nil
-}
+	// Prefer admin kubeconfig (control-plane)
+	kubeconfig := adminKubeconfigPath
+
+	if _, err := os.Stat(kubeconfig); err != nil {
+		klog.V(2).Infof("admin kubeconfig not found, skipping node metadata apply")
+		return nil
+	}
+
+	restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
+	if err != nil {
+		return fmt.Errorf("build kubeconfig: %w", err)
+	}
 
-func ReconcileControlPlane(context.Context, *NodeContext) error {
-    klog.Info("reconcile_control_plane: TODO implement existing CP reconciliation")
-    return nil
+	client, err := kubernetes.NewForConfig(restConfig)
+	if err != nil {
+		return fmt.Errorf("create kubernetes client: %w", err)
+	}
+
+	node, err := client.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
+	if err != nil {
+		return fmt.Errorf("get node %q: %w", nodeName, err)
+	}
+
+	if node.Labels == nil {
+		node.Labels = make(map[string]string)
+	}
+	if node.Annotations == nil {
+		node.Annotations = make(map[string]string)
+	}
+
+	// Apply labels
+	for k, v := range spec.NodeLabels {
+		node.Labels[k] = v
+	}
+
+	// Apply annotations
+	for k, v := range spec.NodeAnnotations {
+		node.Annotations[k] = v
+	}
+
+	_, err = client.CoreV1().Nodes().Update(ctx, node, metav1.UpdateOptions{})
+	if err != nil {
+		return fmt.Errorf("update node metadata: %w", err)
+	}
+
+	klog.Infof("applied labels/annotations to node %q", nodeName)
+	return nil
 }
 
-func ReconcileNode(context.Context, *NodeContext) error {
-    klog.Info("reconcile_node: TODO implement existing joined node reconciliation")
-    return nil
+func AllowSingleNodeScheduling(context.Context, *NodeContext) error {
+	klog.Info("allow_single_node_scheduling: TODO implement control-plane taint removal")
+	return nil
 }
diff --git a/clitools/pkg/node/prereqs.go b/clitools/pkg/node/prereqs.go
index 5bb4e73..6551116 100644
--- a/clitools/pkg/node/prereqs.go
+++ b/clitools/pkg/node/prereqs.go
@@ -2,6 +2,7 @@ package node
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net"
 	"strings"
@@ -106,7 +107,85 @@ func ValidateNodeIPAndAPIServerReachability(ctx context.Context, nct *NodeContex
 	return nil
 }
 
-func CheckUpgradePrereqs(context.Context, *NodeContext) error {
-	klog.Info("check_upgrade_prereqs: TODO implement kubeadm version / skew checks")
+func CheckForVersionSkew(ctx context.Context, nctx *NodeContext) error {
+	if nctx.BootstrapState == nil {
+		return errors.New("BootstrapState is nil, call ClassifyBootstrapAction() first")
+	}
+
+	role := strings.TrimSpace(nctx.Config.Spec.ClusterRole)
+	wantVersion := normalizeKubeVersion(strings.TrimSpace(nctx.Config.Spec.KubernetesVersion))
+	if wantVersion == "" {
+		return errors.New("spec.kubernetesVersion is required")
+	}
+
+	switch nctx.LocalClusterState.MembershipKind {
+	case LocalMembershipFresh:
+		// Nothing to compare for fresh nodes.
+		return nil
+	case LocalMembershipPartial:
+		return fmt.Errorf("cannot check version skew with partial local cluster state")
+	}
+
+	versionKubeconfig := chooseVersionKubeconfig(nctx.LocalClusterState)
+	if versionKubeconfig == "" {
+		return fmt.Errorf("no kubeconfig available for version detection")
+	}
+
+	currentVersion, err := getServerVersion(ctx, versionKubeconfig)
+	if err != nil {
+		if role == "control-plane" {
+			return fmt.Errorf("existing control-plane state found, but cluster version could not be determined: %w", err)
+		}
+
+		// Worker path stays permissive.
+		nctx.BootstrapState.UnsupportedWorkerVersionSkew = true
+		nctx.BootstrapState.VersionSkewReason = "cluster version could not be determined"
+
+		if nctx.BootstrapState.Action == BootstrapActionManageWorker {
+			nctx.BootstrapState.Action = BootstrapActionReconcileWorker
+		}
+		return nil
+	}
+
+	nctx.BootstrapState.DetectedClusterVersion = currentVersion
+
+	switch role {
+	case "control-plane":
+		if !isSupportedControlPlaneSkew(currentVersion, wantVersion) {
+			return fmt.Errorf(
+				"unsupported control-plane version skew: cluster=%s node=%s",
+				currentVersion, wantVersion,
+			)
+		}
+
+		if nctx.BootstrapState.Action == BootstrapActionManageControlPlane {
+			if versionEq(currentVersion, wantVersion) {
+				nctx.BootstrapState.Action = BootstrapActionReconcileControlPlane
+			} else {
+				nctx.BootstrapState.Action = BootstrapActionUpgradeControlPlane
+			}
+		}
+
+	case "worker":
+		if !isSupportedWorkerSkew(currentVersion, wantVersion) {
+			nctx.BootstrapState.UnsupportedWorkerVersionSkew = true
+			nctx.BootstrapState.VersionSkewReason = fmt.Sprintf(
+				"unsupported worker version skew: cluster=%s node=%s",
+				currentVersion, wantVersion,
+			)
+		}
+
+		if nctx.BootstrapState.Action == BootstrapActionManageWorker {
+			if versionEq(currentVersion, wantVersion) {
+				nctx.BootstrapState.Action = BootstrapActionReconcileWorker
+			} else {
+				nctx.BootstrapState.Action = BootstrapActionUpgradeWorker
+			}
+		}
+
+	default:
+		return fmt.Errorf("unsupported cluster role %q", role)
+	}
+
 	return nil
 }
diff --git a/clitools/pkg/templates/templates.go b/clitools/pkg/templates/templates.go
index e68a96e..7598a09 100644
--- a/clitools/pkg/templates/templates.go
+++ b/clitools/pkg/templates/templates.go
@@ -66,10 +66,12 @@ func DefaultMonoKSConfig() types.MonoKSConfig {
 			},
 
 			NodeLabels: map[string]string{
-				"monok8s.io/label": "label",
+				"monok8s.io/label": "value",
 			},
 
-			NodeAnnotations: map[string]string{},
+			NodeAnnotations: map[string]string{
+				"monok8s.io/annotation": "value",
+			},
 
 			Network: types.NetworkSpec{
 				Hostname:        "monok8s-worker-1",