No description
  • HCL 93.9%
  • Go Template 6.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-08 15:58:52 +02:00
ansible initial commit 2026-09-08 15:58:52 +02:00
terraform initial commit 2026-09-08 15:58:52 +02:00
.gitignore initial commit 2026-09-08 15:58:52 +02:00
README.md initial commit 2026-09-08 15:58:52 +02:00

Proxmox Kubernetes Lab

Provisions VMs on Proxmox with Terraform, then bootstraps a vanilla kubeadm Kubernetes cluster with Cilium networking using Ansible.

Either Rocky Linux or Ubuntu can be used as the guest OS — the Ansible roles detect the distro automatically (ansible_facts['os_family']) and use dnf/SELinux/firewalld on Rocky or apt/ufw on Ubuntu, so no extra configuration is needed beyond pointing template_vm_id at the template you want to clone.

Prerequisites

  • A Rocky Linux or Ubuntu template already built in Proxmox, with qemu-guest-agent and cloud-init installed (the template's VM ID is all Terraform needs).
  • A Proxmox API token with permission to clone/create/start VMs.
  • terraform >= 1.6, ansible >= 2.15, and an SSH key pair.
  • Set ssh_username in terraform.tfvars to match the template's cloud-init user convention (e.g. rocky for Rocky Linux, ubuntu for Ubuntu).
  • The template's default virtio NIC name (eth0 vs ens18) — boot one VM from the template and check with ip -o link if you're not sure; set kube_vip_interface in ansible/group_vars/all.yml to match.

1. Provision the VMs (Terraform)

cd terraform
cp terraform.tfvars.example terraform.tfvars
# edit terraform.tfvars: Proxmox connection, template_vm_id, network, etc.

terraform init
terraform apply

control_plane_count and worker_count are left unset in the example tfvars on purpose — terraform apply will prompt for them interactively:

  1. Control-plane count — 1 (single control plane) or 3 (HA control plane, validated; anything else is rejected and re-prompted).
  2. Worker count — any number from 1 to 5.

Each VM gets a random, role-tagged name (e.g. k8s-cp-fluent-panther, k8s-worker-brave-otter) and a static IP from network_cidr starting at ip_start. The address at ip_start - 1 is reserved for the kube-vip VIP used in the HA case — leave it free on your network.

On success, Terraform writes ansible/inventory/hosts.ini for you, grouped into control_plane_primary, control_plane_secondary, control_plane, workers, and kubernetes.

2. Bootstrap Kubernetes (Ansible)

cd ansible
ansible-galaxy collection install -r requirements.yml
ansible-playbook site.yml

This will, across all nodes:

  • Disable swap; on Rocky, set SELinux to permissive and disable firewalld; on Ubuntu, disable ufw (lab-only shortcut — replace with explicit firewall rules for anything beyond a lab); load overlay/br_netfilter, and set the required sysctls.
  • Install and configure containerd (systemd cgroup driver) and kubelet/kubeadm/kubectl from the pinned pkgs.k8s.io repo for kubernetes_version (via dnf on Rocky, apt on Ubuntu).

Then, control-plane-specific:

  • If control_plane_count = 3: deploy kube-vip as a static pod on every control-plane node for API-server HA, ARP mode, before kubeadm init.
  • Run kubeadm init on the primary control plane (API endpoint = the VIP in the HA case, or the node's own IP for a single control plane).
  • Join the remaining control planes with kubeadm join --control-plane and a freshly generated certificate key.
  • Install Cilium via the cilium CLI.
  • Join all worker nodes with kubeadm join.

3. Verify

ssh <ssh_username>@<control_plane_primary_ip>
kubectl get nodes -o wide
cilium status

Notes / things to adjust before using this beyond a lab

  • Firewalld (Rocky) / ufw (Ubuntu) is disabled outright rather than opening the specific kubeadm/Cilium ports — fine for an isolated lab network, not for anything exposed further.
  • kubeadm token create --print-join-command tokens expire after 24h; re-run the relevant Ansible role (or kubeadm token create by hand) if you provision workers well after the control plane.