Problema

Muchos entusiastas de homelab quieren practicar Kubernetes sin invertir en hardware dedicado. La configuración típica consiste en un host Proxmox que ejecuta varias VMs Ubuntu: una para el control‑plane y una o más para workers. El reto no es solo lanzar las VMs; es asegurarse de que la red interna, los requisitos de kubeadm y la persistencia de los componentes funcionen sin sorpresas. Cuando la red de Proxmox está configurada con puentes Linux y NAT, es fácil encontrarse con errores de “connection refused”, “node not ready” o problemas de sincronización de tiempo que hacen que el clúster nunca alcance estado “Ready”.

Causa

  1. Puente de red mal configurado – Proxmox permite varios tipos de interfaces (Linux Bridge, Open vSwitch, VLAN). Si el bridge no pasa tráfico entre las VMs o no está conectado a la red física, los nodos no pueden comunicarse con el API server.
  2. Direcciones IP estáticas vs DHCP – kubeadm genera certificados basados en la IP del control‑plane. Cambiar la IP después de kubeadm init invalida los certificados y los workers no pueden unirse.
  3. Desfase de tiempo – Los nodos sin NTP pueden presentar diferencias de varios segundos; los certificados TLS fallan y el kubelet se queda en NotReady.
  4. Swap activado – kubeadm aborta si el swap está activo; en entornos de prueba a veces se olvida desactivarlo.
  5. Módulos del kernel ausentes – Algunas configuraciones de Proxmox usan kernels personalizados que no incluyen br_netfilter o overlay, obligatorios para la red de pods.

Solución

1. Preparar la infraestructura en Proxmox

  • Crea un Linux Bridge (por ejemplo vmbr10) conectado a la NIC física que usarás para el tráfico del clúster.
  • Asigna a cada VM una IP estática dentro del mismo rango (p.ej. 192.168.100.10‑12) y habilita la opción “bridge firewall” solo si ya tienes reglas definidas.
  • Desactiva el swap en la plantilla Ubuntu antes de clonarla: swapoff -a && sed -i '/ swap / s/^/#/' /etc/fstab.

2. Configurar los nodos Ubuntu

En cada VM:

apt-get update && apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io
systemctl enable docker && systemctl start docker

cat <<EOF > /etc/modules-load.d/k8s.conf
br_netfilter
overlay
EOF
modprobe br_netfilter
modprobe overlay

cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sysctl --system

apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
  • Habilita NTP: apt-get install -y chrony && systemctl enable --now chrony.

3. Inicializar el control‑plane

En la VM designada como master, ejecuta:

kubeadm init --apiserver-advertise-address=192.168.100.10 --pod-network-cidr=10.244.0.0/16 --control-plane-endpoint=192.168.100.10
  • Copia el kubeconfig al usuario regular:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
  • Despliega una red de pods ligera (Flannel funciona sin requisitos extra):
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

4. Añadir los workers

En cada worker, usa la línea kubeadm join que kubeadm init imprimió, por ejemplo:

kubeadm join 192.168.100.10:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>

Si la línea se perdió, recupérala en el master con kubeadm token create --print-join-command.

5. Ajustes post‑instalación

  • Persistencia de la IP: bloquea la interfaz con netplan para evitar que DHCP cambie la dirección.
  • Firewall: abre puertos 6443 (API), 10250 (kubelet), 10255 (read‑only) y los rangos de la red de pods. En Proxmox, desactiva el firewall del bridge o crea reglas específicas.
  • Snapshots: toma un snapshot de cada VM después de que el nodo esté Ready. Facilita volver a un estado limpio si pruebas algo arriesgado.

Cuándo aplicar esta solución

  • Entornos de aprendizaje donde se dispone de un único host físico y se quiere simular un clúster multi‑node.
  • Pruebas de CI/CD que requieren un clúster real pero no justifican hardware dedicado.
  • Validación de add‑ons (Ingress, CSI, observabilidad) antes de migrar a producción.

No es adecuada cuando:

  • Se necesita alta disponibilidad del control‑plane; un solo master no tolera fallos.
  • El tráfico del clúster debe cruzar subredes distintas a la del bridge de Proxmox sin configuración adicional de routing.
  • Se busca rendimiento de producción; la sobrecarga de virtualización y la red NAT pueden ser cuellos de botella.

Código

# En el master
kubeadm init --apiserver-advertise-address=192.168.100.10 \
  --pod-network-cidr=10.244.0.0/16 \
  --control-plane-endpoint=192.168.100.10

# Copiar kubeconfig
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# Instalar Flannel
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# En cada worker
kubeadm join 192.168.100.10:6443 --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash>

Verificación

  1. En el master, kubectl get nodes debe listar 3 nodos con estado Ready.
  2. kubectl get pods -n kube-system debe mostrar todos los pods de Flannel y CoreDNS en Running.
  3. Desde cualquier worker, prueba la conectividad al API: curl -k https://192.168.100.10:6443/healthz.
  4. Verifica que los pods pueden comunicarse entre sí creando un deployment de prueba y ejecutando kubectl exec para ping a otro pod.

Notas adicionales

  • Tiempo de arranque: la primera vez que inicias los workers pueden tardar varios minutos en pasar de NotReady a Ready mientras el kubelet descarga imágenes del repositorio.
  • Versión de Docker vs containerd: kubeadm soporta ambos, pero si prefieres containerd, reemplaza la instalación de Docker por apt-get install -y containerd. Ajusta el archivo /etc/containerd/config.toml para usar el driver systemd.
  • Actualizaciones: cuando actualices Kubernetes, repite solo apt-get upgrade -y kubeadm kubelet kubectl y sigue el proceso de kubeadm upgrade. Los nodos VM pueden ser recreados desde snapshots para evitar problemas de versión.
  • Persistencia de datos: para practicar StatefulSets, crea discos virtuales adicionales y móntalos dentro de los workers; recuerda añadir la etiqueta node.kubernetes.io/disk si usas un provisionador dinámico.

Con esta guía puedes levantar rápidamente un clúster funcional en Proxmox, experimentar con recursos de Kubernetes y, lo más importante, evitar los errores típicos que aparecen cuando la red o el tiempo no están alineados. ¡A jugar con pods, servicios y operadores!