Skip to main content

Cluster Kubernetes con K3s en Homelab

· 10 min read
David Puziol Prata
DevSecOps at @ dLocal

Llega una hora en que uno se cansa de instalar el cluster Kubernetes con Kind, hasta porque Kind no fue hecho para ser un cluster permanente. Es óptimo para pruebas locales, pero no escala, no actualiza fácil y no guarda estado entre reinstalaciones. Si quieres subir una nueva versión de Kubernetes, tienes que destruir todo, reinstalar y restaurar los backups — un gran dolor de cabeza.

Cuando queremos mantener varias herramientas ejecutándose, probar integraciones o usar GitOps de verdad, lo ideal es tener un cluster persistente, que puedas formatear el host si necesitas, pero mantener el cluster en pie o reerguirlo rapidito. Ahí entra el K3s.

alt text

El K3s es una distribución ligera y simplificada de Kubernetes, creada por Rancher, pensada especialmente para ambientes de edge, IoT, homelabs y hasta producción en clusters menores. Se destaca por ser fácil de instalar (un único binario), consumir menos recursos y venir con varias optimizaciones incorporadas, como el uso del containerd en lugar de Docker y la inclusión de componentes esenciales como el traefik y flannel (que pueden ser desactivados si prefieres personalizar). Otra gran ventaja es que K3s reduce la complejidad del setup tradicional de Kubernetes, sin perder compatibilidad con APIs y herramientas del ecosistema. Ideal para quien quiere agilidad sin abrir mano de la robustez.

¿Por qué escogí el K3s?

  • Es ligero y optimizado para ambientes pequeños, ideal para homelab o hasta producción en edge.
  • Ya viene con batería incluida: containerd, flannel, traefik (opcional), servicelb, metrics-server e incluso kubectl.
  • Instalación ridículamente simple: un script resuelve todo, inclusive añadiendo workers.
  • Actualizaciones automáticas (opcional, pero disponibles).

Menos consumo de memoria: ideal para VMs con 2~4 GB de RAM.

¿Cuál sistema operativo en el Host?

Escogí el Ubuntu Server y no otras opciones como Arch, Fedora CoreOS, o Bottlerocket por algunos motivos bien prácticos:

🟢 Ubuntu Server:

  • Documentación clara y fácil de encontrar solución (Stack Overflow salva).
  • Compatible con prácticamente todo en el ecosistema Linux.
  • Actualización simple con apt — sin sorpresas.
  • Comunidad gigantesca: cualquier error, alguien ya sufrió por ti.
  • La versión mínima es ligera lo bastante para el K3s, sin perder practicidad.

🔴 Bottlerocket?

  • Cerrado demasiado. Sin apt, sin shell, sin paciencia.
  • Difícil de debuguear, exige una mentalidad "GitOps o nada".
  • Óptimo en EKS, pero un infierno en homelab o VM.

🟠 Arch?

  • Para quien gusta de vivir en el límite.
  • Control total, pero mantenimiento constante y riesgo de romper las cosas.
  • No combina con ambientes que piden estabilidad.

🟡 Fedora CoreOS?

  • Arquitectura inmutable, enfocada en GitOps y seguridad.
  • Óptimo para producción con CI/CD bien definido.
  • ¿Pero para homelab? Es subir cuesta con patineta — funcional, pero trabajoso.

Instalación e Infraestructura

Creé dos VMs en mi Unraid Server (Mi servidor para HomeLab) ambas utilizando el Ubuntu 24.04 LTS con apenas el openssh-server instalado durante el setup y sin LVM group.

MáquinaFunciónCPURAMDiscoIPUsuarioLinux
master-1Control-plane2 vCPU3GB30 GB10.0.0.11ubuntuUbuntu Server 24.04 LTS
worker-1Worker node2 vCPU4GB60 GB10.0.0.12ubuntuUbuntu Server 24.04 LTS

alt text

💡 Consejo: fijar los IPs en el DHCP de tu router para evitar dolor de cabeza después. Usa un Ip de tu franja de red. Como uso una red 10.0.0.0/24 entonces voy a utilizar los Ips entre 10.0.0.11 y 10.0.0.15 para montar el cluster completo en el futuro.

alt text

Acceso SSH

Copia tu llave SSH pública para las máquinas para facilitar el acceso:

ssh-copy-id [email protected]
ssh-copy-id [email protected]

Preparación de las Máquinas

En ambas las máquinas vamos a actualizar los paquetes, el sistema operativo e instalar algunas herramientas útiles. Para acceder las máquinas usa ssh [email protected].

sudo apt update && sudo apt upgrade -y
sudo apt dist-upgrade -y
sudo apt-get dist-upgrade
# Iptables es necesario
sudo apt install -y curl vim net-tools bash-completion dnsutils lsof iptables iptables-persistent ebtables arptables

# Añadiendo los módulos para cargar en la inicialización
echo ip_tables | sudo tee -a /etc/modules-load.d/k3s.conf
echo nf_conntrack | sudo tee -a /etc/modules-load.d/k3s.conf
# Forzando los módulos
sudo modprobe ip_tables
sudo modprobe nf_conntrack

sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy
sudo update-alternatives --set arptables /usr/sbin/arptables-legacy

Toda esa stack del iptables es necesaria para el calico, el CNI que vamos a utilizar.

¿Por qué usar iptables con Calico en nuestro cluster Kubernetes en vez del nftables o eBPF que son mucho más modernos?

Porque no necesitamos esas tecnologías más recientes. Nuestro cluster es simple, dirigido para estudio y homelab, donde estabilidad y compatibilidad son prioridad. El iptables es maduro, ampliamente soportado y atiende perfectamente sin complicación. Entonces, para ese escenario, es el mejor costo-beneficio. No ganaríamos performance suficiente que valga el esfuerzo.

Además de eso, instalamos arptables y ebtables porque complementan el iptables para controlar el tráfico en capas específicas de la red:

arptables cuida del filtro de paquetes ARP (capa 2, dirección MAC), esencial para redes locales.

ebtables manipula el tráfico Ethernet, importante para políticas más granulares en bridges e interfaces virtuales usadas en el cluster.

Esas herramientas juntas garantizan que el Calico tenga el control completo del tráfico, desde la capa 2 hasta la 4, manteniendo la seguridad y el aislamiento sin necesitar de soluciones más complejas.

Instalando el K3s en el Master (sin CNI)

El K3s viene con Flannel como CNI por defecto, pero yo prefiero el Calico. El Flannel es simple, pero limita algunas cosas como network policies. El Calico da más control, permite seguridad entre pods, y puede hasta usar eBPF. Entonces vamos a instalar el K3s desactivando el CNI padrón:

ubuntu@master-1:~$ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--flannel-backend=none" sh -

Ahora, aplicamos el manifest del Calico (sin usar el Operator, pues no vamos a escalar eso tanto al punto de justificar).

ubuntu@master-1:~$ sudo kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml

Preparando acceso externo al cluster

En el master-1:

ubuntu@master-1:~$ sudo cp /etc/rancher/k3s/k3s.yaml /home/ubuntu/k3s.yaml
ubuntu@master-1:~$ sudo chown ubuntu:ubuntu /home/ubuntu/k3s.yaml
# Esa etapa es solo para adelantar el acceso al token que vamos a necesitar después.
ubuntu@master-1:~$ sudo cat /var/lib/rancher/k3s/server/node-token > ~/token
ubuntu@master-1:~$ cat token
K108xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx::server:xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Solo confirmando
ubuntu@master-1:~$ sudo kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-1 Ready control-plane,master 69m v1.32.5+k3s1

En tu máquina personal copia el config del master para ~/.kube/config. Recordando que tenemos que tener el kubectl para acceder ese cluster.

# Creando el directorio caso no exista
mkdir -p ~/.kube
# Copiando el config
scp [email protected]:/home/ubuntu/k3s.yaml ~/.kube/config
# El config viene con el ip 127.0.0.1 que es el acceso al cluster localmente, necesitamos cambiar para apuntar al master.
sed -i 's/127.0.0.1/10.0.0.11/' ~/.kube/config

❯ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-1 Ready control-plane,master 72m v1.32.5+k3s1

Añadiendo el Worker

Listo ya tenemos un cluster, pero solo con el master. Este proceso para añadir un worker sirve para cuantos workers quieras añadir.

En el worker-1, para acceder ssh [email protected].

ubuntu@worker-1:~$ curl -sfL https://get.k3s.io | \
K3S_URL=https://10.0.0.11:6443 \
K3S_TOKEN="K108xxxxxxxxxxxxxxxxx::server:xxxxxxxxxxxxxxxxx" \
sh -
❯ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-1 Ready control-plane,master 74m v1.32.5+k3s1
worker-1 Ready <none> 67m v1.32.5+k3s1

Ajustando

¿Qué tenemos en el cluster?

alt text

  • Traefik
  • Metric Server
  • Calico

Tenemos una situación específica que no me agrada. El traefik (responsable por el ingress) ejecutándose en el master también. Eso sucede pues el K3s puede ejecutar solamente con un node master sin ningún worker.

Necesito aislar el master. No me gusta la idea del traefik en el master.

# Vamos a colocar un taint en él
❯ kubectl taint nodes master-1 node-role.kubernetes.io/master=:NoSchedule

# Al reiniciar el deploy del traefik garantizamos que no irá a ejecutar dentro del master.
kubectl rollout restart deployment -n kube-system traefik

# Edita el daemonset removiendo todo el bloque toleration
kubectl edit daemonsets -n kube-system svclb-traefik-dee52a6c

# tolerations:
# - effect: NoSchedule
# key: node-role.kubernetes.io/master
# operator: Exists
# - effect: NoSchedule
# key: node-role.kubernetes.io/control-plane
# operator: Exists
# - key: CriticalAddonsOnly
# operator: Exists

El calico es necesario ejecutar en el master y el metric server es un servicio crítico entonces vamos a dejarlo en el master sin problemas.

Actualizando el K3s

¿Y para actualizar? Cuando instalamos fue utilizada la versión 1.32, pero ya tenemos la 1.33. Es solo confirmar en la página de release del K3s.

Es importante esa vez no instalar el Traefik. Incluso que reinstales con --disable traefik, el Traefik continúa ahí, pues ya está en el cluster, pero si no evitamos la instalación irá a reinstalar y tendremos que nuevamente remover las tolerations.

En el master:

ubuntu@master-1:~$ sudo systemctl stop k3s
ubuntu@master-1:~$ curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="v1.33.1+k3s1" INSTALL_K3S_EXEC="--disable traefik" sh -
ubuntu@master-1:~$ sudo systemctl start k3s

En cada worker:

ubuntu@worker-1:~$ sudo systemctl stop k3s-agent
ubuntu@worker-1:~$ curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="v1.33.1+k3s1" K3S_URL="https://10.0.0.11:6443" K3S_TOKEN="K108xxxxxxxxxxxxxxxxx::server:xxxxxxxxxxxxxxxxx" sh -

Y todo actualizado.

❯ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-1 Ready control-plane,master 5h29m v1.33.1+k3s1
worker-1 Ready <none> 5h22m v1.33.1+k3s1

Consideraciones Finales

Incluso ejecutando todo en el mismo hardware (Unraid), quiero expandir el cluster con más dos nodes para probar escenarios de failover, tolerancia a fallas y deploys distribuidos. No es una infra "de producción", sino un laboratorio para aprender a mantener el carro ejecutándose mientras cambiamos las ruedas.

En el k3s es un poco diferente del Kubernetes "puro" porque el k3s es todo empaquetado en un binario solo — incluyendo el kube-apiserver, controller-manager, scheduler, etcd.

Para mexer en el kube-apiserver en el k3s, las opciones son medio limitadas, pero podemos pasar flags personalizadas vía INSTALL_K3S_EXEC o en el archivo de configuración del k3s que podemos crear en /etc/rancher/k3s/config.yaml. Este archivo es leído para completar el INSTALL_K3S_EXEC.

También puedes editar el archivo /etc/systemd/system/k3s.service (en el master) para pasar flags extras para el k3s. Por ejemplo, para pasar flags para el kube-apiserver, usa la flag --kube-apiserver-arg, tipo:

##--kube-apiserver-arg=<flag>=<value>
--kube-apiserver-arg=authorization-mode=RBAC,Node

Creando un archivo de configuración por ejemplo en tendríamos

# Desactiva el Traefik
disable:
- traefik

# Pasa argumentos para el kube-apiserver para mejorar seguridad
kube-apiserver-arg:
- "anonymous-auth=false" # Desabilita acceso anónimo
- "authorization-mode=RBAC" # Usa RBAC para autorización
- "enable-admission-plugins=NodeRestriction,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota"
- "secure-port=6443" # Puerto seguro (default)
- "tls-cert-file=/etc/rancher/k3s/ssl/k3s-server.crt"
- "tls-private-key-file=/etc/rancher/k3s/ssl/k3s-server.key"

Para mexer en el kube-apiserver en el k3s, el modo padrón es pasar flags customizadas vía kube-apiserver-arg en el archivo de config del k3s o en la línea de comando del sistema. Después reinicia el servicio.

Teniendo un archivo de configuración vamos a seguir actualizaciones con un comando más simple.