K3s en Proxmox: un clúster de Kubernetes de nivel producción en tu homelab
Mucha gente duda en llevar Kubernetes a su homelab. Un K8s completo se siente exagerado para unos cuantos servicios autoalojados, y el mantenimiento de un clúster que tocas una vez por semana parece un mal negocio. K3s sobre VMs de Proxmox cambia esa cuenta por completo.
K3s viene en un solo binario de 70 MB. Pasa todas las pruebas de conformidad de Kubernetes. Funciona cómodamente en nodos con 1 CPU y 512 MB de RAM (aunque vas a querer más para cargas reales). Lo mantiene Rancher/SUSE y tiene desarrollo activo. Y si lo combinas con Proxmox como hipervisor, obtienes aislamiento a nivel de VM, snapshots antes de actualizar y recreación fácil de nodos, todo en hardware común.
Esta guía te lleva a montar un clúster K3s real de varios nodos en Proxmox: tres VMs, almacenamiento persistente con Longhorn, ingress con Traefik y acceso remoto con Tailscale. Nada de setups de juguete.
Por qué K3s y no Kubernetes completo (y por qué no simplemente Docker Compose)
Si corres servicios en tu homelab, Docker Compose funciona muy bien hasta que deja de funcionar. Los límites típicos: no hay actualizaciones progresivas, no hay reprogramación automática según el estado de salud, hay que manejar los certificados SSL a mano por servicio, no se aplican límites de recursos y las definiciones de las cargas no son portables entre máquinas.
K3s resuelve todo eso. Y a diferencia de Kubernetes completo:
- No requiere etcd por defecto (usa SQLite en clústeres pequeños; puedes agregar etcd embebido para alta disponibilidad)
- Elimina APIs heredadas y en fase alfa que agregan superficie de mantenimiento
- Incluye Traefik, CoreDNS, Flannel y local-path provisioner: valores por defecto razonables, sin instalaciones extra
- Se actualiza con
k3s-upgrade-controller, aplicando un solo CRD
La contrapartida frente a k8s: K3s toma algunas decisiones por ti (Flannel como CNI por defecto, Traefik como ingress). Puedes cambiarlas, pero si necesitas redes con Calico/Cilium o vas detrás de lo más nuevo de sig-network, un k8s completo puede encajar mejor. ¿Para un homelab con cargas reales? K3s es la decisión correcta.
Requisitos de hardware y setup de referencia
No necesitas mucho. Un setup de referencia razonable es un mini PC Beelink EQ12 como host, con tres VMs de Proxmox:
| Nodo | Rol | vCPU | RAM | Disco |
|---|---|---|---|---|
| k3s-server-01 | Servidor (plano de control) | 2 | 4 GB | 32 GB |
| k3s-agent-01 | Agente (worker) | 2 | 4 GB | 64 GB |
| k3s-agent-02 | Agente (worker) | 2 | 4 GB | 64 GB |
Los discos de 64 GB de los agentes son los que Longhorn usará para el almacenamiento distribuido. Un NVMe Samsung 970 EVO Plus de 500 GB como disco principal del host de Proxmox da suficientes IOPS para que todo se sienta ágil.
Si quieres correrlo en hardware dedicado, el Minisforum MS-01 es una opción excelente para un clúster de 3 nodos: doble 2.5GbE, un puerto SFP+ y espacio para dos NVMe por unidad. El Beelink GTi14 Ultra es otra opción sólida si prefieres un solo host corriendo varias VMs.
En cuanto a RAM, si tu mini PC lo soporta, sube al menos a 32 GB de DDR5 para tener margen tanto para las VMs como para el sistema del host.
Paso 1: crea las VMs en Proxmox
Para las VMs de K3s usamos imágenes cloud de Ubuntu 24.04 LTS. Descárgala una vez en Proxmox y crea una plantilla:
# On the Proxmox host
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
# Create VM template
qm create 9000 --name ubuntu-2404-template --memory 2048 --net0 virtio,bridge=vmbr0
qm importdisk 9000 noble-server-cloudimg-amd64.img local-lvm
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
qm set 9000 --boot c --bootdisk scsi0
qm set 9000 --ide2 local-lvm:cloudinit
qm set 9000 --serial0 socket --vga serial0
qm set 9000 --agent enabled=1
qm template 9000
Luego clónala tres veces:
for i in 101 102 103; do
qm clone 9000 $i --name k3s-node-$i --full
qm set $i --memory 4096 --cores 2
qm resize $i scsi0 +30G # agents get +60G instead
done
Configura cloud-init en cada VM (llave SSH, IP fija, DNS):
qm set 101 --ciuser ubuntu --sshkeys ~/.ssh/authorized_keys --ipconfig0 ip=192.168.1.101/24,gw=192.168.1.1 --nameserver 192.168.1.1
Arráncalas y asegúrate de poder entrar por SSH antes de seguir.
Paso 2: instala K3s, primero el nodo servidor
En k3s-server-01 (192.168.1.101):
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --cluster-init --tls-san 192.168.1.101 --disable traefik --disable servicelb" sh -
Aquí desactivamos el Traefik y el ServiceLB integrados porque vamos a instalar Traefik con Helm para tener más control, y usaremos MetalLB para los servicios LoadBalancer en la red local.
Obtén el token del nodo para los agentes:
sudo cat /var/lib/rancher/k3s/server/node-token
Verifica que el servidor esté arriba:
sudo k3s kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# k3s-server-01 Ready control-plane,etcd,master 30s v1.32.x+k3s1
Paso 3: une los nodos agentes
En cada VM agente (reemplaza y la IP del servidor):
curl -sfL https://get.k3s.io | K3S_URL=https://192.168.1.101:6443 K3S_TOKEN=<TOKEN> sh -
De vuelta en el servidor, confirma que ambos agentes se unieron:
sudo k3s kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# k3s-server-01 Ready control-plane,etcd,master 5m v1.32.x+k3s1
# k3s-agent-01 Ready <none> 2m v1.32.x+k3s1
# k3s-agent-02 Ready <none> 1m v1.32.x+k3s1
Copia el kubeconfig a tu máquina local:
# On the Proxmox host or your workstation
scp ubuntu@192.168.1.101:/etc/rancher/k3s/k3s.yaml ~/.kube/config-k3s
# Edit the server URL: change 127.0.0.1 to 192.168.1.101
export KUBECONFIG=~/.kube/config-k3s
kubectl get nodes # works from your laptop now
Paso 4: almacenamiento persistente con Longhorn
El local-path provisioner integrado sirve para cargas sin estado, pero para cualquier cosa con base de datos o archivos, quieres Longhorn. Ofrece almacenamiento en bloques replicado entre tus nodos: si una VM agente se cae, tus datos sobreviven.
Instálalo con Helm:
helm repo add longhorn https://charts.longhorn.io
helm repo update
helm install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace --set defaultSettings.defaultReplicaCount=2
Espera a que todos los pods estén en Running:
kubectl -n longhorn-system get pods --watch
Define Longhorn como StorageClass por defecto:
kubectl patch storageclass local-path -p '{"metadata": {"annotations": {"storageclass.kubernetes.io/is-default-class": "false"}}}'
kubectl patch storageclass longhorn -p '{"metadata": {"annotations": {"storageclass.kubernetes.io/is-default-class": "true"}}}'
Para que el almacenamiento funcione bien, asegúrate de que cada nodo agente tenga ese volumen de disco extra que mencioné. Longhorn detecta /var/lib/longhorn automáticamente. Monta el disco extra de Proxmox en esa ruta:
# On each agent, after formatting the extra disk as ext4
sudo mkfs.ext4 /dev/sdb
echo '/dev/sdb /var/lib/longhorn ext4 defaults 0 2' | sudo tee -a /etc/fstab
sudo mount -a
El NVMe Western Digital Blue SN580 de 1 TB es una opción económica para los volúmenes de Longhorn: buen rendimiento secuencial y precio competitivo. El SSD SATA Samsung 870 EVO de 1 TB también funciona bien si tu Proxmox usa SATA en lugar de passthrough de NVMe.
Paso 5: ingress con Traefik y cert-manager
Instala Traefik con Helm usando un archivo de valores pensado para un homelab:
helm repo add traefik https://traefik.github.io/charts
helm repo update
cat << 'EOF' > traefik-values.yaml
deployment:
replicas: 1
service:
type: LoadBalancer
annotations:
metallb.universe.tf/loadBalancerIPs: "192.168.1.200"
additionalArguments:
- "--certificatesresolvers.letsencrypt.acme.email=contact@gilricardo.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/data/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
EOF
helm install traefik traefik/traefik --namespace traefik --create-namespace -f traefik-values.yaml
Para MetalLB (así los servicios de tipo LoadBalancer reciben IPs reales de tu red local):
helm repo add metallb https://metallb.github.io/metallb
helm install metallb metallb/metallb -n metallb-system --create-namespace
# Configure IP pool
kubectl apply -f - <<EOF
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: homelab-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.220
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: homelab-l2
namespace: metallb-system
EOF
Ahora cualquier servicio LoadBalancer recibe una IP real en tu rango 192.168.1.200-220, accesible desde cualquier equipo de tu red local.
Paso 6: acceso remoto con el operador de Tailscale
El operador de Tailscale para Kubernetes es una forma limpia de exponer servicios del clúster en Tailscale sin instalar un cliente VPN en cada dispositivo. Instálalo:
helm repo add tailscale https://pkgs.tailscale.com/helmcharts
helm repo update
helm upgrade --install tailscale-operator tailscale/tailscale-operator --namespace=tailscale --create-namespace --set-string oauth.clientId=<TAILSCALE_CLIENT_ID> --set-string oauth.clientSecret=<TAILSCALE_CLIENT_SECRET> --wait
Luego agrega la anotación a cualquier servicio que quieras exponer en tu tailnet:
apiVersion: v1
kind: Service
metadata:
name: my-app
annotations:
tailscale.com/expose: "true"
tailscale.com/hostname: "my-app-k3s"
spec:
type: LoadBalancer
...
El servicio recibe un hostname my-app-k3s en tu tailnet, accesible desde tu teléfono, tu laptop o cualquier dispositivo conectado a Tailscale. Sin redirección de puertos, sin DNS dinámico, sin dolores de cabeza con certificados para servicios internos.
Problemas comunes (ahórrate la depuración)
La E/S de disco de etcd importa más de lo que crees. Si el disco de la VM del plano de control es lento, etcd se vuelve inestable. Usa almacenamiento NVMe para la VM servidor. Un síntoma típico es que los comandos kubectl empiezan a colgarse de forma intermitente cuando la VM servidor está en un pool SATA lento.
Longhorn necesita nombres de nodo consistentes. Si recreas una VM y cambia el hostname, Longhorn se confunde sobre qué nodo tiene qué réplicas de volumen. Define los hostnames en cloud-init y no los cambies. Usa k3s-agent-01 y k3s-agent-02 como hostnames fijos, no el hostname por defecto ubuntu de cloud-init.
firewalld/ufw rompen la comunicación entre nodos. Los nodos de K3s se comunican en varios puertos (6443, 8472 UDP para Flannel VXLAN, 10250, etc.). Configura bien las reglas del firewall o (en un homelab) simplemente desactiva ufw en las VMs: sudo ufw disable.
Haz snapshots en Proxmox antes de cualquier actualización de K3s. Ejecuta qm snapshot antes de actualizar. Las actualizaciones de K3s suelen ir bien, pero tener un camino de vuelta atrás con un solo comando te cuesta 30 segundos.
Para conectar tus mini PCs con un switch administrable, el TP-Link TL-SG108E de 8 puertos es un switch barato que soporta VLANs si quieres aislar el tráfico del clúster. El Netgear GS308E es otra buena opción en el mismo rango.
Despliega tu primera carga real
Este es un ejemplo completo que despliega Gitea (Git autoalojado) en el clúster con un PVC de Longhorn:
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea
namespace: gitea
spec:
replicas: 1
selector:
matchLabels:
app: gitea
template:
metadata:
labels:
app: gitea
spec:
containers:
- name: gitea
image: gitea/gitea:latest
ports:
- containerPort: 3000
- containerPort: 22
volumeMounts:
- name: gitea-data
mountPath: /data
volumes:
- name: gitea-data
persistentVolumeClaim:
claimName: gitea-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-pvc
namespace: gitea
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 20Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gitea
namespace: gitea
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
spec:
rules:
- host: git.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gitea
port:
number: 3000
Aplícalo y Traefik se encarga del certificado TLS automáticamente. Longhorn replica el volumen /data en tus dos nodos agentes. Si un agente se cae, K3s reprograma el pod en el nodo que sobrevive y Longhorn sirve el volumen desde la réplica.
Esa es toda la historia de producción en un solo archivo YAML.
¿Vale la pena?
Si ya usas Proxmox, sí, sin duda. Montar K3s toma unas dos horas desde VMs nuevas hasta un clúster funcionando con almacenamiento e ingress. A partir de ahí, desplegar servicios nuevos es kubectl apply -f y obtienen TLS automático, límites de recursos, monitoreo de salud y agregación de logs sin esfuerzo extra.
La sobrecarga es real: solo Longhorn corre unos 10 pods por nodo y el plano de control consume ~1.5 GB de RAM en reposo. Pero en una máquina con 32 GB de RAM repartidos entre VMs, eso es poca cosa.
La ganancia más grande es que tu homelab ahora usa las mismas piezas que se usan en el trabajo. Cuando te toca depurar un problema de Kubernetes en el trabajo, puedes reproducirlo en local en minutos: no en un entorno de staging compartido, sino en hardware que controlas por completo.
Aviso: este artículo contiene enlaces de afiliado. Si compras a través de ellos, puedo ganar una pequeña comisión sin costo extra para ti.
¿Quieres algo así para tu negocio?
Instalo infraestructura privada y autoalojada en Proxmox: IA, automatización y herramientas internas en hardware que es tuyo, documentado y entregado. Ver el stack de IA privada →
¿No sabes qué necesitas? Agenda una llamada gratis.
