- Shell 81.4%
- Go Template 18.6%
| cloud-init | ||
| config | ||
| kubeadm | ||
| scripts | ||
| .gitignore | ||
| CLAUDE.md | ||
| README.md | ||
k8s-local-cluster
KVM/QEMU definition for a compact, kubeadm-compatible lab cluster: one
control-plane node and two workers, all Ubuntu Server 24.04 minimal. Sized
to run comfortably alongside a normal desktop on a 16GB / 4-core (8 vCPU)
laptop. See CLAUDE.md for the full brief.
Scope: get from nothing to three provisioned, kubeadm-ready VMs. Actually
running kubeadm init/kubeadm join to form the cluster is a deliberate,
manual final step — see kubeadm/NEXT-STEPS.md.
How it works
config/cluster.env— single source of truth: VM names/IPs/MACs, sizing, Kubernetes version, image URLs. Edit this to resize or reconfigure the cluster; nothing else should need to change.- No custom OS image is baked. Every VM boots the stock
Ubuntu 24.04 cloud image
and is provisioned on first boot via cloud-init
(
cloud-init/user-data.yaml.tmpl), which installs and holdscontainerd,kubeadm,kubelet,kubectland sets the kernel/sysctl prerequisites kubeadm needs. - Each VM's disk is a thin qcow2 overlay on top of one shared, read-only base image, so the actual footprint per extra node is small.
- Nodes get static IPs via libvirt DHCP host reservations (MAC → IP) on the default NAT network, rather than static netplan config in the guest.
Sizing
3 VMs × 2 vCPU / 2GB RAM = 6 vCPU / 6GB RAM, leaving headroom on the 8
vCPU / 16GB host for the laptop's own desktop session. See
config/cluster.env to change this.
Usage
This project defines the cluster, not the hypervisor — it assumes a working KVM/libvirt host already. On a new machine, run the preflight check first; it verifies everything below without changing anything, and tells you exactly what to fix if not:
./scripts/00-check-prereqs.sh
Then the whole cluster is one command (it re-runs the preflight check as its first step, so it's safe to call directly too):
./scripts/04-create-cluster.sh # preflight, fetch base image, reserve IPs, create all 3 VMs
./scripts/05-destroy-cluster.sh # tear everything down (keeps the base image)
Individual steps (01–03) can also be run standalone; see each script's
header comment.
| Node | IP | Role |
|---|---|---|
| k8s-control | 192.168.123.11 | control-plane |
| k8s-worker-1 | 192.168.123.12 | worker |
| k8s-worker-2 | 192.168.123.13 | worker |
Default login: ubuntu user, key-only SSH using
~/.ssh/id_ed25519.pub (configurable via SSH_PUBKEY_FILE in
config/cluster.env).
Next: forming the cluster
Not automated here by design — see kubeadm/NEXT-STEPS.md.