Added kubeadm init

This commit is contained in:
2026-03-30 01:31:38 +08:00
parent 5fbc2846a1
commit 210fabdcc6
7 changed files with 419 additions and 102 deletions

View File

@@ -22,29 +22,29 @@ func NewRegistry(ctx *node.NodeContext) *Registry {
return &Registry{ return &Registry{
steps: map[string]node.Step{ steps: map[string]node.Step{
"validate_network_requirements": node.ValidateNetworkRequirements, "AllowSingleNodeScheduling": node.AllowSingleNodeScheduling,
"detect_local_cluster_state": node.DetectLocalClusterState, "ApplyLocalNodeMetadataIfPossible": node.ApplyLocalNodeMetadataIfPossible,
"classify_bootstrap_action": node.ClassifyBootstrapAction, "CheckUpgradePrereqs": node.CheckUpgradePrereqs,
"configure_default_cni": node.ConfigureDefaultCNI, "ClassifyBootstrapAction": node.ClassifyBootstrapAction,
"start_crio": node.StartCRIO, "ConfigureDNS": node.ConfigureDNS(netCfg),
"wait_for_existing_cluster_if_needed": node.WaitForExistingClusterIfNeeded, "ConfigureDefaultCNI": node.ConfigureDefaultCNI,
"validate_required_images": node.ValidateRequiredImagesPresent, "ConfigureHostname": node.ConfigureHostname(netCfg),
"generate_kubeadm_config": node.GenerateKubeadmConfig, "ConfigureMgmtInterface": node.ConfigureMgmtInterface(netCfg),
"run_kubeadm_init": node.RunKubeadmInit, "DetectLocalClusterState": node.DetectLocalClusterState,
"apply_local_node_metadata_if_possible": node.ApplyLocalNodeMetadataIfPossible, "EnsureIPForward": node.EnsureIPForward,
"allow_single_node_scheduling": node.AllowSingleNodeScheduling, "GenerateKubeadmConfig": node.GenerateKubeadmConfig,
"ensure_ip_forward": node.EnsureIPForward, "PrintSummary": node.PrintSummary,
"configure_hostname": node.ConfigureHostname(netCfg), "ReconcileControlPlane": node.ReconcileControlPlane,
"configure_mgmt_interface": node.ConfigureMgmtInterface(netCfg), "ReconcileNode": node.ReconcileNode,
"configure_dns": node.ConfigureDNS(netCfg), "RunKubeadmInit": node.RunKubeadmInit,
"set_hostname_if_needed": node.SetHostnameIfNeeded, "RunKubeadmJoin": node.RunKubeadmJoin,
"print_summary": node.PrintSummary, "RunKubeadmUpgradeApply": node.RunKubeadmUpgradeApply,
"reconcile_control_plane": node.ReconcileControlPlane, "RunKubeadmUpgradeNode": node.RunKubeadmUpgradeNode,
"check_upgrade_prereqs": node.CheckUpgradePrereqs, "SetHostnameIfNeeded": node.SetHostnameIfNeeded,
"run_kubeadm_upgrade_apply": node.RunKubeadmUpgradeApply, "StartCRIO": node.StartCRIO,
"run_kubeadm_join": node.RunKubeadmJoin, "ValidateNodeIPAndAPIServerReachability": node.ValidateNodeIPAndAPIServerReachability,
"reconcile_node": node.ReconcileNode, "ValidateRequiredImagesPresent": node.ValidateRequiredImagesPresent,
"run_kubeadm_upgrade_node": node.RunKubeadmUpgradeNode, "WaitForExistingClusterIfNeeded": node.WaitForExistingClusterIfNeeded,
}, },
} }
} }

View File

@@ -37,112 +37,107 @@ func NewRunner(cfg *monov1alpha1.MonoKSConfig) *Runner {
Registry: NewRegistry(nctx), Registry: NewRegistry(nctx),
initSteps: []StepInfo{ initSteps: []StepInfo{
{ {
RegKey: "configure_hostname", RegKey: "ConfigureHostname",
Name: "Configure hostname", Name: "Configure hostname",
Desc: "Set system hostname according to cluster configuration", Desc: "Set system hostname according to cluster configuration",
}, },
{ {
RegKey: "configure_mgmt_interface", RegKey: "ConfigureMgmtInterface",
Name: "Configure management interface", Name: "Configure management interface",
Desc: "Configure management network interface, IP address, and gateway", Desc: "Configure management network interface, IP address, and gateway",
}, },
{ {
RegKey: "configure_dns", RegKey: "ConfigureDNS",
Name: "Configure DNS", Name: "Configure DNS",
Desc: "Set system DNS resolver configuration for cluster and external access", Desc: "Set system DNS resolver configuration for cluster and external access",
}, },
{ {
RegKey: "ensure_ip_forward", RegKey: "EnsureIPForward",
Name: "Ensure IP forwarding", Name: "Ensure IP forwarding",
Desc: "Enable kernel IP forwarding required for pod networking", Desc: "Enable kernel IP forwarding required for pod networking",
}, },
{ {
RegKey: "configure_default_cni", RegKey: "ConfigureDefaultCNI",
Name: "Configure default CNI", Name: "Configure default CNI",
Desc: "Install or configure default container networking (CNI bridge, IPAM, etc.)", Desc: "Install or configure default container networking (CNI bridge, IPAM, etc.)",
}, },
{ {
RegKey: "start_crio", RegKey: "StartCRIO",
Name: "Start CRI-O runtime", Name: "Start CRI-O runtime",
Desc: "Start container runtime and verify it is ready for Kubernetes workloads", Desc: "Start container runtime and verify it is ready for Kubernetes workloads",
}, },
{ {
RegKey: "validate_required_images", RegKey: "ValidateRequiredImagesPresent",
Name: "Validate required images", Name: "Validate required images",
Desc: "Ensure all required Kubernetes images are present or available locally", Desc: "Ensure all required Kubernetes images are present or available locally",
}, },
{ {
RegKey: "validate_network_requirements", RegKey: "ValidateNodeIPAndAPIServerReachability",
Name: "Validate network requirements", Name: "Validate Node IP and wether API Server is available",
Desc: "Ensure required kernel networking features (iptables/nftables, bridge, forwarding) are 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", Name: "Detect local cluster state",
Desc: "Inspect local node to determine existing Kubernetes membership and configuration", Desc: "Inspect local node to determine existing Kubernetes membership and configuration",
}, },
{ {
RegKey: "classify_bootstrap_action", RegKey: "ClassifyBootstrapAction",
Name: "Classify bootstrap action", Name: "Classify bootstrap action",
Desc: "Decide whether to init, join, upgrade, or reconcile based on local state and desired version", 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", Name: "Wait for existing cluster",
Desc: "Block until control plane is reachable when joining or reconciling an existing cluster", Desc: "Block until control plane is reachable when joining or reconciling an existing cluster",
}, },
{ {
RegKey: "generate_kubeadm_config", RegKey: "ReconcileControlPlane",
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",
Name: "Reconcile control plane", Name: "Reconcile control plane",
Desc: "Ensure control plane components match desired state without full reinitialization", Desc: "Ensure control plane components match desired state without full reinitialization",
}, },
{ {
RegKey: "check_upgrade_prereqs", RegKey: "CheckUpgradePrereqs",
Name: "Check upgrade prerequisites", Name: "Check upgrade prerequisites",
Desc: "Validate cluster state and version compatibility before upgrade", Desc: "Validate cluster state and version compatibility before upgrade",
}, },
{ {
RegKey: "run_kubeadm_upgrade_apply", RegKey: "RunKubeadmUpgradeApply",
Name: "Run kubeadm upgrade apply", Name: "Run kubeadm upgrade apply",
Desc: "Upgrade control plane components using kubeadm", Desc: "Upgrade control plane components using kubeadm",
}, },
{ {
RegKey: "run_kubeadm_init", RegKey: "RunKubeadmInit",
Name: "Run kubeadm init", Name: "Run kubeadm init",
Desc: "Initialize a new Kubernetes control plane using kubeadm", Desc: "Initialize a new Kubernetes control plane using kubeadm",
}, },
{ {
RegKey: "run_kubeadm_join", RegKey: "RunKubeadmJoin",
Name: "Run kubeadm join", Name: "Run kubeadm join",
Desc: "Join node to existing cluster as worker or control-plane", Desc: "Join node to existing cluster as worker or control-plane",
}, },
{ {
RegKey: "reconcile_node", RegKey: "ReconcileNode",
Name: "Reconcile node state", Name: "Reconcile node state",
Desc: "Ensure node configuration matches desired state after join or upgrade", Desc: "Ensure node configuration matches desired state after join or upgrade",
}, },
{ {
RegKey: "run_kubeadm_upgrade_node", RegKey: "RunKubeadmUpgradeNode",
Name: "Run kubeadm upgrade node", Name: "Run kubeadm upgrade node",
Desc: "Upgrade node components (kubelet, config) to match control plane", 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", Name: "Print summary",
Desc: "Output final bootstrap summary and detected state", Desc: "Output final bootstrap summary and detected state",
}, },

View File

@@ -20,14 +20,27 @@ func NewCmdInit(_ *genericclioptions.ConfigFlags) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "init [list|STEPSEL]", Use: "init [list|STEPSEL]",
Short: "Start the bootstrap process for this node", 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
ctl init list ctl init list
ctl init 1-3 ctl init 1-3
ctl init -3 ctl init -3
ctl init 3- ctl init 3-
ctl init 1,3,5 ctl init 1,3,5
`), ctl init 9-10,15
`,
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
path, err := (config.Loader{}).ResolvePath(configPath) path, err := (config.Loader{}).ResolvePath(configPath)
@@ -68,6 +81,7 @@ func NewCmdInit(_ *genericclioptions.ConfigFlags) *cobra.Command {
return err return err
} }
klog.InfoS("Running selected init steps", "steps", sel.Indices)
return runner.InitSelected(cmd.Context(), sel) return runner.InitSelected(cmd.Context(), sel)
}, },
} }
@@ -87,26 +101,27 @@ func parseStepSelection(raw string, max int) (bootstrap.StepSelection, error) {
selected := map[int]struct{}{} selected := map[int]struct{}{}
parts := strings.Split(raw, ",") for _, item := range strings.Split(raw, ",") {
for _, part := range parts { item = strings.TrimSpace(item)
part = strings.TrimSpace(part) if item == "" {
if part == "" {
return bootstrap.StepSelection{}, fmt.Errorf("invalid empty selector in %q", raw) return bootstrap.StepSelection{}, fmt.Errorf("invalid empty selector in %q", raw)
} }
if strings.Contains(part, "-") { // Range or open-ended range
if strings.Count(part, "-") != 1 { if strings.Contains(item, "-") {
return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", part) if strings.Count(item, "-") != 1 {
return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", item)
} }
bounds := strings.SplitN(part, "-", 2) parts := strings.SplitN(item, "-", 2)
left := strings.TrimSpace(bounds[0]) left := strings.TrimSpace(parts[0])
right := strings.TrimSpace(bounds[1]) right := strings.TrimSpace(parts[1])
var start, end int var start, end int
switch { switch {
case left == "" && right == "": case left == "" && right == "":
return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", part) return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", item)
case left == "": case left == "":
n, err := parseStepNumber(right, max) n, err := parseStepNumber(right, max)
@@ -131,11 +146,10 @@ func parseStepSelection(raw string, max int) (bootstrap.StepSelection, error) {
if err != nil { if err != nil {
return bootstrap.StepSelection{}, err return bootstrap.StepSelection{}, err
} }
start, end = a, b 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++ { for i := start; i <= end; i++ {
@@ -144,20 +158,21 @@ func parseStepSelection(raw string, max int) (bootstrap.StepSelection, error) {
continue continue
} }
n, err := parseStepNumber(part, max) // Single step
n, err := parseStepNumber(item, max)
if err != nil { if err != nil {
return bootstrap.StepSelection{}, err return bootstrap.StepSelection{}, err
} }
selected[n] = struct{}{} selected[n] = struct{}{}
} }
out := make([]int, 0, len(selected)) indices := make([]int, 0, len(selected))
for n := range 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) { func parseStepNumber(raw string, max int) (int, error) {

View File

@@ -1,6 +1,7 @@
package node package node
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
@@ -9,16 +10,20 @@ import (
"strings" "strings"
"time" "time"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery" "k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2" "k8s.io/klog/v2"
system "undecided.project/monok8s/pkg/system"
) )
const ( const (
adminKubeconfigPath = "/etc/kubernetes/admin.conf" adminKubeconfigPath = "/etc/kubernetes/admin.conf"
kubeletKubeconfigPath = "/etc/kubernetes/kubelet.conf" kubeletKubeconfigPath = "/etc/kubernetes/kubelet.conf"
tmpKubeadmInitConf = "/tmp/kubeadm-init.yaml"
) )
func DetectLocalClusterState(ctx context.Context, nctx *NodeContext) error { 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 { 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 { switch nctx.LocalClusterState.MembershipKind {
case LocalMembershipFresh: case LocalMembershipFresh:
klog.V(4).Infof("Nothing to to do LocalMembershipFresh")
return nil return nil
case LocalMembershipExistingWorker: case LocalMembershipExistingWorker:
klog.V(4).Infof("Starting Kubelet in LocalMembershipExistingWorker")
if err := StartKubelet(ctx, nctx); err != nil { if err := StartKubelet(ctx, nctx); err != nil {
return fmt.Errorf("start kubelet: %w", err) 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) 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 // Existing local control-plane state: wait for local apiserver if this
// machine is meant to be a control-plane node. // machine is meant to be a control-plane node.
if strings.TrimSpace(nctx.Config.Spec.ClusterRole) == "control-plane" { if strings.TrimSpace(nctx.Config.Spec.ClusterRole) == "control-plane" {
@@ -92,7 +103,6 @@ func WaitForExistingClusterIfNeeded(ctx context.Context, nctx *NodeContext) erro
return nil return nil
case LocalMembershipPartial: case LocalMembershipPartial:
// Be strict here. Partial state is suspicious.
return fmt.Errorf("partial local cluster state detected: admin=%t kubelet=%t", return fmt.Errorf("partial local cluster state detected: admin=%t kubelet=%t",
nctx.LocalClusterState.HasAdminKubeconfig, nctx.LocalClusterState.HasAdminKubeconfig,
nctx.LocalClusterState.HasKubeletKubeconfig, nctx.LocalClusterState.HasKubeletKubeconfig,
@@ -543,20 +553,224 @@ func normalizeKubeVersion(v string) string {
return v return v
} }
func GenerateKubeadmConfig(context.Context, *NodeContext) error { func GenerateKubeadmConfig(_ context.Context, nctx *NodeContext) error {
klog.Info("generate_kubeadm_config: TODO render kubeadm v1beta4 config from MonoKSConfig") 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 return nil
} }
func RunKubeadmInit(context.Context, *NodeContext) error {
klog.Info("run_kubeadm_init: TODO implement kubeadm init --config <file>") func RunKubeadmInit(ctx context.Context, nctx *NodeContext) error {
return nil 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 { func RunKubeadmUpgradeApply(context.Context, *NodeContext) error {
klog.Info("run_kubeadm_upgrade_apply: TODO implement kubeadm upgrade apply") klog.Info("run_kubeadm_upgrade_apply: TODO implement kubeadm upgrade apply")
return nil return nil
} }
func RunKubeadmJoin(context.Context, *NodeContext) error { 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 return nil
} }
func RunKubeadmUpgradeNode(context.Context, *NodeContext) error { func RunKubeadmUpgradeNode(context.Context, *NodeContext) error {

View File

@@ -10,7 +10,7 @@ import (
"k8s.io/klog/v2" "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 { requireLocalIP := func(wantedIP string) error {
wantedIP = strings.TrimSpace(wantedIP) wantedIP = strings.TrimSpace(wantedIP)
if wantedIP == "" { if wantedIP == "" {

View File

@@ -1,6 +1,7 @@
package system package system
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"errors" "errors"
@@ -54,6 +55,10 @@ type RunOptions struct {
Stderr io.Writer Stderr io.Writer
Quiet bool Quiet bool
RedactEnv []string 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 { type RetryOptions struct {
@@ -86,23 +91,30 @@ func (r *Runner) RunWithOptions(ctx context.Context, name string, args []string,
var stdoutBuf bytes.Buffer var stdoutBuf bytes.Buffer
var stderrBuf bytes.Buffer var stderrBuf bytes.Buffer
stdoutW := io.Writer(&stdoutBuf) stdoutDst := io.Writer(&stdoutBuf)
stderrW := io.Writer(&stderrBuf) stderrDst := io.Writer(&stderrBuf)
if opt.Stdout != nil { if opt.Stdout != nil {
stdoutW = io.MultiWriter(stdoutW, opt.Stdout) stdoutDst = io.MultiWriter(stdoutDst, opt.Stdout)
} else if r.cfg.StreamOutput && !opt.Quiet { } else if r.cfg.StreamOutput && !opt.Quiet {
stdoutW = io.MultiWriter(stdoutW, os.Stdout) stdoutDst = io.MultiWriter(stdoutDst, os.Stdout)
} }
if opt.Stderr != nil { if opt.Stderr != nil {
stderrW = io.MultiWriter(stderrW, opt.Stderr) stderrDst = io.MultiWriter(stderrDst, opt.Stderr)
} else if r.cfg.StreamOutput && !opt.Quiet { } else if r.cfg.StreamOutput && !opt.Quiet {
stderrW = io.MultiWriter(stderrW, os.Stderr) stderrDst = io.MultiWriter(stderrDst, os.Stderr)
} }
cmd.Stdout = stdoutW if opt.OnStdoutLine != nil {
cmd.Stderr = stderrW stdoutDst = newLineHookWriter(stdoutDst, opt.OnStdoutLine)
}
if opt.OnStderrLine != nil {
stderrDst = newLineHookWriter(stderrDst, opt.OnStderrLine)
}
cmd.Stdout = stdoutDst
cmd.Stderr = stderrDst
start := time.Now() start := time.Now()
if r.cfg.Logger != nil { if r.cfg.Logger != nil {
@@ -110,6 +122,13 @@ func (r *Runner) RunWithOptions(ctx context.Context, name string, args []string,
} }
err := cmd.Run() err := cmd.Run()
if hw, ok := stdoutDst.(interface{ Flush() }); ok {
hw.Flush()
}
if hw, ok := stderrDst.(interface{ Flush() }); ok {
hw.Flush()
}
end := time.Now() end := time.Now()
res := &Result{ res := &Result{
@@ -344,3 +363,61 @@ func (l *StdLogger) Printf(format string, args ...any) {
defer l.mu.Unlock() defer l.mu.Unlock()
fmt.Fprintf(os.Stderr, format+"\n", args...) 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()
}

View File

@@ -1,11 +1,23 @@
package templates package templates
import ( import (
"os"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "undecided.project/monok8s/pkg/apis/monok8s/v1alpha1" types "undecided.project/monok8s/pkg/apis/monok8s/v1alpha1"
buildinfo "undecided.project/monok8s/pkg/buildinfo" 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 { func DefaultMonoKSConfig() types.MonoKSConfig {
return types.MonoKSConfig{ return types.MonoKSConfig{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
@@ -18,7 +30,7 @@ func DefaultMonoKSConfig() types.MonoKSConfig {
}, },
Spec: types.MonoKSConfigSpec{ Spec: types.MonoKSConfigSpec{
KubernetesVersion: buildinfo.Version, KubernetesVersion: buildinfo.Version,
NodeName: "monok8s-master-1", NodeName: ValHostname,
ClusterRole: "control-plane", ClusterRole: "control-plane",
InitControlPlane: true, InitControlPlane: true,
@@ -32,6 +44,10 @@ func DefaultMonoKSConfig() types.MonoKSConfig {
APIServerAdvertiseAddress: "10.0.0.10", APIServerAdvertiseAddress: "10.0.0.10",
APIServerEndpoint: "10.0.0.10:6443", APIServerEndpoint: "10.0.0.10:6443",
// Fake token and hash for placeholder purpose
BootstrapToken: ValBootstrapToken,
DiscoveryTokenCACertHash: ValDiscoveryTokenCACertHash,
ContainerRuntimeEndpoint: "unix:///var/run/crio/crio.sock", ContainerRuntimeEndpoint: "unix:///var/run/crio/crio.sock",
CNIPlugin: "default", CNIPlugin: "default",
@@ -44,17 +60,17 @@ func DefaultMonoKSConfig() types.MonoKSConfig {
}, },
SubjectAltNames: []string{ SubjectAltNames: []string{
"10.0.0.10", "10.0.0.10", "localhost", ValHostname,
}, },
NodeLabels: map[string]string{ NodeLabels: map[string]string{
"node-role.kubernetes.io/control-plane": "", "monok8s.io/label": "label",
}, },
NodeAnnotations: map[string]string{}, NodeAnnotations: map[string]string{},
Network: types.NetworkSpec{ Network: types.NetworkSpec{
Hostname: "monok8s-master-1", Hostname: "monok8s-worker-1",
ManagementIface: "eth1", ManagementIface: "eth1",
ManagementCIDR: "10.0.0.10/24", ManagementCIDR: "10.0.0.10/24",
ManagementGW: "10.0.0.1", ManagementGW: "10.0.0.1",
@@ -85,7 +101,7 @@ func DefaultOSUpgrade() types.OSUpgrade {
ImageURL: "https://example.invalid/images/monok8s-v0.0.1.img.zst", ImageURL: "https://example.invalid/images/monok8s-v0.0.1.img.zst",
TargetPartition: "B", TargetPartition: "B",
NodeSelector: []string{ NodeSelector: []string{
"monok8s-master-1", ValHostname,
}, },
Force: false, Force: false,
}, },