commit 210fabdcc6f6ecff6a8a93fb0c3ab647619b433e8c0f8d1a5a67a5c26df26fc9
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-03-29T17:31:38Z |
| subject | Added kubeadm init |
commit 210fabdcc6f6ecff6a8a93fb0c3ab647619b433e8c0f8d1a5a67a5c26df26fc9
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-03-29T17:31:38Z
Added kubeadm init
---
clitools/pkg/bootstrap/registry.go | 46 ++++----
clitools/pkg/bootstrap/runner.go | 67 +++++------
clitools/pkg/cmd/initcmd/init.go | 59 ++++++----
clitools/pkg/node/kubeadm.go | 228 ++++++++++++++++++++++++++++++++++--
clitools/pkg/node/prereqs.go | 2 +-
clitools/pkg/system/runner.go | 93 +++++++++++++--
clitools/pkg/templates/templates.go | 26 +++-
7 files changed, 419 insertions(+), 102 deletions(-)
diff --git a/clitools/pkg/bootstrap/registry.go b/clitools/pkg/bootstrap/registry.go
index 412dfd7..560b17e 100644
--- a/clitools/pkg/bootstrap/registry.go
+++ b/clitools/pkg/bootstrap/registry.go
@@ -22,29 +22,29 @@ func NewRegistry(ctx *node.NodeContext) *Registry {
return &Registry{
steps: map[string]node.Step{
- "validate_network_requirements": node.ValidateNetworkRequirements,
- "detect_local_cluster_state": node.DetectLocalClusterState,
- "classify_bootstrap_action": node.ClassifyBootstrapAction,
- "configure_default_cni": node.ConfigureDefaultCNI,
- "start_crio": node.StartCRIO,
- "wait_for_existing_cluster_if_needed": node.WaitForExistingClusterIfNeeded,
- "validate_required_images": node.ValidateRequiredImagesPresent,
- "generate_kubeadm_config": node.GenerateKubeadmConfig,
- "run_kubeadm_init": node.RunKubeadmInit,
- "apply_local_node_metadata_if_possible": node.ApplyLocalNodeMetadataIfPossible,
- "allow_single_node_scheduling": node.AllowSingleNodeScheduling,
- "ensure_ip_forward": node.EnsureIPForward,
- "configure_hostname": node.ConfigureHostname(netCfg),
- "configure_mgmt_interface": node.ConfigureMgmtInterface(netCfg),
- "configure_dns": node.ConfigureDNS(netCfg),
- "set_hostname_if_needed": node.SetHostnameIfNeeded,
- "print_summary": node.PrintSummary,
- "reconcile_control_plane": node.ReconcileControlPlane,
- "check_upgrade_prereqs": node.CheckUpgradePrereqs,
- "run_kubeadm_upgrade_apply": node.RunKubeadmUpgradeApply,
- "run_kubeadm_join": node.RunKubeadmJoin,
- "reconcile_node": node.ReconcileNode,
- "run_kubeadm_upgrade_node": node.RunKubeadmUpgradeNode,
+ "AllowSingleNodeScheduling": node.AllowSingleNodeScheduling,
+ "ApplyLocalNodeMetadataIfPossible": node.ApplyLocalNodeMetadataIfPossible,
+ "CheckUpgradePrereqs": node.CheckUpgradePrereqs,
+ "ClassifyBootstrapAction": node.ClassifyBootstrapAction,
+ "ConfigureDNS": node.ConfigureDNS(netCfg),
+ "ConfigureDefaultCNI": node.ConfigureDefaultCNI,
+ "ConfigureHostname": node.ConfigureHostname(netCfg),
+ "ConfigureMgmtInterface": node.ConfigureMgmtInterface(netCfg),
+ "DetectLocalClusterState": node.DetectLocalClusterState,
+ "EnsureIPForward": node.EnsureIPForward,
+ "GenerateKubeadmConfig": node.GenerateKubeadmConfig,
+ "PrintSummary": node.PrintSummary,
+ "ReconcileControlPlane": node.ReconcileControlPlane,
+ "ReconcileNode": node.ReconcileNode,
+ "RunKubeadmInit": node.RunKubeadmInit,
+ "RunKubeadmJoin": node.RunKubeadmJoin,
+ "RunKubeadmUpgradeApply": node.RunKubeadmUpgradeApply,
+ "RunKubeadmUpgradeNode": node.RunKubeadmUpgradeNode,
+ "SetHostnameIfNeeded": node.SetHostnameIfNeeded,
+ "StartCRIO": node.StartCRIO,
+ "ValidateNodeIPAndAPIServerReachability": node.ValidateNodeIPAndAPIServerReachability,
+ "ValidateRequiredImagesPresent": node.ValidateRequiredImagesPresent,
+ "WaitForExistingClusterIfNeeded": node.WaitForExistingClusterIfNeeded,
},
}
}
diff --git a/clitools/pkg/bootstrap/runner.go b/clitools/pkg/bootstrap/runner.go
index 15e27d1..ae13886 100644
--- a/clitools/pkg/bootstrap/runner.go
+++ b/clitools/pkg/bootstrap/runner.go
@@ -37,112 +37,107 @@ func NewRunner(cfg *monov1alpha1.MonoKSConfig) *Runner {
Registry: NewRegistry(nctx),
initSteps: []StepInfo{
{
- RegKey: "configure_hostname",
+ RegKey: "ConfigureHostname",
Name: "Configure hostname",
Desc: "Set system hostname according to cluster configuration",
},
{
- RegKey: "configure_mgmt_interface",
+ RegKey: "ConfigureMgmtInterface",
Name: "Configure management interface",
Desc: "Configure management network interface, IP address, and gateway",
},
{
- RegKey: "configure_dns",
+ RegKey: "ConfigureDNS",
Name: "Configure DNS",
Desc: "Set system DNS resolver configuration for cluster and external access",
},
{
- RegKey: "ensure_ip_forward",
+ RegKey: "EnsureIPForward",
Name: "Ensure IP forwarding",
Desc: "Enable kernel IP forwarding required for pod networking",
},
{
- RegKey: "configure_default_cni",
+ RegKey: "ConfigureDefaultCNI",
Name: "Configure default CNI",
Desc: "Install or configure default container networking (CNI bridge, IPAM, etc.)",
},
{
- RegKey: "start_crio",
+ RegKey: "StartCRIO",
Name: "Start CRI-O runtime",
Desc: "Start container runtime and verify it is ready for Kubernetes workloads",
},
{
- RegKey: "validate_required_images",
+ RegKey: "ValidateRequiredImagesPresent",
Name: "Validate required images",
Desc: "Ensure all required Kubernetes images are present or available locally",
},
{
- RegKey: "validate_network_requirements",
- Name: "Validate network requirements",
- Desc: "Ensure required kernel networking features (iptables/nftables, bridge, forwarding) are available",
+ RegKey: "ValidateNodeIPAndAPIServerReachability",
+ Name: "Validate Node IP and wether API Server is available",
+ Desc: "Verify the local ip address with the api server advertise address. Contact remote api server",
},
{
- RegKey: "detect_local_cluster_state",
+ RegKey: "DetectLocalClusterState",
Name: "Detect local cluster state",
Desc: "Inspect local node to determine existing Kubernetes membership and configuration",
},
{
- RegKey: "classify_bootstrap_action",
+ RegKey: "ClassifyBootstrapAction",
Name: "Classify bootstrap action",
Desc: "Decide whether to init, join, upgrade, or reconcile based on local state and desired version",
},
{
- RegKey: "wait_for_existing_cluster_if_needed",
+ RegKey: "WaitForExistingClusterIfNeeded",
Name: "Wait for existing cluster",
Desc: "Block until control plane is reachable when joining or reconciling an existing cluster",
},
{
- RegKey: "generate_kubeadm_config",
- Name: "Generate kubeadm config",
- Desc: "Render kubeadm configuration for init, join, or upgrade operations",
- },
- {
- RegKey: "apply_local_node_metadata_if_possible",
- Name: "Apply node metadata",
- Desc: "Apply labels/annotations to the local node if API server is reachable",
- },
- {
- RegKey: "allow_single_node_scheduling",
- Name: "Allow single-node scheduling",
- Desc: "Remove control-plane taints to allow workloads on single-node clusters",
- },
- {
- RegKey: "reconcile_control_plane",
+ RegKey: "ReconcileControlPlane",
Name: "Reconcile control plane",
Desc: "Ensure control plane components match desired state without full reinitialization",
},
{
- RegKey: "check_upgrade_prereqs",
+ RegKey: "CheckUpgradePrereqs",
Name: "Check upgrade prerequisites",
Desc: "Validate cluster state and version compatibility before upgrade",
},
{
- RegKey: "run_kubeadm_upgrade_apply",
+ RegKey: "RunKubeadmUpgradeApply",
Name: "Run kubeadm upgrade apply",
Desc: "Upgrade control plane components using kubeadm",
},
{
- RegKey: "run_kubeadm_init",
+ RegKey: "RunKubeadmInit",
Name: "Run kubeadm init",
Desc: "Initialize a new Kubernetes control plane using kubeadm",
},
{
- RegKey: "run_kubeadm_join",
+ RegKey: "RunKubeadmJoin",
Name: "Run kubeadm join",
Desc: "Join node to existing cluster as worker or control-plane",
},
{
- RegKey: "reconcile_node",
+ RegKey: "ReconcileNode",
Name: "Reconcile node state",
Desc: "Ensure node configuration matches desired state after join or upgrade",
},
{
- RegKey: "run_kubeadm_upgrade_node",
+ RegKey: "RunKubeadmUpgradeNode",
Name: "Run kubeadm upgrade node",
Desc: "Upgrade node components (kubelet, config) to match control plane",
},
{
- RegKey: "print_summary",
+ RegKey: "ApplyLocalNodeMetadataIfPossible",
+ Name: "Apply node metadata",
+ Desc: "Apply labels/annotations to the local node if API server is reachable",
+ },
+ {
+ RegKey: "AllowSingleNodeScheduling",
+ 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/cmd/initcmd/init.go b/clitools/pkg/cmd/initcmd/init.go
index 44539de..562105e 100644
--- a/clitools/pkg/cmd/initcmd/init.go
+++ b/clitools/pkg/cmd/initcmd/init.go
@@ -20,14 +20,27 @@ func NewCmdInit(_ *genericclioptions.ConfigFlags) *cobra.Command {
cmd := &cobra.Command{
Use: "init [list|STEPSEL]",
Short: "Start the bootstrap process for this node",
- Example: strings.TrimSpace(`
+ Long: `Run the node bootstrap process.
+
+STEPSEL allows running specific steps instead of the full sequence.
+It supports:
+
+ 3 Run step 3
+ 1-3 Run steps 1 through 3
+ -3 Run steps from start through 3
+ 3- Run steps from 3 to the end
+ 1,3,5 Run specific steps
+ 9-10,15 Combine ranges and individual steps
+`,
+ Example: `
ctl init
ctl init list
ctl init 1-3
ctl init -3
ctl init 3-
ctl init 1,3,5
-`),
+ ctl init 9-10,15
+`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
path, err := (config.Loader{}).ResolvePath(configPath)
@@ -68,6 +81,7 @@ func NewCmdInit(_ *genericclioptions.ConfigFlags) *cobra.Command {
return err
}
+ klog.InfoS("Running selected init steps", "steps", sel.Indices)
return runner.InitSelected(cmd.Context(), sel)
},
}
@@ -87,26 +101,27 @@ func parseStepSelection(raw string, max int) (bootstrap.StepSelection, error) {
selected := map[int]struct{}{}
- parts := strings.Split(raw, ",")
- for _, part := range parts {
- part = strings.TrimSpace(part)
- if part == "" {
+ for _, item := range strings.Split(raw, ",") {
+ item = strings.TrimSpace(item)
+ if item == "" {
return bootstrap.StepSelection{}, fmt.Errorf("invalid empty selector in %q", raw)
}
- if strings.Contains(part, "-") {
- if strings.Count(part, "-") != 1 {
- return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", part)
+ // Range or open-ended range
+ if strings.Contains(item, "-") {
+ if strings.Count(item, "-") != 1 {
+ return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", item)
}
- bounds := strings.SplitN(part, "-", 2)
- left := strings.TrimSpace(bounds[0])
- right := strings.TrimSpace(bounds[1])
+ parts := strings.SplitN(item, "-", 2)
+ left := strings.TrimSpace(parts[0])
+ right := strings.TrimSpace(parts[1])
var start, end int
+
switch {
case left == "" && right == "":
- return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", part)
+ return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", item)
case left == "":
n, err := parseStepNumber(right, max)
@@ -131,33 +146,33 @@ func parseStepSelection(raw string, max int) (bootstrap.StepSelection, error) {
if err != nil {
return bootstrap.StepSelection{}, err
}
+ if a > b {
+ return bootstrap.StepSelection{}, fmt.Errorf("invalid descending range %q", item)
+ }
start, end = a, b
}
- if start > end {
- return bootstrap.StepSelection{}, fmt.Errorf("invalid descending range %q", part)
- }
-
for i := start; i <= end; i++ {
selected[i] = struct{}{}
}
continue
}
- n, err := parseStepNumber(part, max)
+ // Single step
+ n, err := parseStepNumber(item, max)
if err != nil {
return bootstrap.StepSelection{}, err
}
selected[n] = struct{}{}
}
- out := make([]int, 0, len(selected))
+ indices := make([]int, 0, len(selected))
for n := range selected {
- out = append(out, n)
+ indices = append(indices, n)
}
- sort.Ints(out)
+ sort.Ints(indices)
- return bootstrap.StepSelection{Indices: out}, nil
+ return bootstrap.StepSelection{Indices: indices}, nil
}
func parseStepNumber(raw string, max int) (int, error) {
diff --git a/clitools/pkg/node/kubeadm.go b/clitools/pkg/node/kubeadm.go
index b3fda8b..f3fb5f7 100644
--- a/clitools/pkg/node/kubeadm.go
+++ b/clitools/pkg/node/kubeadm.go
@@ -1,6 +1,7 @@
package node
import (
+ "bytes"
"context"
"errors"
"fmt"
@@ -9,16 +10,20 @@ import (
"strings"
"time"
+ "gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
+
+ system "undecided.project/monok8s/pkg/system"
)
const (
adminKubeconfigPath = "/etc/kubernetes/admin.conf"
kubeletKubeconfigPath = "/etc/kubernetes/kubelet.conf"
+ tmpKubeadmInitConf = "/tmp/kubeadm-init.yaml"
)
func DetectLocalClusterState(ctx context.Context, nctx *NodeContext) error {
@@ -64,11 +69,16 @@ func DetectLocalClusterState(ctx context.Context, nctx *NodeContext) error {
}
func WaitForExistingClusterIfNeeded(ctx context.Context, nctx *NodeContext) error {
+ if nctx.LocalClusterState == nil {
+ return errors.New("LocalClusterState is nil, please run dependency step first")
+ }
switch nctx.LocalClusterState.MembershipKind {
case LocalMembershipFresh:
+ klog.V(4).Infof("Nothing to to do LocalMembershipFresh")
return nil
case LocalMembershipExistingWorker:
+ klog.V(4).Infof("Starting Kubelet in LocalMembershipExistingWorker")
if err := StartKubelet(ctx, nctx); err != nil {
return fmt.Errorf("start kubelet: %w", err)
}
@@ -79,6 +89,7 @@ func WaitForExistingClusterIfNeeded(ctx context.Context, nctx *NodeContext) erro
return fmt.Errorf("start kubelet: %w", err)
}
+ klog.V(4).Infof("Waiting for local apiserver in LocalMembershipExistingControlPlane")
// Existing local control-plane state: wait for local apiserver if this
// machine is meant to be a control-plane node.
if strings.TrimSpace(nctx.Config.Spec.ClusterRole) == "control-plane" {
@@ -92,7 +103,6 @@ func WaitForExistingClusterIfNeeded(ctx context.Context, nctx *NodeContext) erro
return nil
case LocalMembershipPartial:
- // Be strict here. Partial state is suspicious.
return fmt.Errorf("partial local cluster state detected: admin=%t kubelet=%t",
nctx.LocalClusterState.HasAdminKubeconfig,
nctx.LocalClusterState.HasKubeletKubeconfig,
@@ -543,20 +553,224 @@ func normalizeKubeVersion(v string) string {
return v
}
-func GenerateKubeadmConfig(context.Context, *NodeContext) error {
- klog.Info("generate_kubeadm_config: TODO render kubeadm v1beta4 config from MonoKSConfig")
+func GenerateKubeadmConfig(_ context.Context, nctx *NodeContext) error {
+ if nctx == nil {
+ return fmt.Errorf("node context is nil")
+ }
+
+ spec := nctx.Config.Spec
+
+ advertiseAddress := strings.TrimSpace(spec.APIServerAdvertiseAddress)
+ if advertiseAddress == "" {
+ return fmt.Errorf("api server advertise address is required")
+ }
+
+ nodeName := strings.TrimSpace(spec.NodeName)
+ if nodeName == "" {
+ return fmt.Errorf("node name is required")
+ }
+
+ criSocket := strings.TrimSpace(spec.ContainerRuntimeEndpoint)
+ if criSocket == "" {
+ return fmt.Errorf("container runtime endpoint is required")
+ }
+
+ clusterName := strings.TrimSpace(spec.ClusterName)
+ if clusterName == "" {
+ return fmt.Errorf("cluster name is required")
+ }
+
+ kubernetesVersion := strings.TrimSpace(spec.KubernetesVersion)
+ if kubernetesVersion == "" {
+ return fmt.Errorf("kubernetes version is required")
+ }
+
+ podSubnet := strings.TrimSpace(spec.PodSubnet)
+ if podSubnet == "" {
+ return fmt.Errorf("pod subnet is required")
+ }
+
+ serviceSubnet := strings.TrimSpace(spec.ServiceSubnet)
+ if serviceSubnet == "" {
+ return fmt.Errorf("service subnet is required")
+ }
+
+ clusterDomain := strings.TrimSpace(spec.ClusterDomain)
+ if clusterDomain == "" {
+ return fmt.Errorf("cluster domain is required")
+ }
+
+ certSANs := []string{advertiseAddress}
+ seen := map[string]struct{}{
+ advertiseAddress: {},
+ }
+
+ for _, raw := range spec.SubjectAltNames {
+ san := strings.TrimSpace(raw)
+ if san == "" {
+ continue
+ }
+ if _, ok := seen[san]; ok {
+ continue
+ }
+ seen[san] = struct{}{}
+ certSANs = append(certSANs, san)
+ }
+
+ type kubeadmInitConfiguration struct {
+ APIVersion string `yaml:"apiVersion"`
+ Kind string `yaml:"kind"`
+ LocalAPIEndpoint struct {
+ AdvertiseAddress string `yaml:"advertiseAddress"`
+ BindPort int `yaml:"bindPort"`
+ } `yaml:"localAPIEndpoint"`
+ NodeRegistration struct {
+ Name string `yaml:"name"`
+ CRISocket string `yaml:"criSocket"`
+ ImagePullPolicy string `yaml:"imagePullPolicy"`
+ KubeletExtraArgs []struct {
+ Name string `yaml:"name"`
+ Value string `yaml:"value"`
+ } `yaml:"kubeletExtraArgs"`
+ } `yaml:"nodeRegistration"`
+ }
+
+ type kubeadmClusterConfiguration struct {
+ APIVersion string `yaml:"apiVersion"`
+ Kind string `yaml:"kind"`
+ ClusterName string `yaml:"clusterName"`
+ KubernetesVersion string `yaml:"kubernetesVersion"`
+ Networking struct {
+ PodSubnet string `yaml:"podSubnet"`
+ ServiceSubnet string `yaml:"serviceSubnet"`
+ DNSDomain string `yaml:"dnsDomain"`
+ } `yaml:"networking"`
+ APIServer struct {
+ CertSANs []string `yaml:"certSANs"`
+ } `yaml:"apiServer"`
+ }
+
+ type kubeletConfiguration struct {
+ APIVersion string `yaml:"apiVersion"`
+ Kind string `yaml:"kind"`
+ CgroupDriver string `yaml:"cgroupDriver"`
+ ContainerRuntimeEndpoint string `yaml:"containerRuntimeEndpoint"`
+ }
+
+ initCfg := kubeadmInitConfiguration{
+ APIVersion: "kubeadm.k8s.io/v1beta4",
+ Kind: "InitConfiguration",
+ }
+ initCfg.LocalAPIEndpoint.AdvertiseAddress = advertiseAddress
+ initCfg.LocalAPIEndpoint.BindPort = 6443
+ initCfg.NodeRegistration.Name = nodeName
+ initCfg.NodeRegistration.CRISocket = criSocket
+ initCfg.NodeRegistration.ImagePullPolicy = "IfNotPresent"
+ initCfg.NodeRegistration.KubeletExtraArgs = []struct {
+ Name string `yaml:"name"`
+ Value string `yaml:"value"`
+ }{
+ {Name: "hostname-override", Value: nodeName},
+ {Name: "node-ip", Value: advertiseAddress},
+ {Name: "pod-manifest-path", Value: "/etc/kubernetes/manifests"},
+ }
+
+ clusterCfg := kubeadmClusterConfiguration{
+ APIVersion: "kubeadm.k8s.io/v1beta4",
+ Kind: "ClusterConfiguration",
+ ClusterName: clusterName,
+ KubernetesVersion: kubernetesVersion,
+ }
+ clusterCfg.Networking.PodSubnet = podSubnet
+ clusterCfg.Networking.ServiceSubnet = serviceSubnet
+ clusterCfg.Networking.DNSDomain = clusterDomain
+ clusterCfg.APIServer.CertSANs = certSANs
+
+ kubeletCfg := kubeletConfiguration{
+ APIVersion: "kubelet.config.k8s.io/v1beta1",
+ Kind: "KubeletConfiguration",
+ CgroupDriver: "cgroupfs",
+ ContainerRuntimeEndpoint: criSocket,
+ }
+
+ var docs [][]byte
+
+ for _, doc := range []any{initCfg, clusterCfg, kubeletCfg} {
+ b, err := yaml.Marshal(doc)
+ if err != nil {
+ return fmt.Errorf("marshal kubeadm config document: %w", err)
+ }
+ docs = append(docs, bytes.TrimSpace(b))
+ }
+
+ var buf bytes.Buffer
+ for i, doc := range docs {
+ if i > 0 {
+ buf.WriteString("\n---\n")
+ }
+ buf.Write(doc)
+ buf.WriteByte('\n')
+ }
+
+ rendered := buf.String()
+
+ if err := os.WriteFile(tmpKubeadmInitConf, []byte(rendered), 0o600); err != nil {
+ return fmt.Errorf("write kubeadm config to %s: %w", tmpKubeadmInitConf, err)
+ }
+
+ klog.V(4).Infof("generated kubeadm config at %s:\n%s", tmpKubeadmInitConf, rendered)
return nil
}
-func RunKubeadmInit(context.Context, *NodeContext) error {
- klog.Info("run_kubeadm_init: TODO implement kubeadm init --config <file>")
- return nil
+
+func RunKubeadmInit(ctx context.Context, nctx *NodeContext) error {
+ if err := GenerateKubeadmConfig(ctx, nctx); err != nil {
+ return err
+ }
+
+ _, err := nctx.SystemRunner.RunWithOptions(
+ ctx,
+ "kubeadm",
+ []string{"init", "--config", tmpKubeadmInitConf},
+ system.RunOptions{
+ Timeout: 10 * time.Minute,
+ OnStdoutLine: func(line string) { klog.Infof("[kubeadm] %s", line) },
+ OnStderrLine: func(line string) { klog.Infof("[kubeadm] %s", line) },
+ },
+ )
+ return err
}
+
func RunKubeadmUpgradeApply(context.Context, *NodeContext) error {
klog.Info("run_kubeadm_upgrade_apply: TODO implement kubeadm upgrade apply")
return nil
}
+
func RunKubeadmJoin(context.Context, *NodeContext) error {
- klog.Info("run_kubeadm_join: TODO implement kubeadm join")
+ /*
+ run_kubeadm_join() {
+ log "running kubeadm join..."
+
+ case "$JOIN_KIND" in
+ worker)
+ kubeadm join "${API_SERVER_ENDPOINT}" \
+ --token "${BOOTSTRAP_TOKEN}" \
+ --discovery-token-ca-cert-hash "${DISCOVERY_TOKEN_CA_CERT_HASH}" \
+ --node-name "${NODE_NAME}" \
+ --cri-socket "${CONTAINER_RUNTIME_ENDPOINT}"
+ ;;
+ control-plane)
+ kubeadm join "${API_SERVER_ENDPOINT}" \
+ --token "${BOOTSTRAP_TOKEN}" \
+ --discovery-token-ca-cert-hash "${DISCOVERY_TOKEN_CA_CERT_HASH}" \
+ --control-plane \
+ --certificate-key "${CONTROL_PLANE_CERT_KEY}" \
+ --apiserver-advertise-address "${APISERVER_ADVERTISE_ADDRESS}" \
+ --node-name "${NODE_NAME}" \
+ --cri-socket "${CONTAINER_RUNTIME_ENDPOINT}"
+ ;;
+ esac
+ }
+ */
return nil
}
func RunKubeadmUpgradeNode(context.Context, *NodeContext) error {
diff --git a/clitools/pkg/node/prereqs.go b/clitools/pkg/node/prereqs.go
index bd9b0ea..5bb4e73 100644
--- a/clitools/pkg/node/prereqs.go
+++ b/clitools/pkg/node/prereqs.go
@@ -10,7 +10,7 @@ import (
"k8s.io/klog/v2"
)
-func ValidateNetworkRequirements(ctx context.Context, nct *NodeContext) error {
+func ValidateNodeIPAndAPIServerReachability(ctx context.Context, nct *NodeContext) error {
requireLocalIP := func(wantedIP string) error {
wantedIP = strings.TrimSpace(wantedIP)
if wantedIP == "" {
diff --git a/clitools/pkg/system/runner.go b/clitools/pkg/system/runner.go
index 4d3cda4..8a4cdba 100644
--- a/clitools/pkg/system/runner.go
+++ b/clitools/pkg/system/runner.go
@@ -1,6 +1,7 @@
package system
import (
+ "bufio"
"bytes"
"context"
"errors"
@@ -54,6 +55,10 @@ type RunOptions struct {
Stderr io.Writer
Quiet bool
RedactEnv []string
+
+ // Optional line hooks. Called for each complete line seen on stdout/stderr.
+ OnStdoutLine func(line string)
+ OnStderrLine func(line string)
}
type RetryOptions struct {
@@ -86,23 +91,30 @@ func (r *Runner) RunWithOptions(ctx context.Context, name string, args []string,
var stdoutBuf bytes.Buffer
var stderrBuf bytes.Buffer
- stdoutW := io.Writer(&stdoutBuf)
- stderrW := io.Writer(&stderrBuf)
+ stdoutDst := io.Writer(&stdoutBuf)
+ stderrDst := io.Writer(&stderrBuf)
if opt.Stdout != nil {
- stdoutW = io.MultiWriter(stdoutW, opt.Stdout)
+ stdoutDst = io.MultiWriter(stdoutDst, opt.Stdout)
} else if r.cfg.StreamOutput && !opt.Quiet {
- stdoutW = io.MultiWriter(stdoutW, os.Stdout)
+ stdoutDst = io.MultiWriter(stdoutDst, os.Stdout)
}
if opt.Stderr != nil {
- stderrW = io.MultiWriter(stderrW, opt.Stderr)
+ stderrDst = io.MultiWriter(stderrDst, opt.Stderr)
} else if r.cfg.StreamOutput && !opt.Quiet {
- stderrW = io.MultiWriter(stderrW, os.Stderr)
+ stderrDst = io.MultiWriter(stderrDst, os.Stderr)
+ }
+
+ if opt.OnStdoutLine != nil {
+ stdoutDst = newLineHookWriter(stdoutDst, opt.OnStdoutLine)
+ }
+ if opt.OnStderrLine != nil {
+ stderrDst = newLineHookWriter(stderrDst, opt.OnStderrLine)
}
- cmd.Stdout = stdoutW
- cmd.Stderr = stderrW
+ cmd.Stdout = stdoutDst
+ cmd.Stderr = stderrDst
start := time.Now()
if r.cfg.Logger != nil {
@@ -110,6 +122,13 @@ func (r *Runner) RunWithOptions(ctx context.Context, name string, args []string,
}
err := cmd.Run()
+ if hw, ok := stdoutDst.(interface{ Flush() }); ok {
+ hw.Flush()
+ }
+ if hw, ok := stderrDst.(interface{ Flush() }); ok {
+ hw.Flush()
+ }
+
end := time.Now()
res := &Result{
@@ -344,3 +363,61 @@ func (l *StdLogger) Printf(format string, args ...any) {
defer l.mu.Unlock()
fmt.Fprintf(os.Stderr, format+"\n", args...)
}
+
+type lineHookWriter struct {
+ dst io.Writer
+ fn func(string)
+ mu sync.Mutex
+ buf bytes.Buffer
+}
+
+func newLineHookWriter(dst io.Writer, fn func(string)) *lineHookWriter {
+ return &lineHookWriter{
+ dst: dst,
+ fn: fn,
+ }
+}
+
+func (w *lineHookWriter) Write(p []byte) (int, error) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+
+ n, err := w.dst.Write(p)
+
+ // Keep line processing separate from dst write result.
+ w.buf.Write(p)
+ w.flushCompleteLinesLocked()
+
+ return n, err
+}
+
+func (w *lineHookWriter) flushCompleteLinesLocked() {
+ r := bufio.NewReader(&w.buf)
+ var pending bytes.Buffer
+
+ for {
+ line, err := r.ReadString('\n')
+ if err == io.EOF {
+ pending.WriteString(line)
+ break
+ }
+ line = strings.TrimRight(line, "\r\n")
+ w.fn(line)
+ }
+
+ w.buf.Reset()
+ _, _ = w.buf.Write(pending.Bytes())
+}
+
+// Flush emits the final unterminated line, if any.
+// Not strictly required for kubeadm, but useful and correct.
+func (w *lineHookWriter) Flush() {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+
+ if w.buf.Len() == 0 {
+ return
+ }
+ w.fn(strings.TrimRight(w.buf.String(), "\r\n"))
+ w.buf.Reset()
+}
diff --git a/clitools/pkg/templates/templates.go b/clitools/pkg/templates/templates.go
index 619a2e6..93af46e 100644
--- a/clitools/pkg/templates/templates.go
+++ b/clitools/pkg/templates/templates.go
@@ -1,11 +1,23 @@
package templates
import (
+ "os"
+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "undecided.project/monok8s/pkg/apis/monok8s/v1alpha1"
buildinfo "undecided.project/monok8s/pkg/buildinfo"
)
+var ValHostname string = "monoks-master-1"
+var ValBootstrapToken string = "abcd12.ef3456789abcdef0"
+var ValDiscoveryTokenCACertHash string = "sha256:9f1c2b3a4d5e6f7890abc1234567890abcdef1234567890abcdef1234567890ab"
+
+func init() {
+ ValBootstrapToken = os.Getenv("HOSTNAME")
+ ValBootstrapToken = os.Getenv("BOOTSTRAP_TOKEN")
+ ValDiscoveryTokenCACertHash = os.Getenv("TOKEN_CACERT_HASH")
+}
+
func DefaultMonoKSConfig() types.MonoKSConfig {
return types.MonoKSConfig{
TypeMeta: metav1.TypeMeta{
@@ -18,7 +30,7 @@ func DefaultMonoKSConfig() types.MonoKSConfig {
},
Spec: types.MonoKSConfigSpec{
KubernetesVersion: buildinfo.Version,
- NodeName: "monok8s-master-1",
+ NodeName: ValHostname,
ClusterRole: "control-plane",
InitControlPlane: true,
@@ -32,6 +44,10 @@ func DefaultMonoKSConfig() types.MonoKSConfig {
APIServerAdvertiseAddress: "10.0.0.10",
APIServerEndpoint: "10.0.0.10:6443",
+ // Fake token and hash for placeholder purpose
+ BootstrapToken: ValBootstrapToken,
+ DiscoveryTokenCACertHash: ValDiscoveryTokenCACertHash,
+
ContainerRuntimeEndpoint: "unix:///var/run/crio/crio.sock",
CNIPlugin: "default",
@@ -44,17 +60,17 @@ func DefaultMonoKSConfig() types.MonoKSConfig {
},
SubjectAltNames: []string{
- "10.0.0.10",
+ "10.0.0.10", "localhost", ValHostname,
},
NodeLabels: map[string]string{
- "node-role.kubernetes.io/control-plane": "",
+ "monok8s.io/label": "label",
},
NodeAnnotations: map[string]string{},
Network: types.NetworkSpec{
- Hostname: "monok8s-master-1",
+ Hostname: "monok8s-worker-1",
ManagementIface: "eth1",
ManagementCIDR: "10.0.0.10/24",
ManagementGW: "10.0.0.1",
@@ -85,7 +101,7 @@ func DefaultOSUpgrade() types.OSUpgrade {
ImageURL: "https://example.invalid/images/monok8s-v0.0.1.img.zst",
TargetPartition: "B",
NodeSelector: []string{
- "monok8s-master-1",
+ ValHostname,
},
Force: false,
},