The Chicken-and-Egg Deadlock: Bootstrapping Kubernetes 1.29+ with kube-vip
Bootstrapping a highly available Kubernetes cluster on bare-metal usually involves setting up a Virtual IP (VIP) that floats between control plane nodes. My tool of choice for this is kube-vip. But recently, while bootstrapping a fresh Debian 13 cluster on Kubernetes 1.29+, I ran into a complete deadlock.
Impact
kubeadm inithung forever: the new highly available control plane never came up.- The cluster bootstrap was blocked until the VIP assignment was decoupled from
kube-vip.
Timeline
- Following the standard
kube-vipprocedure, I generated a static pod manifest in/etc/kubernetes/manifests/and rankubeadm init. The idea is that askubeadmbrings up the API server, thekube-vipstatic pod spins up alongside it and claims the VIP (192.168.1.50). kubeadm inithung, waiting for the control plane.- The
kube-viplogs repeated this error:
leases.coordination.k8s.io "plndr-svcs-lock" is forbidden: User "kubernetes-admin" cannot get resource "leases"
- A
403 Forbiddenfor the admin on a newly bootstrapping cluster pointed to an RBAC change in Kubernetes 1.29. - I bound the VIP at the OS level, re-ran
kubeadm init, and only then deployedkube-vip.
Root cause
Kubernetes 1.29 introduced a security change to how kubeadm generates the admin.conf file used during initialization. Historically, kubeadm granted the bootstrapping admin the system:masters superuser group, which bypasses all RBAC (Role-Based Access Control) checks. In 1.29+, that bypass was removed: kubeadm now relies entirely on standard RBAC roles to grant permissions.
This creates a chicken-and-egg problem:
kubeadmstarts the API server and tries to talk to it via the VIP (192.168.1.50).kube-viptries to claim the VIP sokubeadmcan reach it.- To claim the VIP,
kube-vipneeds to write a coordination lease to the API server. - Because the cluster is actively bootstrapping, the RBAC rules haven't been fully populated yet.
- Because RBAC isn't ready, and
system:mastersis gone,kube-vipis denied permission to create the lease (403 Forbidden). - Because
kube-vipfails, the VIP is never claimed. - Because the VIP is never claimed,
kubeadmcan't reach the API server, and hangs forever.
Actions
Separate the VIP assignment from the Kubernetes static pod lifecycle.
- Before running
kubeadm init, bind the VIP to the network interface at the OS level:
sudo ip addr add 192.168.1.50/24 dev eth0
- Run
kubeadm init. It talks to the API server over the VIP (handled by the OS), finishes bootstrapping, and fully populates the RBAC rules. - Once
kubeadm inithas completed, generate thekube-vipstatic pod manifest. Whenkube-vipboots, the RBAC rules exist, it writes its lease, and it takes over management of the VIP from the OS.
Lessons learned
- Security improvements often break established workflows in unexpected ways: read the release notes of a new Kubernetes minor version before bootstrapping with it.
- When a static pod throws a 403 during bootstrap, remember that in modern Kubernetes RBAC is king, and it isn't ready until the cluster is.