From 7016ae1ba9ff07cccdda33c9253544c5ef2035bf Mon Sep 17 00:00:00 2001 From: RomanNum3ral Date: Fri, 27 Mar 2026 08:28:19 +0000 Subject: [PATCH] Update master_node_install.sh --- master_node_install.sh | 260 ++++++++++++++++++++++++++--------------- 1 file changed, 168 insertions(+), 92 deletions(-) diff --git a/master_node_install.sh b/master_node_install.sh index 9fafd0f..74c7465 100644 --- a/master_node_install.sh +++ b/master_node_install.sh @@ -4,6 +4,7 @@ set -Eeuo pipefail ######################################## # Arch Linux Kubernetes Control Plane # Fully automated master node installer +# + Official Kubernetes binaries pinned to 1.34.x # + Helm # + ingress-nginx # + cert-manager @@ -14,26 +15,24 @@ set -Eeuo pipefail POD_CIDR="${POD_CIDR:-192.168.0.0/16}" CALICO_VERSION="${CALICO_VERSION:-v3.31.4}" +# Rancher-compatible Kubernetes version +K8S_VERSION="${K8S_VERSION:-v1.34.6}" +K8S_SERIES_REGEX='^v1\.34\.[0-9]+$' +K8S_ARCH="${K8S_ARCH:-amd64}" + KUBECONFIG_DIR_ROOT="/root/.kube" JOIN_COMMAND_FILE="/root/kubeadm-join-command.sh" INSTALL_HELM="${INSTALL_HELM:-true}" INSTALL_RANCHER="${INSTALL_RANCHER:-true}" -# Rancher currently rejects Kubernetes 1.35.x in your setup. -# This guard prevents the script from failing deep into the Rancher install step. -RANCHER_MAX_SUPPORTED_K8S_MINOR="${RANCHER_MAX_SUPPORTED_K8S_MINOR:-34}" - -# Single-node/lab convenience: -# Rancher, ingress-nginx, cert-manager, CoreDNS, etc. need schedulable capacity. -# On a single control-plane node, removing the control-plane taint is the simplest way. ALLOW_WORKLOADS_ON_CONTROL_PLANE="${ALLOW_WORKLOADS_ON_CONTROL_PLANE:-true}" # Rancher settings -RANCHER_REPO_CHANNEL="${RANCHER_REPO_CHANNEL:-stable}" # stable | latest | alpha +RANCHER_REPO_CHANNEL="${RANCHER_REPO_CHANNEL:-stable}" # stable | latest | alpha RANCHER_BOOTSTRAP_PASSWORD="${RANCHER_BOOTSTRAP_PASSWORD:-}" -RANCHER_HOSTNAME="${RANCHER_HOSTNAME:-}" # if empty, auto-generate rancher..sslip.io -RANCHER_REPLICAS="${RANCHER_REPLICAS:-1}" # 1 for single-node lab installs +RANCHER_HOSTNAME="${RANCHER_HOSTNAME:-}" # auto -> rancher..sslip.io +RANCHER_REPLICAS="${RANCHER_REPLICAS:-1}" RANCHER_NAMESPACE="${RANCHER_NAMESPACE:-cattle-system}" # ingress-nginx settings @@ -43,6 +42,11 @@ INGRESS_CLASS_NAME="${INGRESS_CLASS_NAME:-nginx}" # cert-manager settings CERT_MANAGER_NAMESPACE="${CERT_MANAGER_NAMESPACE:-cert-manager}" +# Binary locations +KUBEADM_BIN="/usr/local/bin/kubeadm" +KUBECTL_BIN="/usr/local/bin/kubectl" +KUBELET_BIN="/usr/local/bin/kubelet" + # Detect the real invoking user when run with sudo REAL_USER="${SUDO_USER:-root}" REAL_HOME="$(getent passwd "$REAL_USER" | cut -d: -f6 || true)" @@ -101,31 +105,74 @@ helm_repo_add_force() { kubectl_ns_apply() { local ns="$1" - kubectl create namespace "${ns}" --dry-run=client -o yaml | kubectl apply -f - + "${KUBECTL_BIN}" create namespace "${ns}" --dry-run=client -o yaml | "${KUBECTL_BIN}" apply -f - } -k8s_major_minor() { - kubeadm version -o short 2>/dev/null | sed -E 's/^v([0-9]+)\.([0-9]+).*/\1 \2/' +download_k8s_binary() { + local name="$1" + local tmpdir + tmpdir="$(mktemp -d)" + + curl -fsSL -o "${tmpdir}/${name}" \ + "https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/${K8S_ARCH}/${name}" + + curl -fsSL -o "${tmpdir}/${name}.sha256" \ + "https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/${K8S_ARCH}/${name}.sha256" + + ( + cd "${tmpdir}" + echo "$(cat "${name}.sha256") ${name}" | sha256sum --check --status + ) || die "Checksum verification failed for ${name} ${K8S_VERSION}" + + install -o root -g root -m 0755 "${tmpdir}/${name}" "/usr/local/bin/${name}" + rm -rf "${tmpdir}" } -is_rancher_k8s_supported() { - local version_parts - version_parts="$(k8s_major_minor)" - local major minor - major="$(awk '{print $1}' <<<"${version_parts}")" - minor="$(awk '{print $2}' <<<"${version_parts}")" +install_kubelet_service() { + log "Installing kubelet systemd service" - [[ -n "${major}" && -n "${minor}" ]] || return 1 + mkdir -p /etc/systemd/system/kubelet.service.d + touch /etc/default/kubelet - if (( major > 1 )); then - return 1 + cat >/etc/systemd/system/kubelet.service <<'EOF' +[Unit] +Description=kubelet: The Kubernetes Node Agent +Documentation=https://kubernetes.io/docs/ +After=containerd.service network-online.target +Wants=network-online.target +Requires=containerd.service + +[Service] +ExecStart=/usr/local/bin/kubelet +Restart=always +StartLimitInterval=0 +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF + + cat >/etc/systemd/system/kubelet.service.d/10-kubeadm.conf <<'EOF' +[Service] +Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" +Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" +EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env +EnvironmentFile=-/etc/default/kubelet +ExecStart= +ExecStart=/usr/local/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS +EOF +} + +existing_cluster_version() { + if [[ -f /etc/kubernetes/admin.conf ]]; then + "${KUBECTL_BIN}" --kubeconfig=/etc/kubernetes/admin.conf version -o json 2>/dev/null | \ + jq -r '.serverVersion.gitVersion // empty' fi +} - if (( minor > RANCHER_MAX_SUPPORTED_K8S_MINOR )); then - return 1 - fi - - return 0 +ensure_rancher_supported_k8s() { + [[ "${K8S_VERSION}" =~ ${K8S_SERIES_REGEX} ]] || die \ + "Rancher is enabled, but K8S_VERSION=${K8S_VERSION} is not a 1.34.x release. Set K8S_VERSION to a supported 1.34.x patch release." } # ---------- Root check ---------- @@ -140,12 +187,16 @@ on_error() { warn "Useful diagnostics:" echo " journalctl -u containerd -u kubelet -b --no-pager | tail -n 200" echo " systemctl status containerd kubelet --no-pager" - echo " kubectl get nodes -o wide" - echo " kubectl get pods -A" + echo " ${KUBECTL_BIN} get nodes -o wide" + echo " ${KUBECTL_BIN} get pods -A" exit "${exit_code}" } trap 'on_error $LINENO' ERR +if [[ "${INSTALL_RANCHER}" == "true" ]]; then + ensure_rancher_supported_k8s +fi + # ---------- Step 1: Disable swap ---------- log "Disabling swap immediately" swapoff -a || true @@ -167,8 +218,8 @@ if pacman -Q iptables >/dev/null 2>&1; then pacman -Rdd --noconfirm iptables || true fi -# ---------- Step 4: Install required packages ---------- -log "Installing Kubernetes and runtime packages" +# ---------- Step 4: Install required Arch packages ---------- +log "Installing runtime and support packages from Arch" pacman -S --needed --noconfirm \ ca-certificates \ curl \ @@ -179,16 +230,31 @@ pacman -S --needed --noconfirm \ iptables-nft \ conntrack-tools \ socat \ - kubeadm \ - kubectl \ - kubelet \ tar \ gzip \ jq \ openssl \ helm -# ---------- Step 5: Kernel modules ---------- +# ---------- Step 5: Remove Arch Kubernetes packages if present ---------- +log "Removing Arch-provided kubeadm/kubectl/kubelet if present" +for pkg in kubeadm kubectl kubelet; do + if pacman -Q "${pkg}" >/dev/null 2>&1; then + pacman -Rdd --noconfirm "${pkg}" || true + fi +done + +# ---------- Step 6: Install pinned Kubernetes binaries ---------- +log "Installing Kubernetes binaries ${K8S_VERSION}" +download_k8s_binary kubeadm +download_k8s_binary kubectl +download_k8s_binary kubelet + +require_cmd "${KUBEADM_BIN}" +require_cmd "${KUBECTL_BIN}" +require_cmd "${KUBELET_BIN}" + +# ---------- Step 7: Kernel modules ---------- log "Configuring required kernel modules" cat >/etc/modules-load.d/k8s.conf <<'EOF' overlay @@ -198,7 +264,7 @@ EOF modprobe overlay modprobe br_netfilter -# ---------- Step 6: Sysctl ---------- +# ---------- Step 8: Sysctl ---------- log "Configuring Kubernetes sysctl settings" cat >/etc/sysctl.d/99-kubernetes-cri.conf <<'EOF' net.bridge.bridge-nf-call-iptables = 1 @@ -208,7 +274,7 @@ EOF sysctl --system -# ---------- Step 7: containerd config ---------- +# ---------- Step 9: containerd config ---------- log "Configuring containerd" mkdir -p /etc/containerd @@ -218,16 +284,18 @@ else cp /etc/containerd/config.toml /etc/containerd/config.toml.bak.$(date +%Y%m%d%H%M%S) fi -# Ensure SystemdCgroup = true sed -ri 's/^\s*SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml -# ---------- Step 8: Enable services ---------- +# ---------- Step 10: kubelet service ---------- +install_kubelet_service + +# ---------- Step 11: Enable services ---------- log "Enabling and starting containerd and kubelet" systemctl daemon-reload systemctl enable --now containerd systemctl enable --now kubelet -# ---------- Step 9: Wait for containerd ---------- +# ---------- Step 12: Wait for containerd ---------- log "Waiting for containerd to become active" for i in {1..20}; do if systemctl is-active --quiet containerd; then @@ -237,19 +305,30 @@ for i in {1..20}; do done systemctl is-active --quiet containerd || die "containerd did not start successfully" -# ---------- Step 10: Pre-pull Kubernetes images ---------- -log "Pulling Kubernetes control-plane images" -kubeadm config images pull +# ---------- Step 13: Handle existing cluster ---------- +EXISTING_CLUSTER_VERSION="$(existing_cluster_version || true)" +if [[ -n "${EXISTING_CLUSTER_VERSION}" ]]; then + log "Detected existing Kubernetes cluster: ${EXISTING_CLUSTER_VERSION}" + if [[ "${EXISTING_CLUSTER_VERSION}" != "${K8S_VERSION}" ]]; then + die "Existing cluster version is ${EXISTING_CLUSTER_VERSION}, but this script is pinned to ${K8S_VERSION}. Reset/rebuild the cluster before rerunning." + fi +fi -# ---------- Step 11: Initialize cluster ---------- +# ---------- Step 14: Pre-pull Kubernetes images ---------- +log "Pulling Kubernetes control-plane images" +"${KUBEADM_BIN}" config images pull --kubernetes-version="${K8S_VERSION}" + +# ---------- Step 15: Initialize cluster ---------- if [[ -f /etc/kubernetes/admin.conf ]]; then warn "/etc/kubernetes/admin.conf already exists; skipping kubeadm init" else log "Initializing Kubernetes control plane" - kubeadm init --pod-network-cidr="${POD_CIDR}" + "${KUBEADM_BIN}" init \ + --kubernetes-version="${K8S_VERSION}" \ + --pod-network-cidr="${POD_CIDR}" fi -# ---------- Step 12: Configure kubectl for root ---------- +# ---------- Step 16: Configure kubectl for root ---------- log "Configuring kubectl for root" mkdir -p "${KUBECONFIG_DIR_ROOT}" cp -f /etc/kubernetes/admin.conf "${KUBECONFIG_DIR_ROOT}/config" @@ -257,7 +336,7 @@ chmod 600 "${KUBECONFIG_DIR_ROOT}/config" export KUBECONFIG=/etc/kubernetes/admin.conf -# ---------- Step 13: Configure kubectl for invoking user ---------- +# ---------- Step 17: Configure kubectl for invoking user ---------- if [[ -n "${REAL_HOME}" && -d "${REAL_HOME}" ]]; then log "Configuring kubectl for user ${REAL_USER}" mkdir -p "${REAL_KUBECONFIG_DIR}" @@ -268,43 +347,45 @@ else warn "Could not determine invoking user's home directory; skipping user kubeconfig setup" fi -require_cmd kubectl -require_cmd kubeadm -require_cmd helm +# ---------- Step 18: Verify cluster version ---------- +log "Verifying Kubernetes server version" +SERVER_VERSION="$("${KUBECTL_BIN}" version -o json | jq -r '.serverVersion.gitVersion')" +[[ "${SERVER_VERSION}" =~ ${K8S_SERIES_REGEX} ]] || die \ + "Cluster server version ${SERVER_VERSION} is not a supported 1.34.x release for this Rancher workflow." -# ---------- Step 14: Wait for API ---------- +# ---------- Step 19: Wait for API ---------- log "Waiting for Kubernetes API to become responsive" -retry 60 5 kubectl version --request-timeout=10s >/dev/null +retry 60 5 "${KUBECTL_BIN}" version --request-timeout=10s >/dev/null -# ---------- Step 15: Optionally allow workloads on control-plane ---------- +# ---------- Step 20: Optionally allow workloads on control-plane ---------- if [[ "${ALLOW_WORKLOADS_ON_CONTROL_PLANE}" == "true" ]]; then log "Allowing workloads on the control-plane node (single-node/lab mode)" - kubectl taint nodes --all node-role.kubernetes.io/control-plane- >/dev/null 2>&1 || true - kubectl taint nodes --all node-role.kubernetes.io/master- >/dev/null 2>&1 || true + "${KUBECTL_BIN}" taint nodes --all node-role.kubernetes.io/control-plane- >/dev/null 2>&1 || true + "${KUBECTL_BIN}" taint nodes --all node-role.kubernetes.io/master- >/dev/null 2>&1 || true fi -# ---------- Step 16: Install Calico ---------- +# ---------- Step 21: Install Calico ---------- log "Installing Calico networking" -kubectl apply -f "https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/calico.yaml" +"${KUBECTL_BIN}" apply -f "https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/calico.yaml" -# ---------- Step 17: Wait for node readiness ---------- +# ---------- Step 22: Wait for node readiness ---------- log "Waiting for node(s) to become Ready" -kubectl wait --for=condition=Ready node --all --timeout=10m +"${KUBECTL_BIN}" wait --for=condition=Ready node --all --timeout=10m -# ---------- Step 18: Wait for Calico ---------- +# ---------- Step 23: Wait for Calico ---------- log "Waiting for Calico components" -kubectl -n kube-system rollout status daemonset/calico-node --timeout=10m || true -kubectl -n kube-system rollout status deployment/calico-kube-controllers --timeout=10m || true +"${KUBECTL_BIN}" -n kube-system rollout status daemonset/calico-node --timeout=10m || true +"${KUBECTL_BIN}" -n kube-system rollout status deployment/calico-kube-controllers --timeout=10m || true -# ---------- Step 19: Save worker join command ---------- +# ---------- Step 24: Save worker join command ---------- log "Saving worker join command" -kubeadm token create --print-join-command > "${JOIN_COMMAND_FILE}" +"${KUBEADM_BIN}" token create --print-join-command > "${JOIN_COMMAND_FILE}" chmod 700 "${JOIN_COMMAND_FILE}" -# ---------- Step 20: Determine node info ---------- +# ---------- Step 25: Determine node info ---------- log "Determining control-plane node information" -NODE_NAME="$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')" -NODE_IP="$(kubectl get node "${NODE_NAME}" -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}')" +NODE_NAME="$("${KUBECTL_BIN}" get nodes -o jsonpath='{.items[0].metadata.name}')" +NODE_IP="$("${KUBECTL_BIN}" get node "${NODE_NAME}" -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}')" if [[ -z "${NODE_NAME}" || -z "${NODE_IP}" ]]; then die "Failed to determine node name or node IP" @@ -321,18 +402,7 @@ fi echo "${RANCHER_BOOTSTRAP_PASSWORD}" >/root/rancher-bootstrap-password.txt chmod 600 /root/rancher-bootstrap-password.txt -# ---------- Step 21: Kubernetes compatibility guard for Rancher ---------- -if [[ "${INSTALL_RANCHER}" == "true" ]]; then - log "Checking Kubernetes version compatibility for Rancher" - - INSTALLED_K8S_VERSION="$(kubeadm version -o short 2>/dev/null || true)" - if ! is_rancher_k8s_supported; then - warn "Installed Kubernetes version is ${INSTALLED_K8S_VERSION}" - die "Rancher install is blocked because this script is configured to allow Kubernetes up to 1.${RANCHER_MAX_SUPPORTED_K8S_MINOR}.x only. Downgrade/pin kubeadm, kubectl, and kubelet to a supported version, then rerun." - fi -fi - -# ---------- Step 22: Install Helm repos ---------- +# ---------- Step 26: Install Helm repos ---------- if [[ "${INSTALL_HELM}" == "true" ]]; then log "Configuring Helm repositories" helm_repo_add_force ingress-nginx https://kubernetes.github.io/ingress-nginx @@ -359,7 +429,7 @@ if [[ "${INSTALL_HELM}" == "true" ]]; then helm repo update fi -# ---------- Step 23: Install ingress-nginx ---------- +# ---------- Step 27: Install ingress-nginx ---------- if [[ "${INSTALL_RANCHER}" == "true" ]]; then log "Installing ingress-nginx" @@ -381,10 +451,10 @@ if [[ "${INSTALL_RANCHER}" == "true" ]]; then --timeout 15m log "Waiting for ingress-nginx controller" - kubectl -n "${INGRESS_NAMESPACE}" rollout status daemonset/ingress-nginx-controller --timeout=15m + "${KUBECTL_BIN}" -n "${INGRESS_NAMESPACE}" rollout status daemonset/ingress-nginx-controller --timeout=15m fi -# ---------- Step 24: Install cert-manager ---------- +# ---------- Step 28: Install cert-manager ---------- if [[ "${INSTALL_RANCHER}" == "true" ]]; then log "Installing cert-manager" @@ -398,12 +468,12 @@ if [[ "${INSTALL_RANCHER}" == "true" ]]; then --timeout 15m log "Waiting for cert-manager deployments" - kubectl -n "${CERT_MANAGER_NAMESPACE}" rollout status deployment/cert-manager --timeout=15m - kubectl -n "${CERT_MANAGER_NAMESPACE}" rollout status deployment/cert-manager-cainjector --timeout=15m - kubectl -n "${CERT_MANAGER_NAMESPACE}" rollout status deployment/cert-manager-webhook --timeout=15m + "${KUBECTL_BIN}" -n "${CERT_MANAGER_NAMESPACE}" rollout status deployment/cert-manager --timeout=15m + "${KUBECTL_BIN}" -n "${CERT_MANAGER_NAMESPACE}" rollout status deployment/cert-manager-cainjector --timeout=15m + "${KUBECTL_BIN}" -n "${CERT_MANAGER_NAMESPACE}" rollout status deployment/cert-manager-webhook --timeout=15m fi -# ---------- Step 25: Install Rancher ---------- +# ---------- Step 29: Install Rancher ---------- if [[ "${INSTALL_RANCHER}" == "true" ]]; then log "Installing Rancher" @@ -416,25 +486,29 @@ if [[ "${INSTALL_RANCHER}" == "true" ]]; then --set bootstrapPassword="${RANCHER_BOOTSTRAP_PASSWORD}" \ --set replicas="${RANCHER_REPLICAS}" \ --set ingress.ingressClassName="${INGRESS_CLASS_NAME}" \ + --set ingress.tls.source=rancher \ --wait \ --timeout 20m log "Waiting for Rancher rollout" - kubectl -n "${RANCHER_NAMESPACE}" rollout status deployment/rancher --timeout=20m || true + "${KUBECTL_BIN}" -n "${RANCHER_NAMESPACE}" rollout status deployment/rancher --timeout=20m || true fi -# ---------- Step 26: Show cluster status ---------- +# ---------- Step 30: Show cluster status ---------- log "Cluster status" -kubectl get nodes -o wide || true +"${KUBECTL_BIN}" get nodes -o wide || true echo -kubectl get pods -A || true +"${KUBECTL_BIN}" get pods -A || true echo -kubectl get ingress -A || true +"${KUBECTL_BIN}" get ingress -A || true # ---------- Final output ---------- echo echo "Kubernetes control plane installation is complete." echo +echo "Pinned Kubernetes version:" +echo " ${K8S_VERSION}" +echo echo "kubectl configured for:" echo " root: ${KUBECONFIG_DIR_ROOT}/config" echo " ${REAL_USER}: ${REAL_KUBECONFIG_DIR}/config" @@ -459,7 +533,9 @@ if [[ "${INSTALL_RANCHER}" == "true" ]]; then echo " sudo cat /root/rancher-bootstrap-password.txt" echo echo "Notes:" - echo " - Because ingress-nginx is using host networking, access Rancher directly on this node's IP over 443." + echo " - Rancher is using a 1.34.x Kubernetes control plane on purpose for compatibility." + echo " - ingress-nginx is using host networking, so access Rancher directly on this node's IP over 443." echo " - If a local firewall is enabled, ensure ports 80 and 443 are allowed." + echo " - Rancher-generated TLS will usually produce a browser warning until you trust the cert." echo fi \ No newline at end of file