Beyond Local Storage: Building Fault-Tolerant Kubernetes Volumes with Longhorn

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

The 2:15 AM PagerDuty Alert: Why Local Storage Isn’t Enough

It was a Tuesday morning when my phone started blaring. A worker node in our production cluster—a standard 8-core bare metal instance—had suffered a complete hardware failure. On paper, Kubernetes is designed for this. I expected the scheduler to simply migrate my PostgreSQL and Redis pods to a healthy node.

The reality was different. My PostgreSQL pod was stuck in a Pending state for over ten minutes. The logs showed a dreaded error: pod has unbound immediate PersistentVolumeClaims. Because I was using local storage classes tied to that specific NVMe drive, the data was trapped on dead hardware. The “self-healing” promise of Kubernetes failed because the storage wasn’t mobile.

The Persistent Storage Paradox

Kubernetes does not natively manage data replication across nodes. If you rely on hostPath or local static volumes, your data exists on exactly one disk. If that disk dies, your application dies with it. For stateful microservices, this creates a single point of failure that negates the benefits of a distributed cluster.

I needed a storage layer that met four specific criteria:

  • Block Storage: High-performance, low-latency access for database workloads.
  • Synchronous Replication: Data must exist on at least three nodes simultaneously.
  • Low Operational Burden: A system that doesn’t require a dedicated storage engineer to maintain.
  • Disaster Recovery: Automated snapshots shipped to off-site S3 buckets.

Evaluating the Options

Cloud-native storage is a crowded market. I narrowed my search down to three main paths based on cost and complexity.

1. Managed Cloud Disks (AWS EBS, Azure Disk)

These are reliable but come with a “cloud tax.” AWS EBS gp3 volumes cost roughly $0.08 per GB-month. More importantly, detaching a volume from a failed node and reattaching it to a new one can take 60 to 120 seconds. That delay is often too long for high-availability requirements.

2. Rook and Ceph

Ceph is a powerhouse for enterprise-grade storage. However, it is notoriously resource-heavy. A typical Rook/Ceph setup requires at least 2GB of RAM per OSD (Object Storage Daemon). For a medium-sized cluster, the overhead often outweighs the benefits of the application itself.

3. Longhorn

Longhorn is an open-source project managed by SUSE. It turns the existing local storage of your nodes into a distributed block storage pool. It is lightweight, installs via a single Helm chart, and provides a highly intuitive dashboard. After running performance benchmarks, Longhorn emerged as the most balanced choice for our microservices architecture.

I have since moved our production workloads to this setup. The results have been rock-solid. Here is the process I used to build a resilient storage foundation.

Step 1: Node Preparation

Longhorn utilizes iscsiadm on the host to manage block devices. If this utility is missing, your volumes will fail to attach, leaving your pods in a permanent pending state. This is the most frequent cause of installation failure.

Install the necessary dependencies on all worker nodes:

sudo apt update
sudo apt install open-iscsi -y
sudo systemctl enable --now iscsid

Before proceeding, verify your environment using the official Longhorn check script. It catches configuration issues before they become production bugs:

curl -sSfL https://raw.githubusercontent.com/longhorn/longhorn/v1.5.x/scripts/environment_check.sh | bash

Step 2: Deploying via Helm

Helm is the most reliable way to manage Longhorn lifecycles. It simplifies upgrades and configuration changes. Start by adding the repository:

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

For a standard 3-node cluster, I set the default replica count to three. This ensures that even if two nodes fail, your data remains accessible on the third.

helm install longhorn longhorn/longhorn \
  --namespace longhorn-system \
  --create-namespace \
  --set persistence.defaultClassReplicaCount=3

Step 3: Monitoring via the Dashboard

Longhorn includes a powerful UI that visualizes data health. You can see exactly which nodes hold which replicas. To access it without an Ingress, use a quick port-forward:

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

Visit http://localhost:8080. You will see a breakdown of your nodes, disk space utilization, and volume health. It makes troubleshooting storage issues much faster than parsing raw logs.

Step 4: Implementing Resilient PVCs

With the engine running, we can define a PersistentVolumeClaim (PVC). Longhorn creates a StorageClass named longhorn by default. Here is a manifest for a 10GB PostgreSQL volume:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 10Gi

Once applied, Longhorn carves out the space and replicates it across three nodes. If the node hosting your database crashes, Kubernetes re-schedules the pod elsewhere. Longhorn then instantly attaches the local data replica on that new node.

Step 5: Off-site S3 Backups

Replication protects against hardware failure, but it does not protect against human error. If a script accidentally deletes a database, that deletion is replicated instantly. You need off-site backups to be truly safe.

I use AWS S3 for this, though MinIO or Backblaze B2 work just as well. First, store your credentials in a secret:

kubectl create secret generic longhorn-s3-secret \
  --from-literal=AWS_ACCESS_KEY_ID=YOUR_KEY \
  --from-literal=AWS_SECRET_ACCESS_KEY=YOUR_SECRET \
  -n longhorn-system

In the Longhorn settings, set the Backup Target to s3://your-bucket-name@region/. I recommend configuring a Recurring Job. A snapshot every 2 hours and a full backup every 24 hours provides a solid balance between safety and storage costs.

Lessons from Production

Running Longhorn at scale revealed several practical insights that aren’t in the basic documentation:

Monitor CPU Overhead

Every Longhorn volume launches a dedicated instance-manager pod. Each one consumes roughly 0.1 CPU cores and 100MiB of RAM. If you create hundreds of tiny 1GB volumes, the cumulative overhead can degrade node performance. Consolidate storage where possible.

Avoid Disk Exhaustion

Longhorn stops scheduling replicas once a disk hits 90% capacity. I set my Storage Overprovisioning Tolerance to 150% in the settings. This allows for temporary growth, but you must monitor host disk usage closely to avoid read-only locks.

Use Maintenance Mode

Never reboot a node without preparation. Use the Longhorn UI to “Disable Scheduling” on the target node first. This prevents the system from unnecessarily rebuilding replicas during a brief maintenance window.

Final Thoughts

Fault-tolerant storage is the backbone of any serious Kubernetes deployment. By moving from local volumes to Longhorn, I turned a fragile environment into a resilient one. Node failures are now minor events rather than 2 AM emergencies. If you need a balance of high performance and easy maintenance, Longhorn is the most practical path forward.

Share: