Skip to content

HA Kubernetes on Two Proxmox Hosts

First PublishedByAtif Alam

This lab series builds a kubeadm-based Kubernetes cluster spanning two physical Proxmox hosts that keeps serving workloads when either physical machine fails or is powered off — with no manual intervention. When the failed host returns, it rejoins and resyncs automatically: etcd catches up, the kubelet reconnects, and Longhorn rebuilds storage replicas.

This is a full production-topology build (not k3s/k0s): external etcd with a third tiebreaker member on an always-on Linux box, a kube-vip floating VIP for the API server, and Longhorn replicated block storage. The final page proves the design with mandatory failure drills.

The HA claim only holds end to end: control-plane HA comes from etcd quorum and the VIP, but workload HA additionally requires the node labels, topology spread, and Longhorn anti-affinity configured in the final page. Skipping those leaves both replicas of an app on one physical host.

flowchart TB
  subgraph hostA [Physical Host A]
    cp1["cp1 VM: control plane + etcd1"]
    w1[worker-1 VM]
    w2[worker-2 VM]
  end
  subgraph hostB [Physical Host B]
    cp2["cp2 VM: control plane + etcd2"]
    w3[worker-3 VM]
    w4[worker-4 VM]
  end
  subgraph tiebreaker ["Always-On Linux Box / NAS"]
    etcd3["etcd3 (Docker container)"]
    qnetd[corosync-qnetd QDevice]
    nfs["NFS share: backups"]
  end
  vip["Floating VIP :6443 (kube-vip)"]
  vip --> cp1
  vip --> cp2
  cp1 <-->|"etcd peer 2380"| cp2
  cp1 <-->|"etcd peer 2380"| etcd3
  cp2 <-->|"etcd peer 2380"| etcd3
  hostA <-->|corosync| hostB
  hostA <-->|quorum vote| qnetd
  hostB <-->|quorum vote| qnetd
  w1 -.->|"Longhorn backup (daily)"| nfs
  w3 -.->|"Longhorn backup (daily)"| nfs
  • Either physical host fails → etcd retains quorum (2 of 3 members: surviving host + tiebreaker), the surviving control-plane VM keeps serving the API through the kube-vip VIP, workloads reschedule onto the surviving host’s workers, and Longhorn’s surviving replica keeps volumes available.
  • Failed host returns → its etcd member rejoins and catches up, the kubelet reconnects, and Longhorn resyncs replicas — with zero manual steps (verified in the drills page).

Residual risk: the tiebreaker box is a single point of failure for quorum tie-breaking and the backup target. If it is down while one host is also down, etcd loses quorum and the API goes read-only. Accepted for this lab; mitigating it needs a second always-on device.

DecisionChoiceWhy
TopologyExternal etcd (3 members)A third quorum member can run on a lightweight box without a third hypervisor
API HAkube-vip floating VIP (ARP mode)No external load balancer VM to own
Live storageLonghorn, replica count 2Replicated block storage across workers; survives a host loss
BackupsNFS share on the tiebreaker box, backup target onlyNever NFS for live PVs — it reintroduces a runtime SPOF
Control planesDedicated (tainted)Predictable failover behavior; untainting is a documented tradeoff
VM placementPinned to hosts, no live migration for CP/etcd VMsThe quorum math depends on etcd1/etcd2 staying on separate physical hosts
ProxmoxNo-subscription reposHomelab deployment

External vs stacked, one nuance: etcd1 and etcd2 run on the same VMs as the control planes to save resources, but the topology is still external — etcd runs as a systemd service that kubeadm does not manage, rather than as kubeadm’s stacked static pods. kubeadm is configured with an etcd.external stanza. See etcd and Control Plane Health for the stacked-vs-external tradeoffs.

Tiebreaker host requirements: any always-on Debian-based Linux box works (a Debian-capable NAS, a mini PC, a Pi). It needs apt for corosync-qnetd and Docker for etcd3. Appliance NAS operating systems without apt need qnetd run another way or a different tiebreaker host — and etcd3’s data directory must sit on SSD/NVMe, never spinning disk (etcd is fsync-latency sensitive).

Reference hardware: two hosts with ~16 GB RAM, 6-core CPU, and a 500 GB NVMe each.

VMvCPURAMDiskHost
cp1 (control plane + etcd1)24 GB60 GBA
worker-124 GB150 GBA
worker-224 GB150 GBA
cp2 (control plane + etcd2)24 GB60 GBB
worker-324 GB150 GBB
worker-424 GB150 GBB

RAM headroom policy: with three 4 GB VMs per 16 GB host, only ~4 GB remains for the Proxmox host itself. Do not enable memory ballooning or overcommit; keep ~4 GB reserved for the host. If a host shows memory pressure, trim the worker VMs to 3.5 GB rather than squeezing the hypervisor.

Disk budget per host: ~32 GB for Proxmox OS/local storage, 360 GB allocated to VMs, ~108 GB headroom (target ~20% free on the NVMe).

Everything lives on one flat L2 subnet — required for kube-vip ARP mode and simplest for etcd peers. Reserve static IPs before starting; the VIP must be outside your DHCP pool.

RoleExample IP (adapt to your subnet)
Proxmox Host A10.0.10.11
Proxmox Host B10.0.10.12
Tiebreaker box (etcd3 + QDevice + NFS)10.0.10.13
cp110.0.10.21
cp210.0.10.22
worker-1 … worker-410.0.10.3110.0.10.34
Kubernetes API VIP10.0.10.100
PortProtocolBetweenPurpose
6443TCPeveryone → VIP/CPsKubernetes API
2379TCPCPs → all etcd membersetcd client
2380TCPetcd members ↔ etcd membersetcd peer
10250TCPCPs ↔ all nodeskubelet API
5403TCPProxmox hosts → tiebreakercorosync-qnetd (QDevice)
8472UDPall nodes ↔ all nodesCilium VXLAN overlay
4240TCPall nodes ↔ all nodesCilium health checks
3260, 9500–9504TCPworkers ↔ workersLonghorn iSCSI + manager/engine
2049TCPworkers → tiebreakerNFS backup target

Every page in this series uses versions from this table.

ComponentVersion
Proxmox VE8.x
Guest OSDebian 12 (cloud image)
Kubernetes (kubeadm/kubelet/kubectl)v1.33.x
etcdv3.5.x
containerd1.7.x
kube-vipv0.8.x
Cilium1.17.x
Longhorn1.8.x

Work through the pages in order — each ends with verification steps the next page depends on.

  1. Proxmox Cluster and VMs — Install Proxmox on both hosts (no-subscription), form the 2-node cluster with a QDevice tiebreaker, provision the six VMs from a cloud-init template, and prep every node OS for kubeadm.
  2. External etcd and kubeadm Bootstrap — Generate etcd TLS certs, bring up the 3-member etcd cluster (including the Docker member on the tiebreaker), install kube-vip pre-init, and bootstrap both control planes against the VIP.
  3. Workers, CNI, and VIP Failover — Join the four workers with host-zone labels, install Cilium, and validate VIP failover behavior.
  4. Longhorn and Failure Drills — Install Longhorn with cross-host replica anti-affinity, configure daily NFS backups, and run the mandatory power-off drills for both hosts.