Ir al contenido
  1. Posts/

Monté un clúster Kubernetes con 3 ZimaBoard 2 y consume menos que una bombilla

Tabla de contenido

Tengo un problema con las ZimaBoard: cada vez que compro una, acabo comprando dos más. Empecé con una para probar, le puse K3s, y al mes siguiente tenía tres formando un clúster de Kubernetes conectado por Tailscale. Consume menos de 20W en total. Menos que la bombilla del baño.

Este tutorial es el registro completo de cómo lo monté. No me salto los errores ni las cosas que no funcionaron, porque cuando yo buscaba esto nadie contaba la parte fea. Y con ZimaOS, la parte fea es importante.

Si ya tienes el hardware y solo quieres saber si merece la pena, mira primero la review del ZimaBoard 2 donde cuento los meses previos a este clúster. Y si tienes dudas sobre si necesitas Kubernetes o Docker Compose, el post sobre cuándo usar cada uno te ayudará a decidir.

Qué vamos a montar
#

Un clúster K3s de 3 nodos (1 control plane + 2 workers), conectado a través de Tailscale. Los nodos pueden estar en ubicaciones diferentes. Vamos a instalar paneles de gestión (Rancher, Portainer, Kubernetes Dashboard) y ver qué pasa con el almacenamiento en ZimaOS.

Números reales de mi setup:

  • Consumo total idle: 15-20W (los 3 nodos)
  • Coste del hardware: unos 750€
  • Recursos: 12 cores, 46GB RAM, unos 34 pods en producción
  • Ruido: cero, son fanless

Requisitos previos
#

Hardware
#

NodoRolSpecs
zima3Control PlaneZimaBoard 2, Intel N150, 16GB RAM
zima1WorkerZimaBoard 2, Intel N150, 16GB RAM
zima2WorkerZimaBoard 2, Intel N150, 16GB RAM

Software
#

ZimaOS instalado en las 3 placas y Tailscale configurado y conectado en los 3 nodos.

IPs de Tailscale
#

Cada nodo tiene su IP dentro de la red Tailscale. En mi caso:

NodoIP Tailscale
zima3 (CP)100.64.1.10
zima1 (Worker)100.64.1.20
zima2 (Worker)100.64.1.30

Sustituye estas IPs por las que te asigne Tailscale a ti.


Antes de empezar: cómo funciona el almacenamiento en ZimaOS
#

Esto es lo primero que tienes que entender. ZimaOS no es Debian. Tiene una estructura de almacenamiento rara y si no la tienes en cuenta, vas a perder datos:

1
2
3
/         → 1.2GB (sistema inmutable, solo lectura)
/var      → tmpfs (RAM, ~7.7GB) ⚠️ SE PIERDE AL REINICIAR
/DATA     → 45GB en eMMC (persistente)

K3s guarda sus datos en /var/lib/rancher/, que está en RAM. Si no lo mueves a /DATA, se llena la RAM, los pods entran en “Evicted” por DiskPressure, y al reiniciar se pierde todo.

La solución es mover los datos a /DATA con un symlink. Lo veremos en el paso 2.4.


Paso 1: Preparar los nodos
#

1.1 Acceso SSH
#

Abre 3 terminales y conéctate a cada nodo:

1
2
3
ssh [email protected]   # zima3 (control plane)
ssh [email protected]   # zima1 (worker)
ssh [email protected]   # zima2 (worker)

1.2 Configurar hostnames
#

En ZimaOS los hostnames se configuran desde el panel web. Cada nodo tiene que tener su nombre correcto (zima1, zima2, zima3).

1.3 Añadir hosts de Tailscale
#

Ejecuta en los 3 nodos (con tus IPs):

1
2
3
4
5
sudo bash -c 'cat >> /etc/hosts << EOF
100.64.1.10 zima3
100.64.1.20 zima1
100.64.1.30 zima2
EOF'

Paso 2: Instalar K3s en el Control Plane (zima3)
#

2.1 Instalar K3s server
#

ZimaOS tiene /usr/local/bin en solo lectura, así que instalamos en /opt/bin:

1
2
3
4
5
6
7
sudo mkdir -p /opt/bin
curl -sfL https://get.k3s.io | sudo INSTALL_K3S_BIN_DIR=/opt/bin sh -s - server \
  --node-ip=100.64.1.10 \
  --advertise-address=100.64.1.10 \
  --flannel-iface=tailscale0 \
  --disable=traefik \
  --write-kubeconfig-mode=644

Qué hace cada flag:

  • INSTALL_K3S_BIN_DIR=/opt/bin directorio escribible en ZimaOS
  • --node-ip / --advertise-address la IP de Tailscale del nodo
  • --flannel-iface=tailscale0 la red overlay pasa por Tailscale
  • --disable=traefik lo quitamos para usar otro ingress después
  • --write-kubeconfig-mode=644 para no necesitar sudo con kubectl

2.2 Verificar instalación
#

1
2
export PATH=$PATH:/opt/bin
sudo kubectl get nodes

Deberías ver algo como:

1
2
NAME    STATUS   ROLES           AGE   VERSION
zima3   Ready    control-plane   43s   v1.34.3+k3s1

2.3 Guardar el token
#

1
sudo cat /var/lib/rancher/k3s/server/node-token

Copia este token. Lo necesitas para unir los workers.

2.4 Mover datos a /DATA (obligatorio)
#

Este paso no es opcional en ZimaOS:

1
2
3
4
5
6
sudo systemctl stop k3s
sudo mkdir -p /DATA/k3s
sudo mv /var/lib/rancher/k3s /DATA/k3s/
sudo ln -s /DATA/k3s/k3s /var/lib/rancher/k3s
sudo systemctl start k3s
sudo kubectl get nodes

Si después de reiniciar el nodo ves que no arranca, comprueba que el symlink sigue ahí. ZimaOS a veces recrea /var/lib/rancher como directorio vacío al reiniciar.


Paso 3: Unir los Workers (zima1 y zima2)
#

Puedes hacer los dos en paralelo.

3.1 Instalar K3s agent en zima1
#

1
2
3
4
5
6
7
sudo mkdir -p /opt/bin
curl -sfL https://get.k3s.io | sudo INSTALL_K3S_BIN_DIR=/opt/bin \
  K3S_URL=https://100.64.1.10:6443 \
  K3S_TOKEN=<TU_TOKEN_DEL_PASO_2.3> \
  sh -s - agent \
  --node-ip=100.64.1.20 \
  --flannel-iface=tailscale0

3.2 Instalar K3s agent en zima2
#

1
2
3
4
5
6
7
sudo mkdir -p /opt/bin
curl -sfL https://get.k3s.io | sudo INSTALL_K3S_BIN_DIR=/opt/bin \
  K3S_URL=https://100.64.1.10:6443 \
  K3S_TOKEN=<TU_TOKEN_DEL_PASO_2.3> \
  sh -s - agent \
  --node-ip=100.64.1.30 \
  --flannel-iface=tailscale0

3.3 Mover datos a /DATA en los workers
#

Igual que en el control plane. En cada worker:

1
2
3
4
5
sudo systemctl stop k3s-agent
sudo mkdir -p /DATA/k3s
sudo mv /var/lib/rancher/k3s /DATA/k3s/
sudo ln -s /DATA/k3s/k3s /var/lib/rancher/k3s
sudo systemctl start k3s-agent

En workers el servicio se llama k3s-agent, no k3s.

3.4 Verificar el clúster completo
#

Desde zima3:

1
sudo kubectl get nodes -o wide
1
2
3
4
NAME    STATUS   ROLES           AGE    VERSION        INTERNAL-IP
zima1   Ready    <none>          17s    v1.34.3+k3s1   100.64.1.20
zima2   Ready    <none>          3s     v1.34.3+k3s1   100.64.1.30
zima3   Ready    control-plane   2m9s   v1.34.3+k3s1   100.64.1.10

Tres nodos Ready. El clúster funciona sobre Tailscale.


Paso 4: Almacenamiento, lo que funciona y lo que no
#

Longhorn: no funciona en ZimaOS
#

Intenté instalar Longhorn (almacenamiento distribuido) y no va en ZimaOS. Detecta /var/lib/longhorn como tmpfs y el longhorn-driver-deployer entra en CrashLoopBackOff. No hay forma limpia de arreglarlo.

Si necesitas Longhorn, instala Debian o Ubuntu en las ZimaBoard en vez de ZimaOS. Yo acabé haciendo eso y funciona perfecto.

Lo que sí funciona: local-path
#

K3s trae local-path como StorageClass por defecto. Funciona para la mayoría de aplicaciones:

1
sudo kubectl get storageclass local-path

Es almacenamiento local, no distribuido. Para un homelab suele bastar.


Paso 5: Instalar Helm
#

Lo necesitas para instalar Rancher, Portainer y cualquier chart:

1
2
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | \
  sudo HELM_INSTALL_DIR=/opt/bin VERIFY_CHECKSUM=false bash

ZimaOS tiene /root en solo lectura, así que hay que configurar directorios alternativos para Helm:

1
2
3
4
export HELM_CACHE_HOME=/var/cache/helm
export HELM_CONFIG_HOME=/var/lib/helm
export HELM_DATA_HOME=/var/lib/helm/data
sudo mkdir -p $HELM_CACHE_HOME $HELM_CONFIG_HOME $HELM_DATA_HOME

A partir de aquí, usa siempre sudo -E con Helm para que coja estas variables.


Paso 6: Paneles de gestión
#

Probé tres: Rancher, Portainer y el Dashboard oficial de Kubernetes.

Opción A: Rancher (completo pero pesado)
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# cert-manager (requisito)
sudo kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml
sudo kubectl -n cert-manager get pods -w  # Espera a que estén Running

# nginx-ingress
sudo -E helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
sudo -E helm repo update
sudo -E helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.service.type=LoadBalancer \
  --kubeconfig /etc/rancher/k3s/k3s.yaml

# Rancher
sudo -E helm repo add rancher-latest https://releases.rancher.com/server-charts/latest
sudo -E helm repo update
sudo kubectl create namespace cattle-system
sudo -E helm install rancher rancher-latest/rancher \
  --namespace cattle-system \
  --set hostname=rancher.100.64.1.10.nip.io \
  --set bootstrapPassword=TuPasswordAqui \
  --set ingress.tls.source=rancher \
  --set replicas=1 \
  --kubeconfig /etc/rancher/k3s/k3s.yaml

Accede a https://rancher.100.64.1.10.nip.io (con tu IP).

Si da 404, es el problema del tmpfs otra vez. /var/lib/rancher-data está en RAM:

1
2
3
4
sudo mkdir -p /DATA/rancher-data
sudo rm -rf /var/lib/rancher-data
sudo ln -s /DATA/rancher-data /var/lib/rancher-data
sudo kubectl -n cattle-system delete pod -l app=rancher

Si el ingress no funciona, usa port-forward:

1
2
sudo kubectl -n cattle-system port-forward svc/rancher 8443:443 --address 0.0.0.0 &
# https://TU_IP:8443

Rancher consume unos 2GB de RAM. Ponle replicas=1 o te quedas sin espacio.

Opción B: Portainer (ligero, familiar si vienes de Docker)
#

1
2
3
4
5
6
7
8
9
sudo -E helm repo add portainer https://portainer.github.io/k8s/
sudo -E helm repo update
sudo kubectl create namespace portainer
sudo -E helm install portainer portainer/portainer \
  --namespace portainer \
  --set service.type=NodePort \
  --set service.nodePort=30777 \
  --set persistence.enabled=false \
  --kubeconfig /etc/rancher/k3s/k3s.yaml

Accede a https://TU_IP:30779

Unos 100MB de RAM. Detecta el clúster solo.

Opción C: Kubernetes Dashboard (minimalista)
#

El más ligero, unos 50MB:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sudo kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
sudo kubectl -n kubernetes-dashboard patch svc kubernetes-dashboard \
  -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "nodePort": 30443}]}}'

# Crear usuario admin
sudo kubectl -n kubernetes-dashboard create serviceaccount admin-user
sudo kubectl create clusterrolebinding admin-user \
  --clusterrole=cluster-admin \
  --serviceaccount=kubernetes-dashboard:admin-user
sudo kubectl -n kubernetes-dashboard create token admin-user --duration=87600h

Accede a https://TU_IP:30443 con el token.

Cuál elegir
#

PanelRAMPara quién
Rancher2GBGestión completa de varios clústeres
Portainer100MBSi vienes de Docker
K8s Dashboard50MBLo oficial, lo mínimo

Yo probé los tres y me quedé con Portainer para el día a día. Rancher lo abro cuando necesito algo más complejo.

Lo que NO funcionó: KubeSphere
#

Las imágenes de KubeSphere en Docker Hub están obsoletas. kubesphere/ks-installer:v3.2.1 no existe. No pierdas el tiempo.


Troubleshooting
#

Pods en “Evicted” o “DiskPressure”
#

El problema más común en ZimaOS. /var se llenó:

1
2
sudo kubectl describe node zima3 | grep -A5 "Conditions:"
df -h /var

Si ya moviste datos a /DATA, limpia pods fallidos:

1
2
sudo kubectl delete pods --all-namespaces --field-selector=status.phase=Failed
sudo crictl rmi --prune

Los nodos no se ven entre sí
#

1
2
3
tailscale ping zima1
tailscale ping zima2
tailscale ping zima3

Helm da “permission denied” o “read-only file system”
#

Tres cosas:

  1. Que tengas definidas las variables HELM_CACHE_HOME, HELM_CONFIG_HOME, HELM_DATA_HOME
  2. Que uses sudo -E (preserva las variables de entorno)
  3. Que pongas siempre --kubeconfig /etc/rancher/k3s/k3s.yaml

El resultado
#

1
2
3
4
NAME    CPU       RAM       Pods
zima1   4 cores   15.37Gi   11 (10%)
zima2   4 cores   15.37Gi   11 (10%)
zima3   4 cores   15.37Gi   12 (10.91%)

12 cores, 46GB RAM, 34 pods. Consumo: menos de 20W. Todo por Tailscale, sin puertos abiertos.

Lo más pesado fue pelear con ZimaOS (el tmpfs en /var, el filesystem en solo lectura, los paths de Helm). Una vez resuelto eso, K3s va igual que en cualquier otro Linux. Si vas en serio, plantéate poner Debian directamente en las ZimaBoard. Te ahorras los workarounds de ZimaOS y Longhorn funciona sin problema.

Pero si lo que quieres es aprender Kubernetes con hardware real, bajo consumo y sin ruido, tres ZimaBoard 2 con K3s es un setup difícil de mejorar por el precio.


Dónde comprar
#