Kubernetes Helm MetalLB and Longhorn

Kubernetes series (Article 3)

After my write-up on Kubernetes Cluster Installation (Article 2), I began exploring several critical components that every Kubernetes cluster should include, especially in a homelab or bare-metal setup. These components are not optional but essential for building a resilient, manageable, and production-like environment.

In this article, I’ll cover three of those core components:

1. Helm – Kubernetes Package Manager

Helm is often called the “apt” or “yum” of the Kubernetes world. It’s a package manager that simplifies the deployment, upgrade, and management of applications inside your cluster. Rather than manually defining and applying multiple YAML files, Helm lets you install complex applications like Portainer, Prometheus, or Longhorn with a single command. It also supports version control and rollback, making your deployments more reliable and maintainable.

2. MetalLB – Load Balancing for Bare-Metal Clusters

In cloud environments like AWS, GCP, or Azure, Kubernetes integrates seamlessly with native cloud LoadBalancers. But in self-hosted or on-prem setups (like my Proxmox-based homelab), there is no built-in way for Kubernetes to assign external IPs to services of type. LoadBalancer.

That’s where MetalLB comes in.

It acts as a load balancer implementation for bare-metal clusters, allowing you to expose Kubernetes services to your local network using confirmed IP addresses from a predefined pool, such as:

apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: LoadBalancer
  ports:
    - port: 80
  selector:
    app: my-app

Without MetalLB, the LoadBalancer The type of service would be unusable in a non-cloud environment, and it would never get an external IP. With MetalLB, services like this can be reached from anywhere on your local network using IPs like 192.168.80.20–30.

3. Longhorn – Distributed Persistent Storage

Kubernetes doesn’t provide persistent storage out of the box, especially in bare-metal deployments. Pods are ephemeral, and once they’re rescheduled or a node fails, any local data is lost.

Longhorn solves that by offering a distributed block storage solution for Kubernetes. It provides highly available Persistent Volumes (PVs) that remain intact even when pods are restarted, rescheduled, or when a node crashes.

When a pod (such as a WordPress site, Nextcloud, or a database) makes a PersistentVolumeClaim, Longhorn:

  • Creates a replicated storage volume across multiple nodes
  • Mounts the volume to the requesting pod
  • Automatically handles failover and volume recovery
  • Offers snapshots and backup options, including AWS S3 or NFS

Installing Helm

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh # Run To exicute the script
Bash

This downloads and installs the latest Helm binary. You can then confirm it’s working with:

helm version
Bash

MetalLB Setup – LoadBalancer for Bare-Metal Clusters

Kubernetes clusters running in cloud environments can use built-in cloud LoadBalancers (like AWS ELB or Azure LB). However, on bare-metal or homelab setups like Proxmox, Kubernetes has no way to assign external IPs to Service objects of type LoadBalancer.

MetalLB fills that gap, giving your services external IPs within your local network.

Deploy MetalLB

Add the MetalLB Helm repository:

helm repo add metallb https://metallb.github.io/metallb
helm repo update
Bash

Install MetalLB via Helm

helm upgrade --install metallb metallb/metallb \
  --namespace metallb-system \
  --create-namespace
Bash

Configure IP Address Pool

Create a file called metallb-config.yaml And define an IP range MetalLB can use to assign to services:

---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.80.20-192.168.80.30
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2adv
  namespace: metallb-system
YAML

Then apply the configuration:

kubectl apply -f metallb-config.yaml
Bash

Important: To avoid conflicts, ensure this IP range is within your VLAN and not part of your DHCP pool in Pfsence.

Longhorn Setup – Distributed Persistent Storage for Kubernetes

Longhorn is a cloud-native, distributed block storage system for Kubernetes. It provides reliable Persistent Volumes (PVS) that can survive pod failures, rescheduling, and even node crashes, making it essential for databases, stateful apps, or file-based workloads.

Install Longhorn with Helm

Add the Longhorn Helm chart and install it:

helm repo add longhorn https://charts.longhorn.io
helm repo update
kubectl create namespace longhorn-system
helm install longhorn longhorn/longhorn --namespace longhorn-system
Bash

Fix iSCSI Issues on All Nodes

Longhorn depends on the open-iscsi for volume mounting. Without it, Longhorn pods may fail with CrashLoopBackOff errors.

Run the following on every node:

sudo apt install -y open-iscsi
Bash

Expose Longhorn UI (Optional)

By default, the Longhorn UI is only accessible within the cluster. You can expose it using MetalLB by patching the longhorn-frontend service:

kubectl patch svc longhorn-frontend -n longhorn-system \
  -p '{"spec": {"type": "LoadBalancer"}}'
Bash

Access the Longhorn Web UI

After patching, use this command to get the external IP assigned to the UI:

kubectl -n longhorn-system get svc longhorn-frontend
Bash

Or list everything in the namespace:

kubectl -n longhorn-system get all
Bash

Look for the EXTERNAL-IP in the output. Open that IP in your browser to access the Longhorn dashboard.


Securely Exposing Longhorn UI via Traefik with Basic Auth

This guide explains how to expose the Longhorn UI through Traefik using an IngressRoute, TLS via cert-manager, and basic authentication middleware. This is important because Longhorn itself does not provide built-in authentication.

Step 1: Create longhorn-values.yaml

Customise resource settings for Longhorn components:

defaultSettings:
  defaultReplicaCount: 3

longhornManager:
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 250m
      memory: 256Mi

longhornDriver:
  deployer:
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 250m
        memory: 256Mi

csi:
  attacher:
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 250m
        memory: 256Mi
  provisioner:
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 250m
        memory: 256Mi
  resizer:
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 250m
        memory: 256Mi
  snapshotter:
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 250m
        memory: 256Mi
  plugin:
    kubeletRootDir: /var/lib/kubelet
    resources:
      requests:
        cpu: 30m
        memory: 30Mi
      limits:
        cpu: 100m
        memory: 100Mi

longhornUI:
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 250m
      memory: 256Mi
YAML

Step 2: Create IngressRoute

Create longhorn-ingressroute.yaml:

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: longhorn-ui
  namespace: longhorn-system
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`longhorn.yourdomain.uk`)
      kind: Rule
      services:
        - name: longhorn-frontend
          namespace: longhorn-system
          port: 80
      middlewares:
        - name: longhorn-auth   # Remove if not using auth
  tls:
    secretName: traefik-tls
YAML

Apply it:

kubectl apply -f longhorn-ingressroute.yaml
Bash

Step 3: Create a Certificate for Longhorn

Create longhorn-certificate.yaml:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: traefik-tls
  namespace: longhorn-system
spec:
  secretName: traefik-tls
  commonName: longhorn.yourdomain.uk
  dnsNames:
    - "longhorn.yourdomain.uk"
  issuerRef:
    name: cloudflare
    kind: ClusterIssuer
  duration: 2160h # 90 days
  renewBefore: 168h # 7 days
YAML

Apply:

kubectl apply -f longhorn-certificate.yaml
Bash

Step 4: Enable Basic Auth Middleware

Create longhorn-middleware.yaml:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: longhorn-auth
  namespace: longhorn-system
spec:
  basicAuth:
    secret: authsecret-longhorn
YAML

Apply it:

kubectl apply -f longhorn-middleware.yaml
Bash

Step 5: Create Auth Secret

Create longhorn-middleware-secret.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: authsecret-longhorn
  namespace: longhorn-system
type: Opaque
data:
  users: c2Fuamh1bWE6JDJ5J # base64 encoded "username:hashed-password"
YAML

Generate it with:

htpasswd -nbB sanju yourpassword | base64
Bash

Apply:

kubectl apply -f longhorn-middleware-secret.yaml
Bash

Step 6: Restart or Upgrade Longhorn

After making changes, restart Longhorn UI or upgrade the release:

helm upgrade longhorn longhorn/longhorn \
  -n longhorn-system \
  -f longhorn-values.yaml
Bash

Or restart only the UI:

kubectl rollout restart deployment longhorn-ui -n longhorn-system
Bash

You can now access Longhorn securely:

https://longhorn.yourdomain.uk

With Traefik TLS and optional basic authentication in place.

This completes the setup for Helm, MetalLB, and Longhorn, three essential building blocks for running stable, production-like workloads in your homelab Kubernetes environment.

Back to top arrow