Distributed Block Storage (Longhorn)

K3s comes with a default Local Path Provisioner that allows creating a PersistentVolumeClaim backed by host-based storage. This means the volume is using storage on the host where the pod is located. If the POD need to be started on a different node it won’t be able to access the data.

A distributed block storage is needed to handle this issue. With distributed block storage, the storage is decouple from the pods, and the PersistentVolumeClaim can be mounted to the pod regardless of where the pod is running.

Longhorn is a distributed block storage system for Kubernetes. Lightweight, reliable and easy-to-use can be used as an alternative to Rook/Cephs. It is opensource software initially developed by Rancher Labs supporting AMD64 and ARM64 architectures that can be easily integrated with K3S.

LongHorn Installation

Open-iscsi Requirement

LongHorn requires that open-iscsi package has been installed on all the nodes of the Kubernetes cluster, and iscsid daemon is running on all the nodes.

Longhorn uses internally iSCSI to expose the block device presented by the Longhorn volume to the kuberentes pods. So the iSCSI initiator need to be setup on each node. Longhorn, acting as iSCSI Target, exposes Longhorn Volumes that are discovered by the iSCSI Initiator running on the node as /dev/longhorn/ block devices. For implementation details see Longhorn enfine document.


Since all cluster nodes (node1-node5) have been already configured as iSCSI Initiators all pre-requisties are met.

Longhorn issues with Multipath

Multipath running on the storage nodes might cause problems when starting Pods using Longhorn volumes (“Error messages of type: volume already mounted”).

To prevent the multipath daemon from adding additional block devices created by Longhorn, Longhorn devices must be blacklisted in multipath configuration. See Longhorn documentation related to this issue.

Include in /etc/multipath.conf the following configuration:

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

Restart multipathd service

systemctl restart multipathd

Installation procedure using Helm

Installation using Helm (Release 3):

  • Step 1: Add the Longhorn Helm repository:

    helm repo add longhorn https://charts.longhorn.io
  • Step2: Fetch the latest charts from the repository:

    helm repo update
  • Step 3: Create namespace

    kubectl create namespace longhorn-system
  • Step 4: Prepare longhorn-values.yml file

      defaultDataPath: "/storage"
    # Ingress Resource. Longhorn dashboard.
      ## Enable creation of ingress resource
      enabled: true
      ## Add ingressClassName to the Ingress
      ingressClassName: nginx
      # ingress host
      host: longhorn.picluster.ricsanfre.com
      ## Set this to true in order to enable TLS on the ingress record
      tls: true
      ## TLS Secret Name
      tlsSecret: longhorn-tls
      ## Default ingress path
      path: /
      ## Ingress annotations
        # Enable basic auth
        nginx.ingress.kubernetes.io/auth-type: basic
        # Secret defined in nginx namespace
        nginx.ingress.kubernetes.io/auth-secret: nginx/basic-auth-secret
        # Linkerd configuration. Configure Service as Upstream
        nginx.ingress.kubernetes.io/service-upstream: "true"
        # Enable cert-manager to create automatically the SSL certificate and store in Secret
        # Possible Cluster-Issuer values: 
        #   * 'letsencrypt-issuer' (valid TLS certificate using IONOS API) 
        #   * 'ca-issuer' (CA-signed certificate, not valid)
        cert-manager.io/cluster-issuer: letsencrypt-issuer
        cert-manager.io/common-name: longhorn.picluster.ricsanfre.com

    With this configuration:

    • Longhorn is configured to use /storage as default path for storing data (defaultSettings. defaultDataPath)

    • Ingress resource is created to make Longhorn front-end available through the URL longhorn.picluster.ricsanfre.com. Ingress resource for NGINX (ingress) is annotated so, basic authentication is used and a Valid TLS certificate is generated using Cert-Manager for longhorn.picluster.ricsanfre.com host

  • Step 5: Install Longhorn in the longhorn-system namespace, using Helm:

    helm install longhorn longhorn/longhorn --namespace longhorn-system -f longhorn-values.yml
  • Step 6: Confirm that the deployment succeeded, run:

    kubectl -n longhorn-system get pod

Configuring acces to Longhorn UI (Only Traefik Ingress)

Create a Ingress rule to make Longhorn front-end available through the Ingress Controller (Traefik) using a specific URL (longhorn.picluster.ricsanfre.com), mapped by DNS to Traefik Load Balancer external IP.

Longhorn backend is providing not secure communications (HTTP traffic) and thus Ingress resource will be configured to enable HTTPS (Traefik TLS end-point) and redirect all HTTP traffic to HTTPS. Since Longhorn frontend does not provide any authentication mechanism, Traefik HTTP basic authentication will be configured.

There is a known issue with accessing Longhorn UI from Traefik 2.x that makes Longhorn APIs calls fail. Traefik 2.x ingress controller does not set the WebSocket headers and a specific middleware to route to the Longhorn UI must be specified. See Longhorn documentation: “Troubleshooting Traefik 2.x as ingress controller” to know how to solve this particular issue.

  • Step 1. Create a manifest file longhorn_ingress.yml

    Two Ingress resources will be created, one for HTTP and other for HTTPS. Traefik middlewares, HTTPS redirect, basic authentication and X-Forwareded-Proto headers will be used.

    # Solving API issue. 
    apiVersion: traefik.containo.us/v1alpha1
    kind: Middleware
      name: svc-longhorn-headers
      namespace: longhorn-system
          X-Forwarded-Proto: "https"
    # HTTPS Ingress
    apiVersion: networking.k8s.io/v1
    kind: Ingress
      name: longhorn-ingress
      namespace: longhorn-system
        # HTTPS as entry point
        traefik.ingress.kubernetes.io/router.entrypoints: websecure
        # Enable TLS
        traefik.ingress.kubernetes.io/router.tls: "true"
        # Use Basic Auth Midleware configured
        # Enable cert-manager to create automatically the SSL certificate and store in Secret
        cert-manager.io/cluster-issuer: ca-issuer
        cert-manager.io/common-name: longhorn.picluster.ricsanfre.com
      - hosts:
        - storage.picluster.ricsanfre.com
        secretName: storage-tls
      - host: longhorn.picluster.ricsanfre.com
          - path: /
            pathType: Prefix
                name: longhorn-frontend
                  number: 80
    # http ingress for http->https redirection
    kind: Ingress
    apiVersion: networking.k8s.io/v1
      name: longhorn-redirect
      namespace: longhorn-system
        # Use redirect Midleware configured
        traefik.ingress.kubernetes.io/router.middlewares: traefik-redirect@kubernetescrd
        # HTTP as entrypoint
        traefik.ingress.kubernetes.io/router.entrypoints: web
        - host: longhorn.picluster.ricsanfre.com
            - path: /
              pathType: Prefix
                  name: longhorn-frontend
                    number: 80
  • Step 2. Apply the manifest file

    kubectl apply -f longhorn_ingress.yml

Testing Longhorn

For testing longorn storage, create a specification for a PersistentVolumeClaim and use the storageClassName of longhorn and a POD making use of that volume claim.

  • Step 1. Create testing namespace

    kubectl create namespace testing-longhorn
  • Step 2. Create manifest file longhorn_test.yml

    apiVersion: v1
    kind: PersistentVolumeClaim
      name: longhorn-pvc
      namespace: testing-longhorn
      - ReadWriteOnce
      storageClassName: longhorn
          storage: 1Gi
    apiVersion: v1
    kind: Pod
      name: longhorn-test
      namespace: testing-longhorn
      - name: longhorn-test
        image: nginx:stable-alpine
        imagePullPolicy: IfNotPresent
        - name: longhorn-pvc
          mountPath: /data
        - containerPort: 80
      - name: longhorn-pvc
          claimName: longhorn-pvc
  • Step 2. Apply the manifest file

    kubectl apply -f longhorn_test.yml
  • Step 3. Check created POD has been started

    kubectl get pods -o wide -n testing-longhorn
  • Step 4. Check pv and pvc have been created

    kubectl get pv -n testing-longhorn
    kubectl get pvc -n testing-longhorn
  • Step 5. Connect to the POD and make use of the created volume

    Get a shell to the container and create a file on the persistent volume:

    kubectl exec -n testing-longhorn -it longhorn-test -- sh
    echo "testing" > /data/test.txt
  • Step 6. Check in the longhorn-UI the created volumes and the replicas.



Setting Longhorn as default Kubernetes StorageClass

By default K3S comes with Rancher’s Local Path Provisioner and this enables the ability to create persistent volume claims out of the box using local storage on the respective node.

In order to use Longhorn as default storageClass whenever a new Helm is installed, Local Path Provisioner need to be removed from default storage class.

After longhorn installation check default storage classes with command:

kubectl get storageclass
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  10m
longhorn (default)     driver.longhorn.io      Delete          Immediate              true                   3m27s

Both Local-Path and longhorn are defined as default storage classes:

Remove Local path from default storage classes with the command:

kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

Procedure is explained in kubernetes documentation: “Change default Storage Class”.

Last Update: Jul 26, 2023