penguin/monok8s

k8s image for Mono Gateway Dev Kit

commit bf85462e34926a43e96a17317fad00b6760f2d520c713dddd42c76ae3caaee23

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-03-26T12:44:04Z
subjectFixed reboot not working
commit bf85462e34926a43e96a17317fad00b6760f2d520c713dddd42c76ae3caaee23
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-03-26T12:44:04Z

    Fixed reboot not working
---
 alpine/build-rootfs.sh                             |   1 +
 .../rootfs-extra/opt/scripts/apply-node-config.sh  |  64 ++++-
 .../rootfs-extra/opt/scripts/bootstrap-cluster.sh  | 281 +++++++++++++++++++--
 configs/node.env.default                           |   7 +
 4 files changed, 327 insertions(+), 26 deletions(-)

diff --git a/alpine/build-rootfs.sh b/alpine/build-rootfs.sh
index 691051a..a50ad6b 100755
--- a/alpine/build-rootfs.sh
+++ b/alpine/build-rootfs.sh
@@ -47,6 +47,7 @@ umount "$ROOTFS/sys"
 umount "$ROOTFS/run"
 
 rm -r "$ROOTFS/build"
+rm "$ROOTFS/etc/resolv.conf"
 
 ### Begin making full disk image for the device
 echo "##################################################### Packaging RootFS "$( du -sh "$ROOTFS/" )
diff --git a/alpine/rootfs-extra/opt/scripts/apply-node-config.sh b/alpine/rootfs-extra/opt/scripts/apply-node-config.sh
index 74391e0..6de0254 100755
--- a/alpine/rootfs-extra/opt/scripts/apply-node-config.sh
+++ b/alpine/rootfs-extra/opt/scripts/apply-node-config.sh
@@ -31,6 +31,8 @@ load_config() {
     MGMT_IFACE="${MGMT_IFACE:-}"
     MGMT_ADDRESS="${MGMT_ADDRESS:-}"
     MGMT_GATEWAY="${MGMT_GATEWAY:-}"
+    DNS_NAMESERVERS="${DNS_NAMESERVERS:-}"
+    DNS_SEARCH_DOMAINS="${DNS_SEARCH_DOMAINS:-}"
 }
 
 validate_config() {
@@ -45,17 +47,23 @@ validate_config() {
             fail "MGMT_ADDRESS must include a CIDR prefix, example: 10.0.0.13/24"
             ;;
     esac
+
+    if [ -n "$DNS_NAMESERVERS" ]; then
+        for ns in $DNS_NAMESERVERS; do
+            case "$ns" in
+                10.96.0.10)
+                    fail "DNS_NAMESERVERS must not contain cluster DNS service IP (10.96.0.10)"
+                    ;;
+            esac
+        done
+    fi
 }
 
 check_prereqs() {
+	# only the special one, coreutils should not be checked
     need_cmd ip
     need_cmd hostname
     need_cmd grep
-    need_cmd awk
-    need_cmd cut
-    need_cmd mkdir
-    need_cmd printf
-    need_cmd cat
 }
 
 configure_mgmt_interface() {
@@ -123,16 +131,59 @@ net.ipv4.ip_forward = 1
 EOF
 }
 
+configure_dns() {
+    local tmpfile
+    local ns_count=0
+
+    if [ -z "$DNS_NAMESERVERS" ]; then
+        log "DNS_NAMESERVERS not set; leaving /etc/resolv.conf unchanged"
+        return
+    fi
+
+    mkdir -p /etc
+    tmpfile="/etc/resolv.conf.monok8s.tmp"
+    : > "$tmpfile"
+
+    if [ -n "$DNS_SEARCH_DOMAINS" ]; then
+        printf 'search %s\n' "$DNS_SEARCH_DOMAINS" >> "$tmpfile"
+    fi
+
+    for ns in $DNS_NAMESERVERS; do
+        printf 'nameserver %s\n' "$ns" >> "$tmpfile"
+        ns_count=$((ns_count + 1))
+    done
+
+    [ "$ns_count" -gt 0 ] || fail "DNS_NAMESERVERS is set but no valid nameservers were parsed"
+
+    printf 'options timeout:2 attempts:3\n' >> "$tmpfile"
+
+    mv "$tmpfile" /etc/resolv.conf
+    log "configured /etc/resolv.conf from DNS_NAMESERVERS"
+}
+
 print_summary() {
     log "node configuration applied"
     log "hostname: $HOSTNAME"
     log "interface: $MGMT_IFACE"
     log "address: $MGMT_ADDRESS"
+
     if [ -n "${MGMT_GATEWAY:-}" ]; then
         log "gateway: $MGMT_GATEWAY"
     else
         log "gateway: <not set>"
     fi
+
+    if [ -n "${DNS_NAMESERVERS:-}" ]; then
+        log "dns nameservers: $DNS_NAMESERVERS"
+    else
+        log "dns nameservers: <unchanged>"
+    fi
+
+    if [ -n "${DNS_SEARCH_DOMAINS:-}" ]; then
+        log "dns search: $DNS_SEARCH_DOMAINS"
+    else
+        log "dns search: <not set>"
+    fi
 }
 
 main() {
@@ -142,8 +193,9 @@ main() {
 
     ensure_ip_forward
     configure_mgmt_interface
+    configure_dns
     set_hostname_if_needed
     print_summary
 }
 
-main "$@"
\ No newline at end of file
+main "$@"
diff --git a/alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh b/alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh
index 2e12a44..4477731 100755
--- a/alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh
+++ b/alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh
@@ -4,6 +4,8 @@ set -eu
 CONFIG_DIR="${CONFIG_DIR:-/opt/monok8s/config}"
 CLUSTER_ENV="${CONFIG_DIR}/cluster.env"
 KUBEADM_CONFIG_OUT="${KUBEADM_CONFIG_OUT:-/tmp/kubeadm-init.yaml}"
+ADMIN_KUBECONFIG="/etc/kubernetes/admin.conf"
+KUBELET_KUBECONFIG="/etc/kubernetes/kubelet.conf"
 
 log() {
     echo "[monok8s] $*"
@@ -40,7 +42,7 @@ load_config() {
     SANS="${SANS:-}"
     ALLOW_SCHEDULING_ON_CONTROL_PLANE="${ALLOW_SCHEDULING_ON_CONTROL_PLANE:-yes}"
     SKIP_IMAGE_CHECK="${SKIP_IMAGE_CHECK:-no}"
-    KUBECONFIG_USER_HOME="${KUBECONFIG_USER_HOME:-/root}"
+    KUBE_PROXY_NODEPORT_ADDRESSES="${KUBE_PROXY_NODEPORT_ADDRESSES:-primary}"
 
     BOOTSTRAP_MODE="${BOOTSTRAP_MODE:-init}"
     JOIN_KIND="${JOIN_KIND:-worker}"
@@ -51,6 +53,162 @@ load_config() {
     CNI_PLUGIN="${CNI_PLUGIN:-none}"
 }
 
+kubectl_admin() {
+    kubectl --kubeconfig "$ADMIN_KUBECONFIG" "$@"
+}
+
+kubectl_kubelet() {
+    kubectl --kubeconfig "$KUBELET_KUBECONFIG" "$@"
+}
+
+start_kubelet() {
+    log "starting kubelet..."
+    rc-service kubelet start >/dev/null 2>&1 || true
+}
+
+restart_kubelet() {
+    log "restarting kubelet..."
+    rc-service kubelet restart
+}
+
+check_kubelet_running() {
+    log "waiting for kubelet to become ready..."
+
+    last_status="unknown"
+
+    for _ in $(seq 1 30); do
+        if rc-service kubelet status >/dev/null 2>&1; then
+            log "kubelet is up"
+            return 0
+        fi
+        last_status="service-not-running"
+        sleep 1
+    done
+
+    fail "kubelet did not become ready in time (${last_status})"
+}
+
+is_local_control_plane_node() {
+    [ -f /etc/kubernetes/manifests/kube-apiserver.yaml ]
+}
+
+wait_for_local_apiserver() {
+    need_cmd nc
+
+    log "waiting for local API server on ${APISERVER_ADVERTISE_ADDRESS}:6443..."
+
+    for _ in $(seq 1 90); do
+        if nc -z "${APISERVER_ADVERTISE_ADDRESS}" 6443 >/dev/null 2>&1; then
+            log "local API server TCP port is reachable"
+            return 0
+        fi
+        sleep 2
+    done
+
+    fail "local API server did not become reachable on ${APISERVER_ADVERTISE_ADDRESS}:6443"
+}
+
+wait_for_admin_api() {
+    [ -f "$ADMIN_KUBECONFIG" ] || fail "missing admin kubeconfig: $ADMIN_KUBECONFIG"
+
+    log "waiting for Kubernetes API to respond via admin.conf..."
+
+    for _ in $(seq 1 90); do
+        if kubectl_admin version -o yaml >/dev/null 2>&1; then
+            log "Kubernetes API is responding"
+            return 0
+        fi
+        sleep 2
+    done
+
+    fail "Kubernetes API did not become ready in time"
+}
+
+wait_for_existing_cluster_if_needed() {
+    case "$BOOTSTRAP_MODE" in
+        init)
+            if [ -f "$ADMIN_KUBECONFIG" ]; then
+                start_kubelet
+                check_kubelet_running
+
+                if is_local_control_plane_node; then
+                    wait_for_local_apiserver
+                fi
+
+                wait_for_admin_api
+            fi
+            ;;
+        join)
+            if [ -f "$KUBELET_KUBECONFIG" ]; then
+                start_kubelet
+                check_kubelet_running
+            fi
+            ;;
+    esac
+}
+
+get_cluster_server_version() {
+    kubectl_admin version -o yaml 2>/dev/null \
+        | awk '
+            $1 == "serverVersion:" { in_server=1; next }
+            in_server && $1 == "gitVersion:" { print $2; exit }
+        '
+}
+
+get_api_server_version_from_kubelet_kubeconfig() {
+    kubectl_kubelet version -o yaml 2>/dev/null \
+        | awk '
+            $1 == "serverVersion:" { in_server=1; next }
+            in_server && $1 == "gitVersion:" { print $2; exit }
+        '
+}
+
+validate_cidr_list_or_primary() {
+    value="$1"
+
+    [ -n "$value" ] || return 0
+
+    if [ "$value" = "primary" ]; then
+        return 0
+    fi
+
+    old_ifs="$IFS"
+    IFS=','
+    for item in $value; do
+        trimmed="$(printf '%s' "$item" | sed 's/^ *//;s/ *$//')"
+        [ -n "$trimmed" ] || fail "KUBE_PROXY_NODEPORT_ADDRESSES contains an empty entry"
+
+        case "$trimmed" in
+            */*)
+                ;;
+            *)
+                fail "KUBE_PROXY_NODEPORT_ADDRESSES must be 'primary' or a comma-separated list of CIDRs"
+                ;;
+        esac
+
+        ip_part="${trimmed%/*}"
+        prefix_part="${trimmed#*/}"
+
+        printf '%s' "$prefix_part" | grep -Eq '^[0-9]+$' \
+            || fail "invalid CIDR prefix in KUBE_PROXY_NODEPORT_ADDRESSES: $trimmed"
+
+        case "$ip_part" in
+            *:*)
+                printf '%s' "$prefix_part" | awk '{ exit !($1 >= 0 && $1 <= 128) }' \
+                    || fail "invalid IPv6 CIDR prefix in KUBE_PROXY_NODEPORT_ADDRESSES: $trimmed"
+                ;;
+            *.*.*.*)
+                printf '%s' "$prefix_part" | awk '{ exit !($1 >= 0 && $1 <= 32) }' \
+                    || fail "invalid IPv4 CIDR prefix in KUBE_PROXY_NODEPORT_ADDRESSES: $trimmed"
+                ;;
+            *)
+                fail "invalid CIDR entry in KUBE_PROXY_NODEPORT_ADDRESSES: $trimmed"
+                ;;
+        esac
+    done
+    IFS="$old_ifs"
+}
+
 validate_config() {
     case "$BOOTSTRAP_MODE" in
         init)
@@ -76,6 +234,8 @@ validate_config() {
             fail "BOOTSTRAP_MODE must be 'init' or 'join'"
             ;;
     esac
+
+    validate_cidr_list_or_primary "$KUBE_PROXY_NODEPORT_ADDRESSES"
 }
 
 normalize_version() {
@@ -141,19 +301,43 @@ validate_target_matches_local_binaries() {
 }
 
 decide_bootstrap_action() {
+    current_version=""
+
     case "$BOOTSTRAP_MODE" in
         init)
-            if [ -f /etc/kubernetes/admin.conf ]; then
-                BOOTSTRAP_ACTION="upgrade-control-plane"
-            else
+            if [ ! -f "$ADMIN_KUBECONFIG" ]; then
                 BOOTSTRAP_ACTION="init"
+                log "selected bootstrap action: $BOOTSTRAP_ACTION"
+                return 0
+            fi
+
+            current_version="$(get_cluster_server_version || true)"
+            [ -n "$current_version" ] || fail "existing control-plane config found, but cluster version could not be determined"
+
+            log "detected existing control-plane version: $current_version"
+
+            if version_eq "$current_version" "$KUBERNETES_VERSION"; then
+                BOOTSTRAP_ACTION="reconcile-control-plane"
+            else
+                BOOTSTRAP_ACTION="upgrade-control-plane"
             fi
             ;;
         join)
-            if [ -f /etc/kubernetes/kubelet.conf ]; then
-                BOOTSTRAP_ACTION="upgrade-node"
-            else
+            if [ ! -f "$KUBELET_KUBECONFIG" ]; then
                 BOOTSTRAP_ACTION="join"
+                log "selected bootstrap action: $BOOTSTRAP_ACTION"
+                return 0
+            fi
+
+            current_version="$(get_api_server_version_from_kubelet_kubeconfig || true)"
+            [ -n "$current_version" ] || fail "existing kubelet config found, but cluster version could not be determined"
+
+            log "detected cluster version visible from this node: $current_version"
+
+            if version_eq "$current_version" "$KUBERNETES_VERSION"; then
+                BOOTSTRAP_ACTION="reconcile-node"
+            else
+                BOOTSTRAP_ACTION="upgrade-node"
             fi
             ;;
         *)
@@ -164,6 +348,25 @@ decide_bootstrap_action() {
     log "selected bootstrap action: $BOOTSTRAP_ACTION"
 }
 
+reconcile_control_plane() {
+    log "reconciling existing control-plane node"
+
+    start_kubelet
+    check_kubelet_running
+    wait_for_local_apiserver
+    wait_for_admin_api
+
+    apply_local_node_metadata_if_possible
+    allow_single_node_scheduling
+}
+
+reconcile_node() {
+    log "reconciling existing joined node"
+
+    start_kubelet
+    check_kubelet_running
+}
+
 validate_upgrade_path() {
     current="$1"
     target="$2"
@@ -192,7 +395,9 @@ check_upgrade_prereqs() {
 }
 
 run_kubeadm_upgrade_apply() {
-    current_version="$(get_cluster_server_version)"
+    current_version="$(get_cluster_server_version || true)"
+    [ -n "$current_version" ] || fail "cannot determine current control-plane version; API server is not reachable"
+
     log "current control-plane version: $current_version"
     log "target control-plane version: $KUBERNETES_VERSION"
 
@@ -230,7 +435,7 @@ check_prereqs() {
     need_cmd crictl
     need_cmd rc-service
     need_cmd awk
-	need_cmd ip
+    need_cmd ip
     need_cmd grep
     need_cmd sed
     need_cmd hostname
@@ -256,7 +461,7 @@ check_apiserver_reachable() {
 }
 
 start_crio() {
-	rc-service crio start
+    rc-service crio start
 }
 
 check_crio_running() {
@@ -355,6 +560,36 @@ run_kubeadm_join() {
     esac
 }
 
+generate_kube_proxy_config_block() {
+    if [ -z "${KUBE_PROXY_NODEPORT_ADDRESSES:-}" ]; then
+        return 0
+    fi
+
+    if [ "$KUBE_PROXY_NODEPORT_ADDRESSES" = "primary" ]; then
+        cat <<EOF
+---
+apiVersion: kubeproxy.config.k8s.io/v1alpha1
+kind: KubeProxyConfiguration
+nodePortAddresses:
+  - primary
+EOF
+        return 0
+    fi
+
+    echo "---"
+    echo "apiVersion: kubeproxy.config.k8s.io/v1alpha1"
+    echo "kind: KubeProxyConfiguration"
+    echo "nodePortAddresses:"
+
+    old_ifs="$IFS"
+    IFS=','
+    for item in $KUBE_PROXY_NODEPORT_ADDRESSES; do
+        trimmed="$(printf '%s' "$item" | sed 's/^ *//;s/ *$//')"
+        [ -n "$trimmed" ] && printf '  - "%s"\n' "$trimmed"
+    done
+    IFS="$old_ifs"
+}
+
 generate_kubeadm_config() {
     log "generating kubeadm config at $KUBEADM_CONFIG_OUT..."
 
@@ -405,6 +640,8 @@ kind: KubeletConfiguration
 cgroupDriver: cgroupfs
 containerRuntimeEndpoint: ${CONTAINER_RUNTIME_ENDPOINT}
 EOF
+
+    generate_kube_proxy_config_block >> "$KUBEADM_CONFIG_OUT"
 }
 
 run_kubeadm_init() {
@@ -503,15 +740,10 @@ print_next_steps() {
     case "$BOOTSTRAP_MODE" in
         init)
             cat <<EOF
-Try these now:
-
-  export KUBECONFIG=/root/.kube/config
-  kubectl get nodes -o wide
-  kubectl describe nodes
-
 Notes:
 - On a fresh cluster without a CNI, nodes may stay NotReady.
 - If you want pods to run on this same node, keep ALLOW_SCHEDULING_ON_CONTROL_PLANE=yes.
+- kube-proxy nodePortAddresses is set to: ${KUBE_PROXY_NODEPORT_ADDRESSES:-<unset>}
 EOF
             ;;
         join)
@@ -537,36 +769,45 @@ main() {
 
     check_prereqs
     validate_network_requirements
-    decide_bootstrap_action
 
     install_cni_if_requested
     start_crio
     check_crio_running
 
+    wait_for_existing_cluster_if_needed
+    decide_bootstrap_action
+
     case "$BOOTSTRAP_ACTION" in
         init)
             check_required_images
             generate_kubeadm_config
             run_kubeadm_init
-            rc-service kubelet restart
+            restart_kubelet
             apply_local_node_metadata_if_possible
             allow_single_node_scheduling
             ;;
+        reconcile-control-plane)
+            reconcile_control_plane
+            ;;
         upgrade-control-plane)
             check_upgrade_prereqs
             check_required_images
+            generate_kubeadm_config
             run_kubeadm_upgrade_apply
-            rc-service kubelet restart
+            restart_kubelet
             apply_local_node_metadata_if_possible
             allow_single_node_scheduling
             ;;
         join)
             run_kubeadm_join
             ;;
+        reconcile-node)
+            reconcile_node
+            ;;
         upgrade-node)
             check_upgrade_prereqs
             run_kubeadm_upgrade_node
-            rc-service kubelet restart
+            restart_kubelet
             ;;
         *)
             fail "unsupported BOOTSTRAP_ACTION: $BOOTSTRAP_ACTION"
diff --git a/configs/node.env.default b/configs/node.env.default
index c7ab0f8..3fec91b 100644
--- a/configs/node.env.default
+++ b/configs/node.env.default
@@ -5,3 +5,10 @@ HOSTNAME=monok8s-master
 MGMT_IFACE=eth0
 MGMT_ADDRESS=10.0.0.13/24
 MGMT_GATEWAY=10.0.0.1
+
+# Space-separated real upstream DNS servers.
+# Do NOT put the cluster DNS Service IP here.
+DNS_NAMESERVERS="10.0.0.1 1.1.1.1 1.0.0.1"
+
+# Optional
+DNS_SEARCH_DOMAINS="lan"