Contents

QNAP NAS as a Kubernetes Storage Backend: iSCSI, NFS, or Just Don't

Three ways to give your cluster persistent storage off a consumer NAS, ranked by regret

Contents

Everyone who builds a homelab Kubernetes cluster hits the same wall about a week in: stateful workloads. Your pods are stateless and beautiful until you want to run Postgres, or a media server, or anything that remembers things between restarts. Then you need persistent volumes, and you look around your house, and your eyes land on the QNAP NAS humming away in the corner with several terabytes of perfectly good storage. Surely you can just point the cluster at that?

You can. I have. There are three roads, and they offer very different amounts of pain. Let me walk you through iSCSI, NFS, and the third option — “just don’t, here’s what to do instead” — because pretending a consumer NAS is enterprise SAN gear is how you end up with a cluster that corrupts a database at 3am.

NFS: the easy road with a sharp edge

Advertisement

NFS is the path of least resistance. QNAP exposes it from the web UI in about four clicks, and Kubernetes has a well-trodden CSI driver for it. The killer feature is ReadWriteMany — multiple pods on different nodes can mount the same volume simultaneously, which is exactly what you want for shared media libraries or a bunch of replicas reading the same files.

Install the NFS CSI driver and define a StorageClass pointing at the NAS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: qnap-nfs
provisioner: nfs.csi.k8s.io
parameters:
  server: 192.168.1.10
  share: /share/k8s
mountOptions:
  - nfsvers=4.1
  - hard
  - noatime
reclaimPolicy: Retain
volumeBindingMode: Immediate

The sharp edge: NFS is a network filesystem, not a block device. Databases hate it. Postgres and SQLite both have known foot-guns with NFS locking, and a flaky network or a NAS that goes to sleep can corrupt data in ways that block storage wouldn’t. Use hard mounts (so I/O blocks and retries rather than failing), never soft, and keep databases off NFS. For media, documents, and bulk files it’s genuinely great.

One more NFS-specific trap catches people who did the sensible thing and segmented their network. If your cluster nodes and the NAS live on different VLANs — as they should, if you’ve done the sort of segmentation I describe in keeping your smart toaster away from your NAS — then NFS traffic has to cross a firewall boundary, and NFSv3’s port sprawl (portmapper, mountd, lockd, all on shifting ports) makes that miserable to allow cleanly. This is a real reason to pin nfsvers=4.1 in your mount options: NFSv4 runs over a single well-known port (2049) and is trivial to firewall. Do not fight NFSv3 port ranges across a VLAN if you can possibly avoid it.

iSCSI: block storage, more performance, more rope

iSCSI gives Kubernetes a real block device — the NAS carves out a LUN, the node attaches it, and as far as the pod is concerned it’s a local disk. This makes databases happy and gets you proper ReadWriteOnce semantics. QNAP supports iSCSI targets natively under Storage & Snapshots → iSCSI & Fibre Channel.

The democratic-csi project provides a CSI driver that can dynamically provision QNAP LUNs over SSH/API. A config snippet looks roughly like:

1
2
3
4
5
6
7
driver: freenas-iscsi   # democratic-csi works with QNAP via the generic iscsi driver too
iscsi:
  targetPortal: "192.168.1.10:3260"
  namePrefix: csi-
  targetGroups:
    - targetGroupPortalGroup: 1
      targetGroupInitiatorGroup: 1

And every node that will mount these needs the initiator installed and running:

1
2
3
4
5
6
$ sudo apt install open-iscsi
$ sudo systemctl enable --now iscsid
$ sudo iscsiadm -m discovery -t sendtargets -p 192.168.1.10
192.168.1.10:3260,1 iqn.2004-04.com.qnap:ts-453:iscsi.k8s.abc123
$ sudo iscsiadm -m session
tcp: [1] 192.168.1.10:3260,1 iqn.2004-04.com.qnap:ts-453:... (non-flash)

The catch with iSCSI on a consumer QNAP is twofold. First, a LUN is single-attach — only one node mounts it at a time, so no ReadWriteMany. Second, and more importantly, your NAS is now a single point of failure for block storage that a pod believes is a local disk. If the QNAP reboots for a firmware update mid-write, the node sees a disk vanish out from under a running database. That’s a worse failure mode than a dropped NFS mount.

iSCSI troubleshooting: the things that will actually bite you

I have lost evenings to iSCSI on consumer kit, so here is the shortlist:

  • Multipath fighting with the CSI driver. If multipathd is running on your nodes, it can grab the iSCSI device before Kubernetes does, and you end up with a device-mapper path the pod can’t mount. Either configure multipath to blacklist the CSI-managed devices, or disable it on nodes that only ever see a single portal. Symptoms: the volume attaches, then the pod hangs in ContainerCreating with a failed to get device path event.
  • CHAP authentication mismatches. QNAP lets you set CHAP on the target; if the initiator’s secret doesn’t match, discovery works but login silently fails. Check journalctl -u iscsid for authorization failure and confirm the secret in your CSI driver config matches the NAS exactly.
  • Stale sessions after a NAS reboot. When the QNAP comes back, nodes may hold a dead session. sudo iscsiadm -m session -u logs everything out, then let the driver re-establish. Building this kind of “assume the storage layer will flap and recover from it” thinking into your cluster is exactly the mindset I argue for in using fault injection to build resilience — if you have never deliberately rebooted the NAS to watch what breaks, you don’t actually know what happens when it does.
  • Filesystem corruption on ungraceful detach. A LUN yanked mid-write comes back needing fsck. The CSI driver won’t run it for you. This is the failure mode that turns “the NAS rebooted” into “the database is down for an hour”, and it is the whole reason the next section exists.

Just don’t: when the answer is distributed storage

Advertisement

Here’s the uncomfortable truth I arrived at. A consumer NAS is a fine file server. It is a poor storage backend for a cluster that’s supposed to be resilient, because the whole point of a cluster is to survive a node dying — and you’ve just made one box the thing everything depends on.

If you have three or more nodes with spare disks, run distributed storage inside the cluster instead: Longhorn or Ceph (via Rook). Longhorn in particular is almost embarrassingly easy:

1
2
$ kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.5.1/deploy/longhorn.yaml
$ kubectl -n longhorn-system get pods -w

It replicates each volume across nodes, survives a node loss, snapshots, and backs up to S3 or — yes — your NAS over NFS, which is exactly the right job for it. The NAS becomes the backup target, not the live storage path.

Longhorn is not the only option here, and the choice between it and the alternatives is a real one — I weigh Longhorn against OpenEBS and the trade-offs of each in persistent storage for Kubernetes that isn’t a nightmare. The short version: Longhorn is the easiest to stand up and operate, Ceph via Rook is more capable and considerably more complex, and OpenEBS sits in between with several engines to pick from. For a three-to-five-node homelab, Longhorn is almost always the right first answer.

There is a resource cost to be honest about. Distributed storage replicates data, which means it uses disk and — during rebuilds — a meaningful chunk of network and CPU. Give the storage pods proper requests and limits or a rebuild storm will starve your actual workloads; this is precisely the kind of noisy-neighbour problem I dig into in resource requests and limits. Under-provision the storage layer and you trade one single point of failure for a cluster that falls over the moment a disk needs rebuilding.

Performance: set expectations before you’re disappointed

Whichever path you pick, a consumer NAS over a home network is not fast storage, and it helps to know the ceiling before you benchmark it and panic. A single gigabit link caps you at roughly 110 MB/s, and that is shared across every pod hitting the NAS. If your cluster and NAS both have 2.5GbE or 10GbE ports, use them and put storage traffic on its own network — that is the single biggest win available. On plain gigabit, expect throughput fine for media streaming and config files, and latency that will make a busy database feel sluggish even before you hit the correctness problems.

A few tuning knobs that actually move the needle:

  • Jumbo frames (MTU 9000) end-to-end — NAS, switch, and every node — reduce per-packet overhead noticeably for iSCSI and NFS alike. This only helps if every device on the path agrees; one node still on 1500 will fragment and you lose the benefit.
  • Match nfsvers=4.1 and noatime as in the StorageClass above. Dropping access-time updates cuts a surprising amount of pointless write traffic.
  • Don’t let the NAS spin its disks down. Consumer NAS units love to sleep their drives to save power; a pod that hits a sleeping array sees multi-second latency spikes as the disks spin up, which reads to Kubernetes like a failing volume. Disable disk hibernation for any volume the cluster uses.

The honest framing is that you are borrowing spare capacity from a device built for a different job. It works, within limits, if you respect those limits.

Verdict

So, which one? NFS off the QNAP for media, config, and any ReadWriteMany workload — easy, robust enough, just keep databases off it. iSCSI only if you specifically need block performance and you genuinely accept that the NAS is now a single point of failure; treat it as a deliberate trade, not a default. And for anything that’s meant to be highly available, don’t use the NAS as live storage at all — run Longhorn across your nodes and demote the QNAP to the backup target it’s actually good at. The NAS is a member of your storage strategy, not the whole of it.

Who this is actually for

Be honest with yourself about what you’re building. If your homelab cluster is a learning environment where a bit of downtime costs you nothing but a shrug, NFS off the QNAP is completely fine and you should not overthink it — spend your evenings on more interesting problems. If you’re hosting things you actually care about staying up — a photo library the family relies on, home automation that controls real devices, anything where “the storage went away” means someone in the house is annoyed — then the single-point-of-failure question stops being academic, and distributed storage across your nodes is worth the extra resource cost and setup time.

What almost nobody should do is treat a consumer QNAP as if it were the enterprise SAN that Kubernetes storage tutorials quietly assume. That mismatch is where the 3am database-corruption stories come from. The NAS is a superb file server and a superb backup destination. Ask it to be the resilient block-storage foundation of a supposedly-resilient cluster, and you have designed the exact fragility the cluster was meant to eliminate. Use each thing for what it is good at, and a homelab with a NAS in the corner becomes a genuinely solid platform rather than a corruption incident waiting for a firmware update.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.