Can now reconcile
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
if len(spec.NodeAnnotations) == 0 && len(spec.NodeLabels) == 0 {
|
||||
return nil // nothing to do
|
||||
}
|
||||
|
||||
nodeName := strings.TrimSpace(spec.NodeName)
|
||||
if nodeName == "" {
|
||||
return fmt.Errorf("spec.nodeName is required")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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 AllowSingleNodeScheduling(context.Context, *NodeContext) error {
|
||||
klog.Info("allow_single_node_scheduling: TODO implement control-plane taint removal")
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetHostnameIfNeeded(context.Context, *NodeContext) error {
|
||||
klog.Info("set_hostname_if_needed: TODO implement hostname and /etc/hostname reconciliation")
|
||||
return nil
|
||||
}
|
||||
|
||||
func PrintSummary(context.Context, *NodeContext) error {
|
||||
klog.Info("print_summary: TODO emit final summary")
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReconcileControlPlane(context.Context, *NodeContext) error {
|
||||
klog.Info("reconcile_control_plane: TODO implement existing CP reconciliation")
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReconcileNode(context.Context, *NodeContext) error {
|
||||
klog.Info("reconcile_node: TODO implement existing joined node reconciliation")
|
||||
return nil
|
||||
klog.Info("allow_single_node_scheduling: TODO implement control-plane taint removal")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user