From 967d168b1fa3d2b1ecfd485ede0ea31de462068b Mon Sep 17 00:00:00 2001 From: RomanNum3ral Date: Wed, 25 Mar 2026 18:05:47 +0000 Subject: [PATCH] Update arch_install.sh --- arch_install.sh | 267 +++++++++++++++++++++++++----------------------- 1 file changed, 141 insertions(+), 126 deletions(-) diff --git a/arch_install.sh b/arch_install.sh index 19ac10f..9cec33c 100644 --- a/arch_install.sh +++ b/arch_install.sh @@ -4,51 +4,53 @@ set -Eeuo pipefail ############################################################################### # Arch Linux Kubernetes bootstrap script # +# Purpose: +# Fully automate a single-node Kubernetes control-plane install on Arch Linux +# using: +# - containerd +# - kubeadm +# - kubectl +# - Flannel CNI +# # What this script does: # 1. Updates the system -# 2. Replaces legacy iptables with iptables-nft in one transaction -# 3. Installs Kubernetes packages and containerd -# 4. Configures containerd to use systemd cgroups -# 5. Enables required kernel modules and sysctl settings -# 6. Disables swap now and on reboot -# 7. Enables containerd and kubelet -# 8. Initializes a single control-plane node with kubeadm +# 2. Replaces legacy iptables with iptables-nft in the same pacman transaction +# 3. Installs Kubernetes packages and dependencies +# 4. Configures containerd for systemd cgroups +# 5. Enables required kernel modules and sysctl values +# 6. Disables swap now and on boot +# 7. Enables and starts containerd and kubelet +# 8. Initializes the cluster with kubeadm (if not already initialized) # 9. Configures kubectl for the invoking user # 10. Installs Flannel CNI -# 11. Optionally allows scheduling pods on the control-plane node +# 11. Optionally removes the control-plane taint for single-node use # -# Notes: -# - This script is intended for a fresh/single-node lab setup. -# - Re-running is mostly safe; it skips kubeadm init if already initialized. -# - Run it as: -# sudo ./arch_k8s_bootstrap.sh +# Usage: +# chmod +x arch_install.sh +# sudo ./arch_install.sh +# +# Re-run behavior: +# This script is written to be mostly idempotent. If the cluster is already +# initialized, it will skip kubeadm init and preserve the existing cluster. ############################################################################### ####################################### -# User-tunable variables +# User-configurable values ####################################### -# Pod CIDR required by Flannel docs +# Pod CIDR for Flannel POD_CIDR="${POD_CIDR:-10.244.0.0/16}" -# Flannel manifest URL from current flannel docs/releases -FLANNEL_MANIFEST_URL="${FLANNEL_MANIFEST_URL:-https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml}" +# Flannel manifest +FLANNEL_URL="${FLANNEL_URL:-https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml}" -# Set to "true" if this is a single-node lab and you want workloads allowed -# on the control-plane node after setup. -ALLOW_PODS_ON_CONTROL_PLANE="${ALLOW_PODS_ON_CONTROL_PLANE:-true}" +# Set to true if this is a single-node lab and you want normal workloads to run +# on the control-plane node. +ALLOW_SINGLE_NODE_SCHEDULING="${ALLOW_SINGLE_NODE_SCHEDULING:-true}" -# The user who should receive ~/.kube/config. -# If script is run with sudo, prefer the original user. +# Which user should receive ~/.kube/config TARGET_USER="${SUDO_USER:-root}" -# Figure out that user's home directory safely. -if [[ "${TARGET_USER}" == "root" ]]; then - TARGET_HOME="/root" -else - TARGET_HOME="$(getent passwd "${TARGET_USER}" | cut -d: -f6)" -fi - ####################################### # Helper functions ####################################### @@ -62,42 +64,58 @@ die() { exit 1 } -require_root() { - [[ "${EUID}" -eq 0 ]] || die "Run this script with sudo or as root." -} - -command_exists() { - command -v "$1" >/dev/null 2>&1 +run_as_target_user() { + if [[ "$TARGET_USER" == "root" ]]; then + "$@" + else + sudo -u "$TARGET_USER" "$@" + fi } ####################################### -# Sanity checks +# Root check ####################################### -require_root - -if [[ -z "${TARGET_HOME}" || ! -d "${TARGET_HOME}" ]]; then - die "Could not determine home directory for target user: ${TARGET_USER}" +if [[ "${EUID}" -ne 0 ]]; then + die "Run this script with sudo or as root." fi +####################################### +# Resolve target home directory +####################################### + +if [[ "$TARGET_USER" == "root" ]]; then + TARGET_HOME="/root" +else + TARGET_HOME="$(getent passwd "$TARGET_USER" | cut -d: -f6)" +fi + +[[ -n "${TARGET_HOME:-}" && -d "$TARGET_HOME" ]] || die "Could not determine home directory for $TARGET_USER" + log "Starting Arch Kubernetes bootstrap" -log "Target kubectl user: ${TARGET_USER}" -log "Target home: ${TARGET_HOME}" +log "Target kubectl user: $TARGET_USER" +log "Target home: $TARGET_HOME" ####################################### -# 1) Fully update Arch +# 1) Update system ####################################### log "Updating package databases and upgrading system" pacman -Syu --noconfirm ####################################### -# 2) Install required packages +# 2) Install packages # # Important: -# - iptables-nft must replace legacy iptables in the SAME transaction. -# - We intentionally do not remove iptables first because that can break -# dependency resolution temporarily for packages that need libxtables. +# iptables-nft conflicts with legacy iptables, so we must allow pacman to +# replace iptables during the SAME transaction. +# +# Why we use yes | pacman here: +# pacman may ask: +# "Remove iptables? [y/N]" +# and then: +# "Proceed with installation? [Y/n]" +# Using yes feeds "y" to both prompts so the script does not stop there. ####################################### log "Installing container runtime, Kubernetes tools, networking tools, and iptables-nft" @@ -125,45 +143,24 @@ log "Verifying iptables backend" iptables --version || true ####################################### -# 4) Enable and configure containerd -# -# kubelet works best with containerd configured to use systemd cgroups. +# 4) Persist required kernel modules ####################################### -log "Enabling and starting containerd" -systemctl enable --now containerd.service - -log "Creating default containerd config if missing" -mkdir -p /etc/containerd -if [[ ! -f /etc/containerd/config.toml ]]; then - containerd config default > /etc/containerd/config.toml -fi - -log "Setting SystemdCgroup = true in /etc/containerd/config.toml" -sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml - -log "Restarting containerd to apply config" -systemctl restart containerd.service - -####################################### -# 5) Load required kernel modules now and on boot -####################################### - -log "Persisting required kernel modules" +log "Writing required kernel modules" cat > /etc/modules-load.d/k8s.conf <<'EOF' overlay br_netfilter EOF -log "Loading kernel modules immediately" +log "Loading kernel modules now" modprobe overlay modprobe br_netfilter ####################################### -# 6) Apply required sysctl settings now and on boot +# 5) Persist required sysctl settings ####################################### -log "Persisting required sysctl settings" +log "Writing required sysctl settings" cat > /etc/sysctl.d/k8s.conf <<'EOF' net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 @@ -174,138 +171,156 @@ log "Applying sysctl settings" sysctl --system ####################################### -# 7) Disable swap now and comment swap entries in /etc/fstab +# 6) Disable swap now and on boot ####################################### log "Disabling swap immediately" swapoff -a || true -log "Commenting active swap entries in /etc/fstab" +log "Commenting swap entries in /etc/fstab" if [[ -f /etc/fstab ]]; then - cp /etc/fstab /etc/fstab.bak.$(date +%s) + cp /etc/fstab "/etc/fstab.bak.$(date +%s)" sed -i '/^[^#].*\sswap\s/s/^/# /' /etc/fstab fi ####################################### -# 8) Enable kubelet +# 7) Configure containerd # -# kubelet may show as active but waiting until kubeadm init finishes; that is -# normal before the control plane exists. +# Kubernetes works better with systemd cgroups. ####################################### +log "Preparing /etc/containerd" +mkdir -p /etc/containerd + +log "Generating default containerd config if needed" +if [[ ! -f /etc/containerd/config.toml ]]; then + containerd config default > /etc/containerd/config.toml +fi + +log "Ensuring SystemdCgroup = true" +sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml + +####################################### +# 8) Enable and start services +####################################### + +log "Enabling and starting containerd" +systemctl enable --now containerd.service + log "Enabling and starting kubelet" systemctl enable --now kubelet.service -####################################### -# 9) Preflight visibility -####################################### - -log "Container runtime status" +log "Showing containerd status" systemctl --no-pager --full status containerd.service || true -log "Kubelet status" +log "Showing kubelet status" systemctl --no-pager --full status kubelet.service || true ####################################### -# 10) Initialize cluster if not already initialized +# 9) Initialize cluster if needed ####################################### if [[ ! -f /etc/kubernetes/admin.conf ]]; then - log "Initializing Kubernetes control plane with kubeadm" - kubeadm init --pod-network-cidr="${POD_CIDR}" + log "Initializing Kubernetes control plane" + kubeadm init --pod-network-cidr="$POD_CIDR" else - log "Skipping kubeadm init because /etc/kubernetes/admin.conf already exists" + log "Cluster already initialized; skipping kubeadm init" fi ####################################### -# 11) Configure kubectl for target user +# 10) Configure kubectl for target user ####################################### -log "Configuring kubectl for ${TARGET_USER}" -mkdir -p "${TARGET_HOME}/.kube" -cp -f /etc/kubernetes/admin.conf "${TARGET_HOME}/.kube/config" -chown -R "${TARGET_USER}:${TARGET_USER}" "${TARGET_HOME}/.kube" -chmod 700 "${TARGET_HOME}/.kube" -chmod 600 "${TARGET_HOME}/.kube/config" +log "Configuring kubectl for $TARGET_USER" +mkdir -p "$TARGET_HOME/.kube" +cp -f /etc/kubernetes/admin.conf "$TARGET_HOME/.kube/config" +chown -R "$TARGET_USER:$TARGET_USER" "$TARGET_HOME/.kube" +chmod 700 "$TARGET_HOME/.kube" +chmod 600 "$TARGET_HOME/.kube/config" ####################################### -# 12) Wait briefly for API server to become responsive +# 11) Wait for API responsiveness ####################################### -log "Waiting for Kubernetes API to become reachable" -for _ in $(seq 1 60); do - if sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" get nodes >/dev/null 2>&1; then +log "Waiting for Kubernetes API server" +for _ in $(seq 1 90); do + if run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" get nodes >/dev/null 2>&1; then break fi sleep 2 done ####################################### -# 13) Install Flannel if not already present +# 12) Install Flannel if missing ####################################### -log "Installing Flannel CNI" -if ! sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" get namespace kube-flannel >/dev/null 2>&1; then - sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" apply -f "${FLANNEL_MANIFEST_URL}" +log "Installing Flannel CNI if needed" +if ! run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" get namespace kube-flannel >/dev/null 2>&1; then + run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" apply -f "$FLANNEL_URL" else - log "kube-flannel namespace already exists; skipping Flannel install" + log "Flannel already present; skipping apply" fi ####################################### -# 14) Optionally allow scheduling on single-node control plane +# 13) Allow single-node scheduling if requested ####################################### -if [[ "${ALLOW_PODS_ON_CONTROL_PLANE}" == "true" ]]; then - log "Allowing workloads on the control-plane node (single-node lab mode)" - sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" taint nodes --all node-role.kubernetes.io/control-plane- || true +if [[ "$ALLOW_SINGLE_NODE_SCHEDULING" == "true" ]]; then + log "Removing control-plane taint for single-node scheduling" + run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" taint nodes --all node-role.kubernetes.io/control-plane- || true else - log "Leaving default control-plane taint in place" + log "Leaving control-plane taint in place" fi ####################################### -# 15) Final status / useful commands +# 14) Final status ####################################### -log "Final checks" +log "Final cluster checks" + echo echo "==== iptables backend ====" iptables --version || true echo -echo "==== containerd info ====" -crictl info >/dev/null 2>&1 && echo "crictl can talk to the runtime" || echo "crictl check did not succeed yet" +echo "==== containerd status ====" +systemctl --no-pager --full status containerd.service || true + +echo +echo "==== kubelet status ====" +systemctl --no-pager --full status kubelet.service || true echo echo "==== nodes ====" -sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" get nodes -o wide || true +run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" get nodes -o wide || true echo echo "==== pods (all namespaces) ====" -sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" get pods -A -o wide || true +run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" get pods -A -o wide || true cat <