Longhorn es un sistema de almacenamiento de bloques distribuido para Kubernetes, mantenido por la CNCF. Convierte los discos locales de los nodos en volúmenes persistentes con réplicas en varios nodos, snapshots y backups a un almacenamiento S3, todo gestionado con recursos de Kubernetes y una interfaz web. En este tutorial prepararás los nodos Ubuntu 24.04, instalarás Longhorn con Helm, crearás un volumen replicado de prueba y configurarás backups diarios a un bucket S3.

Requisitos previos

Para seguir esta guía necesitas:

  • Un clúster Kubernetes 1.25 o superior con al menos tres nodos worker con Ubuntu 24.04 LTS (por ejemplo, tres VPS de CubePath con kubeadm, k3s o RKE2).
  • Al menos 2 vCPU y 4 GB de RAM por nodo, y espacio libre en /var/lib/longhorn (el directorio de datos por defecto). Un disco o partición dedicada en formato ext4 o XFS es lo recomendable en producción.
  • kubectl configurado con permisos de administrador del clúster y Helm 3 instalado en tu equipo.
  • Acceso SSH con un usuario con sudo en cada nodo.
  • Para el paso de backups: un bucket S3 (AWS o compatible, como MinIO o Wasabi) y un par de claves de acceso con permisos de lectura y escritura sobre él.

Paso 1: Preparar los nodos

Longhorn expone cada volumen al nodo mediante iSCSI, así que todos los nodos que vayan a montar volúmenes necesitan el iniciador open-iscsi. Los volúmenes ReadWriteMany se sirven por NFSv4, por lo que también hace falta nfs-common.

Ejecuta en cada nodo:

sudo apt update
sudo apt install -y open-iscsi nfs-common

Activa el demonio iscsid y carga el módulo del kernel iscsi_tcp, dejándolo configurado para que se cargue en cada arranque:

sudo systemctl enable --now iscsid
sudo modprobe iscsi_tcp
echo iscsi_tcp | sudo tee /etc/modules-load.d/iscsi_tcp.conf

Comprueba que el servicio está activo y el módulo cargado:

systemctl is-active iscsid
lsmod | grep iscsi_tcp
active
iscsi_tcp              24576  0

Ubuntu trae multipathd activo, y este servicio puede apropiarse de los dispositivos que crea Longhorn, lo que provoca errores de montaje del tipo already mounted or mount point busy. Si no usas multipath para otros discos, excluye los dispositivos sd* editando su configuración:

sudo nano /etc/multipath.conf

Añade este bloque al final del archivo:

blacklist {
    devnode "^sd[a-z0-9]+"
}

Aplica el cambio:

sudo systemctl restart multipathd

Paso 2: Instalar Longhorn con Helm

Añade el repositorio oficial del chart:

helm repo add longhorn https://charts.longhorn.io
helm repo update

Crea un archivo de valores con los ajustes que querrás revisar en producción:

nano longhorn-values.yaml
persistence:
  # Crea la StorageClass "longhorn" y la marca como predeterminada
  defaultClass: true
  defaultClassReplicaCount: 3
  defaultFsType: ext4
  reclaimPolicy: Delete

defaultSettings:
  # Réplicas por defecto para volúmenes creados desde la interfaz
  defaultReplicaCount: 3
  # Directorio de datos en cada nodo
  defaultDataPath: /var/lib/longhorn/
  # Porcentaje mínimo de disco libre para seguir programando réplicas
  storageMinimalAvailablePercentage: 25

Instala el chart en el namespace longhorn-system:

helm install longhorn longhorn/longhorn \
  --namespace longhorn-system \
  --create-namespace \
  --values longhorn-values.yaml

La instalación tarda unos minutos, porque Longhorn despliega un manager, un driver CSI y un gestor de instancias en cada nodo. Espera a que el DaemonSet del manager esté listo:

kubectl -n longhorn-system rollout status daemonset/longhorn-manager
daemon set "longhorn-manager" successfully rolled out

Comprueba que todos los pods están en Running y que Longhorn ve los tres nodos como programables:

kubectl -n longhorn-system get pods
kubectl -n longhorn-system get nodes.longhorn.io
NAME       READY   ALLOWSCHEDULING   SCHEDULABLE   AGE
worker-1   True    true              True          3m
worker-2   True    true              True          3m
worker-3   True    true              True          3m

Si algún nodo aparece con READY a False, revisa el paso 1 en ese nodo.

Paso 3: Acceder a la interfaz web

La interfaz de Longhorn no tiene autenticación propia, así que no la expongas a Internet sin un Ingress con autenticación. Para administrarla basta con un port-forward desde tu equipo:

kubectl -n longhorn-system port-forward svc/longhorn-frontend 8080:80

Abre http://localhost:8080 en el navegador. En el panel verás la capacidad total del clúster, los nodos y los volúmenes. Detén el port-forward con Ctrl+C cuando termines.

Paso 4: Crear un volumen replicado y probarlo

Crea un namespace de pruebas y un PVC que use la StorageClass longhorn:

kubectl create namespace demo-longhorn

Crea el manifiesto con el PVC y un pod que escribe en él:

nano pvc-demo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: datos-demo
  namespace: demo-longhorn
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: escritor
  namespace: demo-longhorn
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "date >> /datos/log.txt && sleep 3600"]
      volumeMounts:
        - name: datos
          mountPath: /datos
  volumes:
    - name: datos
      persistentVolumeClaim:
        claimName: datos-demo

Aplica el manifiesto y espera a que el pod arranque:

kubectl apply -f pvc-demo.yaml
kubectl -n demo-longhorn wait --for=condition=Ready pod/escritor --timeout=180s

Comprueba que el PVC está enlazado y que el archivo se ha escrito en el volumen:

kubectl -n demo-longhorn get pvc datos-demo
kubectl -n demo-longhorn exec escritor -- cat /datos/log.txt
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
datos-demo   Bound    pvc-5c1e8d0a-7b7e-4a1f-9f0e-2b8f3c1d9a41   2Gi        RWO            longhorn       40s

Por último, confirma que Longhorn ha creado tres réplicas en nodos distintos y que el volumen está sano:

kubectl -n longhorn-system get volumes.longhorn.io
kubectl -n longhorn-system get replicas.longhorn.io -o wide
NAME                                       DATA ENGINE   STATE      ROBUSTNESS   SCHEDULED   SIZE         NODE       AGE
pvc-5c1e8d0a-7b7e-4a1f-9f0e-2b8f3c1d9a41   v1            attached   healthy                  2147483648   worker-2   1m

ROBUSTNESS en healthy significa que todas las réplicas están sincronizadas. degraded indica que falta alguna y Longhorn la está reconstruyendo.

StorageClass con otra política de réplicas

Si quieres volúmenes más baratos para datos que ya se replican a nivel de aplicación (por ejemplo, un clúster de bases de datos), crea una StorageClass adicional con menos réplicas:

nano storageclass-longhorn-2r.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: longhorn-2r
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: "2"
  staleReplicaTimeout: "30"
  dataLocality: "best-effort"
  fsType: "ext4"
kubectl apply -f storageclass-longhorn-2r.yaml
kubectl get storageclass

Con dataLocality: best-effort, Longhorn intenta mantener una réplica en el mismo nodo que el pod, lo que reduce la latencia de lectura.

Paso 5: Configurar backups a S3

Los snapshots de Longhorn viven en los mismos discos que el volumen, así que no protegen ante la pérdida del clúster. Los backups sí: se copian a un almacenamiento externo y se pueden restaurar en otro clúster.

Crea un Secret con las credenciales del bucket. Sustituye los valores de ejemplo por los tuyos; AWS_ENDPOINTS solo es necesario con proveedores compatibles con S3 distintos de AWS:

kubectl -n longhorn-system create secret generic longhorn-s3-secret \
  --from-literal=AWS_ACCESS_KEY_ID='tu_access_key' \
  --from-literal=AWS_SECRET_ACCESS_KEY='tu_secret_key' \
  --from-literal=AWS_ENDPOINTS='https://s3.tu_proveedor.com'

Apunta el destino de backup predeterminado de Longhorn al bucket. El formato de la URL es s3://<bucket>@<región>/<ruta>/:

kubectl -n longhorn-system patch backuptargets.longhorn.io default --type merge \
  -p '{"spec":{"backupTargetURL":"s3://tu_bucket@eu-west-1/longhorn/","credentialSecret":"longhorn-s3-secret"}}'

Comprueba que Longhorn puede acceder al bucket:

kubectl -n longhorn-system get backuptargets.longhorn.io

La columna AVAILABLE debe mostrar true al cabo de unos segundos. Si muestra false, revisa el mensaje con kubectl -n longhorn-system describe backuptargets.longhorn.io default; suele ser una región, un endpoint o unas claves incorrectas.

Paso 6: Programar snapshots y backups automáticos

Un RecurringJob ejecuta tareas periódicas sobre los volúmenes. Los volúmenes que no tienen ningún job asignado explícitamente pertenecen al grupo default, así que asociar los jobs a ese grupo los aplica a todo el clúster.

nano recurring-jobs.yaml
apiVersion: longhorn.io/v1beta2
kind: RecurringJob
metadata:
  name: snapshot-cada-6h
  namespace: longhorn-system
spec:
  task: snapshot
  cron: "0 */6 * * *"
  groups:
    - default
  retain: 4
  concurrency: 2
---
apiVersion: longhorn.io/v1beta2
kind: RecurringJob
metadata:
  name: backup-diario
  namespace: longhorn-system
spec:
  task: backup
  cron: "0 2 * * *"
  groups:
    - default
  retain: 14
  concurrency: 2

Con esta configuración se guardan los últimos 4 snapshots locales (un día) y los últimos 14 backups diarios en S3.

kubectl apply -f recurring-jobs.yaml
kubectl -n longhorn-system get recurringjobs.longhorn.io
NAME               GROUPS        TASK       CRON          RETAIN   CONCURRENCY   AGE
backup-diario      ["default"]   backup     0 2 * * *     14       2             5s
snapshot-cada-6h   ["default"]   snapshot   0 */6 * * *   4        2             5s

Para probar el destino sin esperar a las 2:00, abre la interfaz web, entra en el volumen de prueba y pulsa Create Backup. Después lista los backups registrados:

kubectl -n longhorn-system get backups.longhorn.io

Un backup en estado Completed confirma que toda la cadena funciona. Para restaurarlo, ve a la sección Backup de la interfaz, selecciona el backup y usa Restore: Longhorn crea un volumen nuevo al que después puedes asociar un PV y un PVC desde la propia interfaz.

Paso 7: Supervisar Longhorn con Prometheus

El manager de Longhorn expone métricas en formato Prometheus en el puerto 9500 del servicio longhorn-backend. Compruébalo con un port-forward:

kubectl -n longhorn-system port-forward svc/longhorn-backend 9500:9500

En otra terminal:

curl -s http://localhost:9500/metrics | grep '^longhorn_volume_robustness'
longhorn_volume_robustness{node="worker-2",pvc="datos-demo",pvc_namespace="demo-longhorn",volume="pvc-5c1e8d0a-..."} 1

El valor de longhorn_volume_robustness es 1 para un volumen sano, 2 degradado y 3 en fallo, por lo que es la métrica natural para una alerta. Si usas Prometheus Operator, crea un ServiceMonitor que seleccione el servicio con la etiqueta app: longhorn-manager y el puerto manager.

Solución de problemas

El PVC se queda en Pending. Revisa los eventos con kubectl describe pvc <nombre> -n <namespace>. Lo más habitual es que no haya tres nodos programables con espacio suficiente para colocar las réplicas. Comprueba el espacio en la interfaz o con kubectl -n longhorn-system get nodes.longhorn.io -o yaml.

El pod no arranca con MountVolume.SetUp failed o mount point busy. Casi siempre es multipathd reclamando el dispositivo: aplica la exclusión del paso 1 en el nodo afectado. Si el error menciona iSCSI, comprueba que iscsid está activo y que el módulo iscsi_tcp está cargado.

Volumen en estado degraded. Longhorn reconstruye la réplica perdida automáticamente cuando hay un nodo disponible. Si no avanza, revisa los logs del manager:

kubectl -n longhorn-system logs -l app=longhorn-manager --tail=100

Mantenimiento de un nodo. Antes de reiniciar un nodo, vacíalo con kubectl drain <nodo> --ignore-daemonsets --delete-emptydir-data. Longhorn esperará a que los volúmenes se desconecten de forma limpia.

Conclusión

Tienes Longhorn funcionando como almacenamiento predeterminado del clúster, con volúmenes replicados en tres nodos, snapshots cada seis horas y backups diarios a S3 que sobreviven a la pérdida del clúster. Como siguientes pasos, prueba a restaurar un backup en un clúster de staging para validar tu plan de recuperación, configura alertas sobre longhorn_volume_robustness y, si necesitas volúmenes compartidos entre pods, prueba el modo de acceso ReadWriteMany.