Guía Definitiva: Monta un Cluster k3s en tu Homelab para Kubernetes Ligero
Por qué este post
Tengo un homelab con tres nodos físicos en un armario del sótano: un mini-PC x86, una vieja workstation Dell y una Raspberry Pi 5 que aprovecho como agent. Durante años lo administré con docker-compose y un script de systemd que reiniciaba los contenedores cuando se caían. Funcionaba, hasta que dejó de funcionar: el día que necesité migrar tres servicios a la vez entre dos máquinas, descubrí que mi “orquestación casera” era un teatro de operaciones manuales disfrazado de automatización. k3s me dio lo que me faltaba: un plano de control real con scheduling, self-healing y rolling updates, pero con la huella de un solo binario que cabe en cualquier hardware barato.
Esta guía no es una receta para instalar k3s en un solo nodo y jugar con kubectl run nginx --image=nginx. Eso lo cubrimos en otro artículo. Aquí vamos a montar un cluster real con alta disponibilidad, ingress productivo, certificados automáticos, observabilidad, GitOps y plan de disaster recovery. Si terminas este post completo, vas a tener un cluster que aguanta la caída de un servidor entero sin perder workloads, que se actualiza solo desde Git y que te avisa antes de que el disco se llene.
TL;DR — Lo que vas a tener al final
TL;DR — Lo que vas a tener al final
- Tiempo de setup: 4-6 horas la primera vez, 1 hora para clonar el patrón en un cluster nuevo.
- Requisitos: 3 nodos Linux x86_64 con 4 GB de RAM mínimo (8 GB recomendado), systemd, conectividad de red entre ellos y un balanceador TCP (HAProxy, keepalived o un router MikroTik).
- Resultado: cluster k3s v1.37 con 3 servers en HA (etcd embebido), Traefik como ingress, cert-manager con Let’s Encrypt, MetalLB para IPs reales en tu LAN, ArgoCD para GitOps, Prometheus + Grafana + Loki para observabilidad.
- Troubleshooting: si un nodo se queda en
NotReady, revisajournalctl -u k3sen ese nodo ykubectl describe node <nombre>desde el plano de control.
Por qué k3s y no kubeadm, microk8s o k0s
El ecosistema Kubernetes tiene cuatro distribuciones que se promocionan como “ligeras” y todas resuelven problemas distintos. Antes de instalar, vale la pena entender qué compra cada una para que tu homelab no termine siendo una mezcla rara.
k3s (CNCF Sandbox, ahora bajo SUSE) es un solo binario de ~70 MB que combina API server, scheduler, controller-manager, kubelet, containerd, Flannel, CoreDNS, Traefik y un ingress local. Lo desarrolló Rancher Labs originalmente y lo mantienen ahora el equipo de SUSE y la comunidad. Es la distribución más usada en producción edge-computing y homelabs porque la instalación es literalmente un comando curl -sfL ... | sh -. La pega: por defecto usa SQLite como datastore, lo que obliga a HA con embedded etcd (también incluido) si quieres más de un server.
kubeadm es la opción oficial upstream, pero exige que tú montes a mano el balanceador, el runtime de contenedores, la red CNI y el datastore. Más flexible, más verboso. Para homelab es disparar con cañones: si quieres aprender cómo funciona Kubernetes por dentro, kubeadm es la mejor clase; si quieres un cluster productivo rápido, no.
microk8s (Canonical) usa snaps, lo que en servidores Debian/Ubuntu sin snapd es un problema. Trae addons por clic (microk8s enable gpu) pero la integración con systemd fuera de Ubuntu es tormentosa.
k0s (Mirantis) es el competidor más serio: distribución zero-deps, un solo binario, opciones de HA, y un modo “control plane” para correr todo en un solo proceso. La comunidad es más pequeña y la documentación, aunque buena, está un paso por detrás de k3s en tutoriales homelab.
Para homelab serio en hardware variado con HA real, k3s gana por documentación, madurez y por tener el ecosistema de herramientas (ArgoCD, cert-manager, MetalLB, Traefik) ya documentado contra él. Versión actual estable al cierre de este artículo: v1.37.1+k3s1 (publicada el 30 de septiembre de 2026), basada en Kubernetes 1.37.1, con etcd 3.7.1-k3s3 y Traefik 3.7.13 incluidos.
Elige hardware y diseña la red antes de instalar nada
El error más caro que veo en homelabs es comprar tres máquinas iguales, conectarlas al switch y empezar a instalar k3s sin haber decidido qué nodos van a ser servers y qué nodos van a ser agents. En k3s la diferencia importa: un server corre el API server, el scheduler, el controller-manager y (con embedded etcd) el consensus. Un agent solo corre kubelet y el proxy. Si pierdes todos los servers, el cluster se va con ellos aunque los agents sigan vivos.
El plano de hardware mínimo
Para un homelab que aguante cargas reales, la matriz que mejor me ha funcionado tras varios años:
- 3 servers: 4 CPU x86_64, 8 GB RAM, 80 GB SSD. Cualquier mini-PC tipo Intel N100, Beelink EQ12 o un refurbished Dell OptiPlex 3060 sirve. Los SSD son obligatorios: k3s hace muchas escrituras a disco por el datastore etcd, y un HDD te va a matar latencias en el consensus.
- 2+ agents: aquí puedes meter lo que tengas, incluyendo Raspberry Pi 5 (8 GB), siempre que sean arm64. El binario k3s detecta la arquitectura en el install.sh.
- 1 balanceador TCP: antes de los servers necesitas un VIP (virtual IP) en el puerto 6443 que reparte a los 3 servers. Opciones: HAProxy + keepalived en dos de los servers, un MikroTik con reglas mangle, o un router pfSense con HAProxy integrado.
El plano de red
k3s usa por defecto Flannel con backend VXLAN en el rango 10.42.0.0/16 para pods y 10.43.0.0/16 para servicios. Estos rangos NO deben chocar con tu LAN. Si tu red de casa es 192.168.1.0/24, todo OK. Si usas 10.0.0.0/8 corporativo, cambia los rangos en /etc/rancher/k3s/config.yaml con --cluster-cidr y --service-cidr antes del primer arranque. Más adelante, MetalLB anunciará IPs del rango de tu LAN para los servicios tipo LoadBalancer.
Reserva nombres DNS en tu router o servidor DNS interno: k3s-01.tulocal.lan, k3s-02.tulocal.lan, k3s-03.tulocal.lan, y un k3s-api.tulocal.lan que apunte al VIP. Si más adelante montas cert-manager con ACME DNS-01, este DNS local te sirve para emisiones de certificados sin exponer nada a internet.
Instala el primer server con embedded etcd
El install.sh oficial descarga el binario, lo instala en /usr/local/bin/k3s, crea el servicio systemd k3s.service y arranca el cluster. Para HA hay que pasar tres flags: --cluster-init en el primer server, y después --server + --datastore-endpoint en los otros dos. Empieza siempre por el primero.
# En k3s-01, como root
curl -sfL https://get.k3s.io | sh -s - server
--cluster-init
--tls-san=k3s-api.tulocal.lan
--tls-san=192.168.5.20
--write-kubeconfig-mode=644
# Verifica que el servicio está activo
systemctl status k3s --no-pager
journalctl -u k3s -f
El flag --write-kubeconfig-mode=644 hace el kubeconfig legible para tu usuario sin sudo. --tls-san añade nombres alternativos al certificado del API server, lo que es obligatorio si más adelante accedes al cluster desde otro host por nombre DNS o IP. Sin esto, kubectl se queja de “certificate is valid for …, not k3s-api.tulocal.lan”.
Cuando arranque, copia el token del primer server: vive en /var/lib/rancher/k3s/server/node-token. Lo vas a necesitar para unir los otros servers y los agents.
cat /var/lib/rancher/k3s/server/node-token
# Ejemplo: K10c2a4d::server:abcd1234...
Desde tu laptop con el kubeconfig (descárgalo del server con scp root@k3s-01:/etc/rancher/k3s/k3s.yaml ~/.kube/config y edita la IP del API server a tu VIP), verifica que el nodo está listo:
kubectl get nodes -o wide
# NAME STATUS ROLES AGE VERSION
# k3s-01 Ready control-plane,master 1m v1.37.1+k3s1
kubectl get pods -A
# Todo en Running. Si algo está Pending, revisa los eventos: describe el pod.
Une los otros dos servers y los agents
Para los otros servers usa el mismo install.sh pero con --server y el endpoint del datastore embedded etcd. No pases --cluster-init en estos; ese flag solo va en el primero.
# En k3s-02 y k3s-03
curl -sfL https://get.k3s.io | sh -s - server
--server https://k3s-01.tulocal.lan:6443
--token "K10c2a4d::server:abcd1234..."
--tls-san=k3s-api.tulocal.lan
--tls-san=192.168.5.20
Después de unos 30 segundos los tres servers deberían aparecer como control-plane. Comprueba el estado del quorum de etcd:
kubectl -n kube-system exec etcd-k3s-01 -- etcdctl
--cacert=/etc/ssl/certs/ca-certificates.crt
--cert=/var/lib/rancher/k3s/server/tls/etcd/client.crt
--key=/var/lib/rancher/k3s/server/tls/etcd/client.key
endpoint status --cluster -w table
Si la columna “RAFT INDEX” está sincronizada en los tres y IS LEADER=true solo en uno, el cluster está sano. Si algún nodo dice “unhealthy”, revisa la red entre los servers: el puerto 2379/2380 debe estar abierto en todos.
Para los agents (Raspberry Pi, máquinas spare, etc.), el install.sh es similar pero sin --server:
# En cada agent
curl -sfL https://get.k3s.io | sh -s - agent
--server https://k3s-api.tulocal.lan:6443
--token "K10c2a4d::server:abcd1234..."
Storage persistente: de local-path a una opción seria
k3s trae local-path-provisioner por defecto, que crea PersistentVolumes en /var/lib/rancher/k3s/storage del nodo donde corre el pod. Para homelab esto vale para bases de datos pequeñas, pero tiene una limitación seria: si el pod se mueve a otro nodo, los datos no lo siguen. Para Postgres, MinIO o cualquier cosa que te importe, necesitas un storage que sobreviva al nodo.
Tres opciones ordenadas por complejidad:
- Longhorn (Rancher): storage distribuido por bloques, réplicas sincronizadas entre nodos. Instálalo con
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.10.0/deploy/longhorn.yaml. Ocupa ~1 GB por nodo y necesita al menos 3 nodos para réplicas. - NFS subdir external provisioner: si tienes un NAS Synology o un servidor NFS, monta el provisioner y úsalo como default StorageClass. Más simple que Longhorn pero punto único de fallo si el NAS muere.
- OpenEBS: similar a Longhorn pero con más modos (Jiva, cStor, RawFile). Útil si quieres tener workloads stateful de prueba sin hardware dedicado.
Para homelab que ya tiene NAS, NFS es imbatible por coste cero. Para homelab que quiere aprender storage distribuido serio, Longhorn. La documentación de Longhorn contra k3s está muy madura.
Ingress, MetalLB y cert-manager: el stack de red productiva
Por defecto k3s trae Traefik como ingress en el namespace kube-system y ServiceLB (klipper-lb) que asigna IPs de un pool interno. Para que un servicio tipo LoadBalancer tenga una IP real de tu LAN, instala MetalLB. Para certificados automáticos con Let’s Encrypt, cert-manager.
MetalLB en modo capa 2
MetalLB necesita un pool de IPs que NO estén en uso por tu DHCP. Reserva un rango (por ejemplo 192.168.5.100-192.168.5.150) y aplícalo:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml
# Crea el pool y la advertisement (ajusta el rango a tu LAN)
kubectl apply -f - <<EOF
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: lan-pool
namespace: metallb-system
spec:
addresses:
- 192.168.5.100-192.168.5.150
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: lan-l2
namespace: metallb-system
spec:
ipAddressPools:
- lan-pool
EOF
Cuando crees un Service type: LoadBalancer, MetalLB le asignará una IP del pool y la anunciará por ARP en tu LAN. Es transparente para tu router y para los clientes.
cert-manager con Let’s Encrypt
cert-manager observa los recursos Ingress y solicita certificados automáticos. Para dominios reales con DNS público (Cloudflare, Route53, etc.), usa el solver DNS-01 con un provider. Para servicios internos sin dominio público, usa el solver HTTP-01 con un ingress expuesto.
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml
# Crea un ClusterIssuer para Let's Encrypt staging primero (más permisivo)
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: traefik
EOF
Una vez que veas que los certificados staging se emiten, cambia el server a https://acme-v02.api.letsencrypt.org/directory para los certificados de producción. Los staging tienen rate limit más alto pero el navegador los marca como no confiables.
GitOps con ArgoCD: el cluster se actualiza solo desde Git
Cuando tienes más de tres servicios en el cluster, mantener los manifests a mano se convierte en deuda técnica. ArgoCD reconcilia el estado del cluster contra un repositorio Git: tú haces commit, ArgoCD aplica. Si algo se rompe, ArgoCD revierte al estado bueno conocido.
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v3.0.0/manifests/install.yaml
# Expón el UI por NodePort o Ingress
kubectl patch svc argocd-server -n argocd -p '{"spec":{"type":"NodePort","ports":[{"port":443,"nodePort":30443}]}}'
# Obtén la password inicial
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
Estructura típica de repo GitOps:
infra-gitops/
├── apps/
│ ├── my-app/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── ingress.yaml
│ └── postgres/
│ └── statefulset.yaml
├── bootstrap/
│ ├── metallb.yaml
│ ├── cert-manager.yaml
│ └── argocd-apps.yaml
└── README.md
Cada app es un directorio con sus manifests; el ApplicationSet de ArgoCD descubre el árbol y crea una Application por app. Cuando haces push a main, ArgoCD sincroniza automáticamente (configurable con automated.selfHeal: true).
Observabilidad: Prometheus, Grafana y Loki
El kube-prometheus-stack (Prometheus + Alertmanager + Grafana preconfigurada con dashboards para Kubernetes) y el chart de Loki para logs son la combinación estándar. Instálalos con Helm 3 y en cinco minutos tienes visibilidad de CPU, memoria, red, logs y eventos.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
kubectl create namespace monitoring
helm install kube-prom prometheus-community/kube-prometheus-stack
-n monitoring
--set grafana.adminPassword=cambia-esto
--set prometheus.prometheusSpec.retention=15d
helm install loki grafana/loki
-n monitoring
--set loki.persistence.enabled=true
--set loki.persistence.size=20Gi
Después configura un Ingress para Grafana, añade el datasource de Loki, y dale una vuelta a los dashboards oficiales (ID 315 para cluster, 6417 para pods). Las alertas por defecto de kube-prometheus-stack te avisan de nodos NotReady, PVCs casi llenos y pods en CrashLoopBackOff.
Backup, restore y plan de actualizaciones
k3s tiene un sistema de snapshots etcd integrado. El subcomando k3s etcd-snapshot crea un snapshot del estado del cluster y lo guarda en /var/lib/rancher/k3s/server/db/snapshots. Configúralo con un cron que mande los snapshots a un destino fuera del cluster.
# Snapshot manual
k3s etcd-snapshot save --name manual-pre-upgrade
# Listar snapshots
k3s etcd-snapshot list
# Restore (parada total del cluster, ojo)
systemctl stop k3s
k3s server --cluster-resolve
--cluster-init
--etcd-snapshot=/var/lib/rancher/k3s/server/db/snapshots/<nombre>
Para backup automatizado, k3s corre etcd-snapshot cada 5 horas por defecto y retiene 5 snapshots. Ajusta esto en /etc/rancher/k3s/config.yaml:
cat >> /etc/rancher/k3s/config.yaml <<EOF
etcd-snapshot-schedule-cron: "0 */6 * * *"
etcd-snapshot-retention: "10"
etcd-snapshot-dir: "/var/lib/rancher/k3s/server/db/snapshots"
EOF
systemctl restart k3s
Y monta un rsync/cron que copie /var/lib/rancher/k3s/server/db/snapshots/ a tu NAS cada noche. El restore desde un snapshot es cuestión de minutos, pero solo lo necesitas cuando el cluster está completamente roto.
Para actualizaciones, k3s publica nuevas versiones cada 2-3 meses. El proceso es trivial: curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.37.1+k3s1 sh - en cada server, de uno en uno, esperando a que el primero vuelva a Ready antes de tocar el segundo. Los agents se actualizan automáticamente cuando el server lo hace, salvo que configures --node-upgrade-confirm.
Checklist de sysadmin senior antes de producción
Antes de declarar el cluster “productivo”, recorre esta lista:
- Quórum de etcd verificado: los 3 servers responden como
isLeader=trueoisLearner=trueenetcdctl endpoint status. - Backup automatizado: cron manda snapshots a un destino fuera del cluster y haces restore a un cluster vacío cada 3 meses para validar.
- Ingress con HTTPS válido: cert-manager emite staging + production, los certificados se renuevan a 30 días del vencimiento.
- Monitoring con alertas: Prometheus tiene reglas activas, Alertmanager manda a Telegram/Slack/email, y alguien lee las alertas.
- GitOps sincronizado: ArgoCD tiene al menos una app declarando los workloads, el repo está protegido por branch protection, los manifests pasan un linter (kubeconform o kubescape).
- Acceso SSH con clave, no password:
PermitRootLogin prohibit-password,PasswordAuthentication noen todos los nodos. - Firewall con default deny: solo puertos 22, 6443 (API), 80, 443 abiertos al mundo; 2379/2380 solo entre los servers.
- Documentación en Git: README con la matriz de hardware, decisiones de arquitectura, procedimientos de restore y contactos de emergencia.
Si cumples estos ocho puntos, tienes un cluster de homelab que puede correr servicios reales para tu familia o tus proyectos durante años sin dramas. No es un cluster de producción hyperscaler, pero tampoco lo necesita ser: es un homelab serio que te enseña Kubernetes como lo vas a usar cuando trabajes en producción.
Preguntas frecuentes
¿Cuánta RAM necesito realmente para 3 servers?
Para 3 servers con etcd embebido y workloads ligeros, 4 GB por nodo es el suelo técnico. Para correr Postgres + unas pocas apps con margen, 8 GB por nodo es lo realista. El cluster usa ~600-900 MB de RAM solo para el plano de control en cada server, así que con 4 GB estás justo.
¿Puedo mezclar nodos x86 y ARM en el mismo cluster?
Sí. k3s detecta la arquitectura en el install.sh y descarga el binario correcto. La arquitectura del cluster (lo que kubectl ve) es transparente: puedes tener servers x86 y agents ARM (Raspberry Pi 5, por ejemplo) sin configuraciones especiales. El único requisito es que el binario de cada nodo coincida con su hardware.
¿Qué pasa si pierdo dos de los tres servers?
El cluster se cae. etcd requiere mayoría de quorum (2 de 3), así que si pierdes 2 servers, el tercer server no puede formar mayoría y entra en read-only. Para recuperarlo necesitas reconstruir al menos uno de los perdidos desde un snapshot, o restaurar el cluster completo con k3s server –cluster-resolve apuntando al snapshot. Por eso el backup automatizado fuera del cluster es crítico.
¿k3s sirve para producción real o solo homelab?
Sirve para producción. Lo usan edge-computing despliegues, IoT industrial, y clusters en sitios remotos donde kubeadm no encaja por footprint. Empresas como SUSE, Bosch, Siemens y Rancher (antes de SUSE) tienen clusters k3s en producción. Las limitaciones reales son el datastore embebido (SQLite para single-node, etcd para HA) y la dependencia de Flannel como CNI por defecto.
¿Cómo migro un cluster k3s existente a una versión mayor de Kubernetes?
El proceso recomendado es levantar un cluster paralelo con la versión nueva, mover workloads con kubectl get/apply, validar, y decomisar el viejo. No hay upgrade in-place entre versiones mayores (1.x a 1.x+1) soportado oficialmente. Para versiones menores (1.37.0 a 1.37.1) sí hay upgrade in-place con INSTALL_K3S_VERSION, de nodo en nodo, esperando Ready entre cada uno.
