diff --git a/arch_install.sh b/arch_install.sh index f60388b..19ac10f 100644 --- a/arch_install.sh +++ b/arch_install.sh @@ -1,20 +1,111 @@ #!/usr/bin/env bash -set -euo pipefail +set -Eeuo pipefail -# Update system -sudo pacman -Syu --noconfirm +############################################################################### +# 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 +############################################################################### -# Switch from legacy iptables to nft backend if needed -if pacman -Q iptables >/dev/null 2>&1; then - sudo pacman -Rns --noconfirm iptables || true +####################################### +# 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 -# Install Kubernetes prerequisites and container runtime -yes | sudo pacman -S --needed \ +####################################### +# 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 \ - iptables-nft \ containerd \ crictl \ kubelet \ @@ -22,61 +113,199 @@ yes | sudo pacman -S --needed \ kubectl \ conntrack-tools \ socat \ - cni-plugins + ethtool \ + cni-plugins \ + iptables-nft -# Enable and start containerd -sudo systemctl enable --now containerd.service +####################################### +# 3) Verify iptables backend +####################################### -# Generate default containerd config if missing +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 - sudo mkdir -p /etc/containerd - containerd config default | sudo tee /etc/containerd/config.toml >/dev/null + containerd config default > /etc/containerd/config.toml fi -# Set SystemdCgroup = true for kubelet compatibility -sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml -sudo systemctl restart containerd +log "Setting SystemdCgroup = true in /etc/containerd/config.toml" +sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml -# Kernel modules for Kubernetes networking -sudo tee /etc/modules-load.d/k8s.conf >/dev/null <<'EOF' +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 -sudo modprobe overlay -sudo modprobe br_netfilter +log "Loading kernel modules immediately" +modprobe overlay +modprobe br_netfilter -# Sysctl settings required by Kubernetes -sudo tee /etc/sysctl.d/k8s.conf >/dev/null <<'EOF' +####################################### +# 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 -sudo sysctl --system +log "Applying sysctl settings" +sysctl --system -# Disable swap now -sudo swapoff -a +####################################### +# 7) Disable swap now and comment swap entries in /etc/fstab +####################################### -# Disable swap on boot by commenting swap lines in /etc/fstab -sudo sed -i.bak '/\sswap\s/s/^/#/' /etc/fstab +log "Disabling swap immediately" +swapoff -a || true -# Enable kubelet -sudo systemctl enable --now kubelet.service +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 "Base Kubernetes packages are installed." -echo "Next step:" -echo " sudo kubeadm init --pod-network-cidr=10.244.0.0/16" +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 "Then set up kubectl for your user:" -echo " mkdir -p \$HOME/.kube" -echo " sudo cp -i /etc/kubernetes/admin.conf \$HOME/.kube/config" -echo " sudo chown \$(id -u):\$(id -g) \$HOME/.kube/config" +echo "==== nodes ====" +sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" get nodes -o wide || true + echo -echo "Then install Flannel:" -echo " kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml" -echo -echo "Check status with:" -echo " systemctl status containerd kubelet --no-pager" -echo " crictl info" \ No newline at end of file +echo "==== pods (all namespaces) ====" +sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" get pods -A -o wide || true + +cat <