> ## Documentation Index
> Fetch the complete documentation index at: https://docs.celum.io/llms.txt
> Use this file to discover all available pages before exploring further.

# The Storage tab

> What a supervisor's Storage tab shows for LINSTOR, NFS, Ceph or any other CSI — including a reachability check that sends real packets from the host and pod networks — and how to read it.

A supervisor's **Storage** tab (`/s/<supervisor>?tab=storage`) shows whatever actually provides storage on that cluster. It reads the cluster's StorageClasses, and each class's **provisioner** picks the panel:

| Provisioner | Panel |
| - | - |
| `linstor.csi.linbit.com` | LINSTOR — controller, satellites, replication paths, listeners, resources, pools, history |
| `nfs.csi.k8s.io` | NFS — driver, server and share, the route to the server on every node, classes |
| `*.rbd.csi.ceph.com`, `*.cephfs.csi.ceph.com` | Rook-Ceph — cluster health, OSDs, MONs, pools |
| any other provisioner a catalogue backend declares | that driver's workloads, as Kubernetes reports them |

Every StorageClass is also listed at the bottom of the tab with its provisioner, default flag, reclaim policy, binding mode and how many claims are **Bound** or **Pending** — including classes whose provisioner the platform does not know. Two backends on one supervisor get one labelled block each.

> [!NOTE]
>
> An **unreachable supervisor says so**. The tab reads "Storage could not be read" rather than "No storage backend is installed" — the empty list the API returns in that case is marked `unreachable`, because "we could not ask" and "there is nothing" are opposite claims.

## LINSTOR

The LINSTOR panel leads with the network, because that is where LINSTOR fails while every pod stays `Running`.

**Rollup** — the controller (and the node it runs on), satellites online, resources whose every replica is `UpToDate` and connected, and pool usage with a shared array LUN counted once rather than once per node that sees it.

**Connectivity** compares what LINSTOR *declares* with what the `linstor-storage` chart *asked for*, and names any difference at the top of the card:

* **Control plane** — for each satellite, the interface the controller is connected on *right now* (LINSTOR's `CurStltConnName`), its address and port, and every registered interface. The chart pins this connection to the node address (`default-ipv4`); a satellite connected anywhere else, or satellites disagreeing with each other, is flagged.
* **Replication** — for every pair of nodes, the declared node-connection path, the addresses DRBD uses at each end, and how many of the DRBD resources they share report the connection up. A pair with no declared path, a `PrefNic` that differs from the chart, or a storage address that differs from the cluster's `storage` interface role is flagged.
* **Wire** — the NIC carrying each node's storage address, with its MTU, VLAN and link state, as cluster-agent reads them from the node. A node whose MTU or VLAN differs from the rest is the one wired differently.

**Listeners** lists what listens where as LINSTOR and the pod specs declare it: the controller REST API (Service and pod IP, port 3370), each satellite on every registered interface (3366), drbd-reactor's metrics (9942) and each node's DRBD ports on the address they bind.

**Resources** shows unhealthy resources by default — a replica that is not `UpToDate`, or a DRBD peer that is not connected — with the owning PVC; switch the filter to see all of them. **History** and **Storage pools** follow.

> [!WARNING]
>
> **Everything in these sections is declared state** — what the controller believes. Every pod can be `Running` and every node `ONLINE` while a satellite is unreachable on the path the controller uses. The reachability check below is what tests it.

## The reachability check

**Run check** on the LINSTOR and NFS panels sends real packets and keeps the result, stamped with when it ran:

| | LINSTOR | NFS |
| - | - | - |
| TCP, **host network** | every peer satellite on every registered interface | the server on 2049, and rpcbind on 111 when a class mounts NFSv3 |
| TCP, **pod network** | each peer satellite on the **control** interface — the controller is a pod and controls satellites there. The storage interface reads **n/a** | not probed — reads **n/a** |
| Full-size frame | ICMP to each peer's storage address, Don't Fragment, at the source NIC's MTU | ICMP to the server, Don't Fragment, at the MTU of the NIC carrying its route |
| Bound sockets | each declared listener against the sockets the node actually holds | — |

The **host network** column comes from cluster-agent, which runs with host networking. The **pod network** column comes from one short-lived, unprivileged pod per node, running the same agent image; it is deleted as soon as it answers.

The pod column asks only about paths a pod is meant to use. The NIC carrying the `storage` interface role is deliberately **not** a CNI device — replication through the CNI would only add load — so a pod's path to another node's storage address is not a supported one — it can work, and on talos-waw it stopped working the moment an unrelated `NetworkPolicy` made Cilium mislabel the replies (see [LINSTOR](https://docs.celum.io/storage/linstor.md#what-commonly-goes-wrong)). Nothing needs it: satellites and DRBD run on the host network, and the kubelet mounts NFS from there too. What a pod does need is the controller's path to each satellite, on the control interface; a failure there drops the satellite however healthy the host paths are. If a satellite is found controlled over some other interface (the drift the Control plane table flags), that interface is probed from a pod as well, because the controller is using it.

A TCP handshake is a few dozen bytes and crosses a path that drops every jumbo frame, which is why the check also sends one full-size ping with Don't Fragment. Three different answers point at three different places: *larger than this node's route MTU* is the sending node, *fragmentation needed* names a hop and its MTU, and *no reply* is something dropping it silently.

DRBD's sockets are `ESTABLISHED` rather than listening once a resource is connected, so the bound-socket comparison counts a connected DRBD port as bound and lists the peers on the other end — direct evidence of which wire replication actually uses.

> [!NOTE]
>
> The check needs **cluster-agent chart 1.30.450 or newer** (Celum 1.140), which adds the probe endpoints and the `NET_RAW` capability the full-size ping needs. A node on an older agent is reported **not tested**, never as failed. Re-apply the agent from onboarding to enable it.

## NFS

The driver's release state, the server and share, whether every node carries the route to the server — and on which NIC, at which MTU — and the classes with their mount options and whether their driver is still installed. Where no node reports its routes, the panel says the route is *not reported*, not missing: that happens on a cluster-agent older than 1.128.

## History

With Monitoring installed, the LINSTOR panel draws four series over 1 hour to 7 days from the supervisor's Prometheus: used % per storage pool, replication traffic leaving the satellites, out-of-sync bytes, and how many DRBD connections were not `Connected` at each step. Without Prometheus, or before it scrapes the LINSTOR controller, the card says which of the two — the rest of the tab needs neither.

## From the API and from Celum AI

| What | API | MCP tool |
| - | - | - |
| Which backends and classes are installed, with claim counts | `GET /api/supervisors/{name}/storage/csi/summary` | `get_storage_summary` |
| LINSTOR declared state and its comparison with the chart | `GET /api/supervisors/{name}/storage/linstor/overview` | `get_linstor_overview` |
| LINSTOR history | `GET /api/supervisors/{name}/storage/linstor/history?range=24h` | `get_linstor_history` |
| Run the reachability check | `POST /api/supervisors/{name}/storage/connectivity/check?backend=linstor\|nfs` | `run_storage_connectivity_check` |
| The last result, without sending anything | `GET /api/supervisors/{name}/storage/connectivity?backend=linstor\|nfs` | `get_storage_connectivity` |
| NFS driver, classes and route | `GET /api/supervisors/{name}/storage/nfs/status` | `get_component_status` |

One check runs per supervisor and backend at a time; a second request answers `409`. `ran: false` on the last-result read means no check has run since the backend started — not that the network is fine.

## Permissions

| Task | Action |
| - | - |
| See the tab, the LINSTOR overview and history, the last check result | `storage:GetStatus` |
| Run the reachability check (it starts one pod per node) | `storage:Test` |

Both are checked against `krn:celum:supervisor:<supervisor>:storage:*`. Without `storage:Test` the **Run check** button is disabled and says which action it needs.

## Related

> [card] [LINSTOR](https://docs.celum.io/storage/linstor.md)
>
> How the pools are built, which class a workload wants, and the failure signatures.

> [card] [NFS & Fibre Channel](https://docs.celum.io/storage/nfs-and-fibre-channel.md)
>
> The NFS driver, its classes and the route the Storage step installs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.