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. kubectlconfigurado con permisos de administrador del clúster y Helm 3 instalado en tu equipo.- Acceso SSH con un usuario con
sudoen 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
Notasi tus discos de datos también son
sd*y los gestionas con multipath, ajusta la expresión para excluir solo los dispositivos que no sean tuyos.
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.
Importantesi tu clúster ya tenía una StorageClass predeterminada (k3s instala
local-path), ahora tendrás dos. Quita la marca a la antigua conkubectl patch storageclass local-path -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'.
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.
