#!/usr/bin/env bash set -Eeuo pipefail ############################################################################### # Arch Linux Kubernetes bootstrap script # # 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 # 9. Configures kubectl for the invoking user # 10. Installs Flannel CNI # 11. Optionally allows scheduling pods on the control-plane node # # 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 ############################################################################### ####################################### # User-tunable variables ####################################### # Pod CIDR required by Flannel docs 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}" # 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}" # The user who should receive ~/.kube/config. # If script is run with sudo, prefer the original user. 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 ####################################### log() { printf '\n[%s] %s\n' "$(date '+%F %T')" "$*" } die() { printf '\n[ERROR] %s\n' "$*" >&2 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 } ####################################### # Sanity checks ####################################### require_root if [[ -z "${TARGET_HOME}" || ! -d "${TARGET_HOME}" ]]; then die "Could not determine home directory for target user: ${TARGET_USER}" fi log "Starting Arch Kubernetes bootstrap" log "Target kubectl user: ${TARGET_USER}" log "Target home: ${TARGET_HOME}" ####################################### # 1) Fully update Arch ####################################### log "Updating package databases and upgrading system" pacman -Syu --noconfirm ####################################### # 2) Install required 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. ####################################### 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) Enable and configure containerd # # kubelet works best with containerd configured to use systemd cgroups. ####################################### 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" cat > /etc/modules-load.d/k8s.conf <<'EOF' overlay br_netfilter EOF log "Loading kernel modules immediately" modprobe overlay modprobe br_netfilter ####################################### # 6) Apply required sysctl settings now and on boot ####################################### log "Persisting 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 ####################################### # 7) Disable swap now and comment swap entries in /etc/fstab ####################################### log "Disabling swap immediately" swapoff -a || true log "Commenting active 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 ####################################### # 8) Enable kubelet # # kubelet may show as active but waiting until kubeadm init finishes; that is # normal before the control plane exists. ####################################### log "Enabling and starting kubelet" systemctl enable --now kubelet.service ####################################### # 9) Preflight visibility ####################################### log "Container runtime status" systemctl --no-pager --full status containerd.service || true log "Kubelet status" systemctl --no-pager --full status kubelet.service || true ####################################### # 10) Initialize cluster if not already initialized ####################################### if [[ ! -f /etc/kubernetes/admin.conf ]]; then log "Initializing Kubernetes control plane with kubeadm" kubeadm init --pod-network-cidr="${POD_CIDR}" else log "Skipping kubeadm init because /etc/kubernetes/admin.conf already exists" fi ####################################### # 11) 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" ####################################### # 12) Wait briefly for API server to become responsive ####################################### 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 break fi sleep 2 done ####################################### # 13) Install Flannel if not already present ####################################### 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}" else log "kube-flannel namespace already exists; skipping Flannel install" fi ####################################### # 14) Optionally allow scheduling on single-node control plane ####################################### 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 else log "Leaving default control-plane taint in place" fi ####################################### # 15) Final status / useful commands ####################################### log "Final 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 echo "==== nodes ====" sudo -u "${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 cat <