#!/usr/bin/env bash 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 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 removes the control-plane taint for single-node use # # 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-configurable values ####################################### # Pod CIDR for Flannel POD_CIDR="${POD_CIDR:-10.244.0.0/16}" # 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 normal workloads to run # on the control-plane node. ALLOW_SINGLE_NODE_SCHEDULING="${ALLOW_SINGLE_NODE_SCHEDULING:-true}" # Which user should receive ~/.kube/config TARGET_USER="${SUDO_USER:-root}" ####################################### # Helper functions ####################################### log() { printf '\n[%s] %s\n' "$(date '+%F %T')" "$*" } die() { printf '\n[ERROR] %s\n' "$*" >&2 exit 1 } run_as_target_user() { if [[ "$TARGET_USER" == "root" ]]; then "$@" else sudo -u "$TARGET_USER" "$@" fi } ####################################### # Root check ####################################### 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" ####################################### # 1) Update system ####################################### log "Updating package databases and upgrading system" pacman -Syu --noconfirm ####################################### # 2) Install packages # # Important: # 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" yes | pacman -S --needed \ ca-certificates \ curl \ wget \ containerd \ crictl \ kubelet \ kubeadm \ kubectl \ conntrack-tools \ socat \ ethtool \ cni-plugins \ iptables-nft ####################################### # 3) Verify iptables backend ####################################### log "Verifying iptables backend" iptables --version || true ####################################### # 4) Persist required kernel modules ####################################### log "Writing required kernel modules" cat > /etc/modules-load.d/k8s.conf <<'EOF' overlay br_netfilter EOF log "Loading kernel modules now" modprobe overlay modprobe br_netfilter ####################################### # 5) Persist 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 net.ipv4.ip_forward = 1 EOF log "Applying sysctl settings" sysctl --system ####################################### # 6) Disable swap now and on boot ####################################### log "Disabling swap immediately" swapoff -a || true log "Commenting swap entries in /etc/fstab" if [[ -f /etc/fstab ]]; then cp /etc/fstab "/etc/fstab.bak.$(date +%s)" sed -i '/^[^#].*\sswap\s/s/^/# /' /etc/fstab fi ####################################### # 7) Configure containerd # # 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 log "Showing containerd status" systemctl --no-pager --full status containerd.service || true log "Showing kubelet status" systemctl --no-pager --full status kubelet.service || true ####################################### # 9) Initialize cluster if needed ####################################### if [[ ! -f /etc/kubernetes/admin.conf ]]; then log "Initializing Kubernetes control plane" kubeadm init --pod-network-cidr="$POD_CIDR" else log "Cluster already initialized; skipping kubeadm init" fi ####################################### # 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" ####################################### # 11) Wait for API responsiveness ####################################### 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 ####################################### # 12) Install Flannel if missing ####################################### 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 "Flannel already present; skipping apply" fi ####################################### # 13) Allow single-node scheduling if requested ####################################### 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 control-plane taint in place" fi ####################################### # 14) Final status ####################################### log "Final cluster checks" echo echo "==== iptables backend ====" iptables --version || true echo 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 ====" run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" get nodes -o wide || true echo echo "==== pods (all namespaces) ====" run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" get pods -A -o wide || true cat <