Last active
June 20, 2026 14:53
-
-
Save anisharaz/7f41bb85dc43fcb24dadc8c3d20094a3 to your computer and use it in GitHub Desktop.
Bash script to create k8s cluster using incus vm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -Eeuo pipefail | |
| ############################################################################### | |
| # Configuration | |
| ############################################################################### | |
| VM_NAME="i-master" | |
| CPU="2" | |
| MEMORY="2GiB" | |
| IMAGE="images:ubuntu/questing/cloud" | |
| usage() { | |
| cat <<EOF | |
| Usage: $0 [options] | |
| Options: | |
| --cpu <count> vCPU count (default: 2) | |
| --memory <size> Memory size (default: 2GiB) | |
| --name <name> VM name (default: i-master) | |
| Examples: | |
| $0 | |
| $0 --cpu 4 --memory 8GiB | |
| EOF | |
| } | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --cpu) | |
| CPU="$2" | |
| shift 2 | |
| ;; | |
| --memory) | |
| MEMORY="$2" | |
| shift 2 | |
| ;; | |
| --name) | |
| VM_NAME="$2" | |
| shift 2 | |
| ;; | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| echo "Unknown argument: $1" | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| ############################################################################### | |
| # Helpers | |
| ############################################################################### | |
| log() { | |
| echo | |
| echo "====================================================" | |
| echo "$*" | |
| echo "====================================================" | |
| } | |
| wait_for_vm() { | |
| log "Waiting for VM to start" | |
| while true; do | |
| status=$(incus list "$VM_NAME" --format csv -c s 2>/dev/null || true) | |
| if [[ "$status" == "RUNNING" ]]; then | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| echo "VM is running" | |
| } | |
| wait_for_cloud_init() { | |
| log "Waiting for cloud-init" | |
| # Loop until the VM agent is available and cloud-init reports finished | |
| while true; do | |
| # Try to query cloud-init status; ignore errors if the agent is not ready | |
| if incus exec "$VM_NAME" -- cloud-init status --wait 2>/dev/null; then | |
| echo "Cloud-init completed" | |
| break | |
| fi | |
| echo "VM agent not ready yet – retrying in 5s" | |
| sleep 5 | |
| done | |
| } | |
| exec_vm() { | |
| incus exec "$VM_NAME" -- bash -c "$1" | |
| } | |
| ############################################################################### | |
| # Create VM | |
| ############################################################################### | |
| log "Creating VM $VM_NAME" | |
| if incus info "$VM_NAME" >/dev/null 2>&1; then | |
| echo "VM already exists: $VM_NAME" | |
| exit 1 | |
| fi | |
| incus launch "$IMAGE" "$VM_NAME" --vm -c limits.cpu="$CPU" -c limits.memory="$MEMORY" | |
| wait_for_vm | |
| wait_for_cloud_init | |
| ############################################################################### | |
| # Base packages | |
| ############################################################################### | |
| log "Updating apt repositories" | |
| exec_vm " | |
| apt-get update | |
| " | |
| log "Installing prerequisites" | |
| exec_vm " | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget gpg ca-certificates apt-transport-https software-properties-common containerd | |
| " | |
| ############################################################################### | |
| # Containerd | |
| ############################################################################### | |
| log "Configuring containerd" | |
| exec_vm " | |
| systemctl enable containerd | |
| systemctl restart containerd | |
| " | |
| ############################################################################### | |
| # Kernel + sysctl | |
| ############################################################################### | |
| log "Configuring kernel modules" | |
| exec_vm " | |
| cat >/etc/modules-load.d/k8s.conf <<EOF | |
| overlay | |
| br_netfilter | |
| EOF | |
| modprobe overlay | |
| modprobe br_netfilter | |
| " | |
| log "Configuring sysctl" | |
| exec_vm " | |
| touch /etc/sysctl.conf && | |
| (grep -q '^net.ipv4.ip_forward=' /etc/sysctl.conf && sed -i 's/^net.ipv4.ip_forward=.*/net.ipv4.ip_forward=1/' /etc/sysctl.conf) || echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf && | |
| grep -q '^net.bridge.bridge-nf-call-iptables=' /etc/sysctl.conf || echo 'net.bridge.bridge-nf-call-iptables=1' >> /etc/sysctl.conf && | |
| grep -q '^net.bridge.bridge-nf-call-ip6tables=' /etc/sysctl.conf || echo 'net.bridge.bridge-nf-call-ip6tables=1' >> /etc/sysctl.conf && | |
| sysctl -p | |
| " | |
| ############################################################################### | |
| # Kubernetes repo | |
| ############################################################################### | |
| log "Installing Kubernetes repository" | |
| exec_vm " | |
| mkdir -p /etc/apt/keyrings && \ | |
| curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg && \ | |
| chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg && \ | |
| echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.34/deb/ /' > /etc/apt/sources.list.d/kubernetes.list && \ | |
| apt-get update | |
| " | |
| ############################################################################### | |
| # kubeadm | |
| ############################################################################### | |
| log "Installing kubeadm components" | |
| exec_vm " | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y kubelet kubeadm kubectl && \ | |
| apt-mark hold kubelet kubeadm kubectl && \ | |
| systemctl enable kubelet | |
| " | |
| ############################################################################### | |
| # Disable swap | |
| ############################################################################### | |
| log "Disabling swap" | |
| exec_vm " | |
| swapoff -a || true | |
| sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab || true | |
| " | |
| ############################################################################### | |
| # Get IP | |
| ############################################################################### | |
| log "Discovering VM IP" | |
| MASTER_IP=$(incus list "$VM_NAME" --format csv -c 4 | awk '{print $1}') | |
| echo "Master IP: $MASTER_IP" | |
| ############################################################################### | |
| # Cluster init | |
| ############################################################################### | |
| log "Initializing Kubernetes cluster" | |
| exec_vm " | |
| kubeadm init \ | |
| --apiserver-advertise-address=${MASTER_IP} \ | |
| --pod-network-cidr=10.244.0.0/16 | |
| " | |
| ############################################################################### | |
| # kubeconfig | |
| ############################################################################### | |
| log "Configuring kubectl" | |
| exec_vm " | |
| mkdir -p /root/.kube | |
| cp /etc/kubernetes/admin.conf /root/.kube/config | |
| chown root:root /root/.kube/config | |
| " | |
| ############################################################################### | |
| # Wait for API | |
| ############################################################################### | |
| log "Waiting for API server" | |
| until incus exec "$VM_NAME" -- bash -c "export KUBECONFIG=/etc/kubernetes/admin.conf && kubectl get nodes >/dev/null 2>&1" | |
| do | |
| sleep 5 | |
| done | |
| ############################################################################### | |
| # Helm | |
| ############################################################################### | |
| log "Installing Helm" | |
| exec_vm " | |
| curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash | |
| " | |
| ############################################################################### | |
| # Cilium | |
| ############################################################################### | |
| log "Installing Cilium" | |
| exec_vm " | |
| export KUBECONFIG=/etc/kubernetes/admin.conf | |
| helm repo add cilium https://helm.cilium.io | |
| helm repo update | |
| helm upgrade --install cilium cilium/cilium \ | |
| --namespace kube-system \ | |
| --set operator.replicas=1 | |
| " | |
| ############################################################################### | |
| # Wait for node | |
| ############################################################################### | |
| log "Waiting for node readiness" | |
| exec_vm " | |
| export KUBECONFIG=/etc/kubernetes/admin.conf | |
| kubectl wait --for=condition=Ready node/\$(hostname) --timeout=15m | |
| " | |
| ############################################################################### | |
| # Wait for Cilium | |
| ############################################################################### | |
| log "Waiting for Cilium" | |
| exec_vm " | |
| export KUBECONFIG=/etc/kubernetes/admin.conf | |
| kubectl rollout status \ | |
| daemonset/cilium \ | |
| -n kube-system \ | |
| --timeout=15m | |
| " | |
| ############################################################################### | |
| # Cluster status | |
| ############################################################################### | |
| log "Cluster status" | |
| exec_vm " | |
| export KUBECONFIG=/etc/kubernetes/admin.conf | |
| kubectl get nodes -o wide | |
| echo | |
| kubectl get pods -A | |
| " | |
| ############################################################################### | |
| # Join command | |
| ############################################################################### | |
| log "Worker join command" | |
| JOIN_COMMAND=$(incus exec "$VM_NAME" -- bash -c "export KUBECONFIG=/etc/kubernetes/admin.conf && kubeadm token create --print-join-command") | |
| echo | |
| echo "$JOIN_COMMAND" | |
| echo | |
| log "Cluster is ready" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment