kubernetes-arch-install/arch_install.sh

326 lines
8.7 KiB
Bash

#!/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 <<EOF
Bootstrap complete.
kubectl config:
User: $TARGET_USER
Config: $TARGET_HOME/.kube/config
Useful commands:
kubectl get nodes
kubectl get pods -A
kubectl cluster-info
Test workload:
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80 --type=NodePort
kubectl get svc
Worker join command:
kubeadm token create --print-join-command
If the node is still NotReady immediately after install, wait 30-60 seconds and re-check:
kubectl get nodes
kubectl get pods -A
EOF