Update arch_install.sh

This commit is contained in:
RomanNum3ral 2026-03-25 18:05:47 +00:00
parent 394dc98229
commit 967d168b1f
1 changed files with 141 additions and 126 deletions

View File

@ -4,51 +4,53 @@ set -Eeuo pipefail
############################################################################### ###############################################################################
# Arch Linux Kubernetes bootstrap script # 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: # What this script does:
# 1. Updates the system # 1. Updates the system
# 2. Replaces legacy iptables with iptables-nft in one transaction # 2. Replaces legacy iptables with iptables-nft in the same pacman transaction
# 3. Installs Kubernetes packages and containerd # 3. Installs Kubernetes packages and dependencies
# 4. Configures containerd to use systemd cgroups # 4. Configures containerd for systemd cgroups
# 5. Enables required kernel modules and sysctl settings # 5. Enables required kernel modules and sysctl values
# 6. Disables swap now and on reboot # 6. Disables swap now and on boot
# 7. Enables containerd and kubelet # 7. Enables and starts containerd and kubelet
# 8. Initializes a single control-plane node with kubeadm # 8. Initializes the cluster with kubeadm (if not already initialized)
# 9. Configures kubectl for the invoking user # 9. Configures kubectl for the invoking user
# 10. Installs Flannel CNI # 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: # Usage:
# - This script is intended for a fresh/single-node lab setup. # chmod +x arch_install.sh
# - Re-running is mostly safe; it skips kubeadm init if already initialized. # sudo ./arch_install.sh
# - Run it as: #
# sudo ./arch_k8s_bootstrap.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}" POD_CIDR="${POD_CIDR:-10.244.0.0/16}"
# Flannel manifest URL from current flannel docs/releases # Flannel manifest
FLANNEL_MANIFEST_URL="${FLANNEL_MANIFEST_URL:-https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml}" 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 # Set to true if this is a single-node lab and you want normal workloads to run
# on the control-plane node after setup. # on the control-plane node.
ALLOW_PODS_ON_CONTROL_PLANE="${ALLOW_PODS_ON_CONTROL_PLANE:-true}" ALLOW_SINGLE_NODE_SCHEDULING="${ALLOW_SINGLE_NODE_SCHEDULING:-true}"
# The user who should receive ~/.kube/config. # Which user should receive ~/.kube/config
# If script is run with sudo, prefer the original user.
TARGET_USER="${SUDO_USER:-root}" 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 # Helper functions
####################################### #######################################
@ -62,42 +64,58 @@ die() {
exit 1 exit 1
} }
require_root() { run_as_target_user() {
[[ "${EUID}" -eq 0 ]] || die "Run this script with sudo or as root." if [[ "$TARGET_USER" == "root" ]]; then
} "$@"
else
command_exists() { sudo -u "$TARGET_USER" "$@"
command -v "$1" >/dev/null 2>&1 fi
} }
####################################### #######################################
# Sanity checks # Root check
####################################### #######################################
require_root if [[ "${EUID}" -ne 0 ]]; then
die "Run this script with sudo or as root."
if [[ -z "${TARGET_HOME}" || ! -d "${TARGET_HOME}" ]]; then
die "Could not determine home directory for target user: ${TARGET_USER}"
fi 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 "Starting Arch Kubernetes bootstrap"
log "Target kubectl user: ${TARGET_USER}" log "Target kubectl user: $TARGET_USER"
log "Target home: ${TARGET_HOME}" log "Target home: $TARGET_HOME"
####################################### #######################################
# 1) Fully update Arch # 1) Update system
####################################### #######################################
log "Updating package databases and upgrading system" log "Updating package databases and upgrading system"
pacman -Syu --noconfirm pacman -Syu --noconfirm
####################################### #######################################
# 2) Install required packages # 2) Install packages
# #
# Important: # Important:
# - iptables-nft must replace legacy iptables in the SAME transaction. # iptables-nft conflicts with legacy iptables, so we must allow pacman to
# - We intentionally do not remove iptables first because that can break # replace iptables during the SAME transaction.
# dependency resolution temporarily for packages that need libxtables. #
# 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" log "Installing container runtime, Kubernetes tools, networking tools, and iptables-nft"
@ -125,45 +143,24 @@ log "Verifying iptables backend"
iptables --version || true iptables --version || true
####################################### #######################################
# 4) Enable and configure containerd # 4) Persist required kernel modules
#
# kubelet works best with containerd configured to use systemd cgroups.
####################################### #######################################
log "Enabling and starting containerd" log "Writing required kernel modules"
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' cat > /etc/modules-load.d/k8s.conf <<'EOF'
overlay overlay
br_netfilter br_netfilter
EOF EOF
log "Loading kernel modules immediately" log "Loading kernel modules now"
modprobe overlay modprobe overlay
modprobe br_netfilter 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' cat > /etc/sysctl.d/k8s.conf <<'EOF'
net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-ip6tables = 1
@ -174,138 +171,156 @@ log "Applying sysctl settings"
sysctl --system sysctl --system
####################################### #######################################
# 7) Disable swap now and comment swap entries in /etc/fstab # 6) Disable swap now and on boot
####################################### #######################################
log "Disabling swap immediately" log "Disabling swap immediately"
swapoff -a || true swapoff -a || true
log "Commenting active swap entries in /etc/fstab" log "Commenting swap entries in /etc/fstab"
if [[ -f /etc/fstab ]]; then 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 sed -i '/^[^#].*\sswap\s/s/^/# /' /etc/fstab
fi fi
####################################### #######################################
# 8) Enable kubelet # 7) Configure containerd
# #
# kubelet may show as active but waiting until kubeadm init finishes; that is # Kubernetes works better with systemd cgroups.
# normal before the control plane exists.
####################################### #######################################
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" log "Enabling and starting kubelet"
systemctl enable --now kubelet.service systemctl enable --now kubelet.service
####################################### log "Showing containerd status"
# 9) Preflight visibility
#######################################
log "Container runtime status"
systemctl --no-pager --full status containerd.service || true systemctl --no-pager --full status containerd.service || true
log "Kubelet status" log "Showing kubelet status"
systemctl --no-pager --full status kubelet.service || true 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 if [[ ! -f /etc/kubernetes/admin.conf ]]; then
log "Initializing Kubernetes control plane with kubeadm" log "Initializing Kubernetes control plane"
kubeadm init --pod-network-cidr="${POD_CIDR}" kubeadm init --pod-network-cidr="$POD_CIDR"
else else
log "Skipping kubeadm init because /etc/kubernetes/admin.conf already exists" log "Cluster already initialized; skipping kubeadm init"
fi fi
####################################### #######################################
# 11) Configure kubectl for target user # 10) Configure kubectl for target user
####################################### #######################################
log "Configuring kubectl for ${TARGET_USER}" log "Configuring kubectl for $TARGET_USER"
mkdir -p "${TARGET_HOME}/.kube" mkdir -p "$TARGET_HOME/.kube"
cp -f /etc/kubernetes/admin.conf "${TARGET_HOME}/.kube/config" cp -f /etc/kubernetes/admin.conf "$TARGET_HOME/.kube/config"
chown -R "${TARGET_USER}:${TARGET_USER}" "${TARGET_HOME}/.kube" chown -R "$TARGET_USER:$TARGET_USER" "$TARGET_HOME/.kube"
chmod 700 "${TARGET_HOME}/.kube" chmod 700 "$TARGET_HOME/.kube"
chmod 600 "${TARGET_HOME}/.kube/config" 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" log "Waiting for Kubernetes API server"
for _ in $(seq 1 60); do for _ in $(seq 1 90); do
if sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" get nodes >/dev/null 2>&1; then if run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" get nodes >/dev/null 2>&1; then
break break
fi fi
sleep 2 sleep 2
done done
####################################### #######################################
# 13) Install Flannel if not already present # 12) Install Flannel if missing
####################################### #######################################
log "Installing Flannel CNI" log "Installing Flannel CNI if needed"
if ! sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" get namespace kube-flannel >/dev/null 2>&1; then if ! run_as_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}" run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" apply -f "$FLANNEL_URL"
else else
log "kube-flannel namespace already exists; skipping Flannel install" log "Flannel already present; skipping apply"
fi 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 if [[ "$ALLOW_SINGLE_NODE_SCHEDULING" == "true" ]]; then
log "Allowing workloads on the control-plane node (single-node lab mode)" log "Removing control-plane taint for single-node scheduling"
sudo -u "${TARGET_USER}" kubectl --kubeconfig="${TARGET_HOME}/.kube/config" taint nodes --all node-role.kubernetes.io/control-plane- || true run_as_target_user kubectl --kubeconfig="$TARGET_HOME/.kube/config" taint nodes --all node-role.kubernetes.io/control-plane- || true
else else
log "Leaving default control-plane taint in place" log "Leaving control-plane taint in place"
fi fi
####################################### #######################################
# 15) Final status / useful commands # 14) Final status
####################################### #######################################
log "Final checks" log "Final cluster checks"
echo echo
echo "==== iptables backend ====" echo "==== iptables backend ===="
iptables --version || true iptables --version || true
echo echo
echo "==== containerd info ====" echo "==== containerd status ===="
crictl info >/dev/null 2>&1 && echo "crictl can talk to the runtime" || echo "crictl check did not succeed yet" systemctl --no-pager --full status containerd.service || true
echo
echo "==== kubelet status ===="
systemctl --no-pager --full status kubelet.service || true
echo echo
echo "==== nodes ====" 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
echo "==== pods (all namespaces) ====" 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 <<EOF cat <<EOF
Bootstrap complete. Bootstrap complete.
kubectl is configured for: kubectl config:
user: ${TARGET_USER} User: $TARGET_USER
config: ${TARGET_HOME}/.kube/config Config: $TARGET_HOME/.kube/config
Common next commands: Useful commands:
kubectl get nodes kubectl get nodes
kubectl get pods -A kubectl get pods -A
kubectl cluster-info kubectl cluster-info
If the node is not Ready yet, wait a minute and re-run: Test workload:
kubectl get nodes
kubectl get pods -A
To test the cluster:
kubectl create deployment nginx --image=nginx kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80 --type=NodePort kubectl expose deployment nginx --port=80 --type=NodePort
kubectl get svc kubectl get svc
To join worker nodes later, generate a join command with: Worker join command:
kubeadm token create --print-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 EOF