CSI Driver Architecture in Kubernetes: Controller and Node Plugins
How Kubernetes talks to any storage system through one standard interface, why every driver is split into a controller part and a node part, and how a simple claim for storage becomes a real disk mounted inside a running container.
Imagine moving into a new apartment and asking the building manager for a storage locker. The manager does not build the locker personally. A back-office team allocates one somewhere in the building and records the number. Then, when you actually arrive, a porter on your floor walks you to the locker, unlocks it and makes sure you can use it from your own front door. Two teams, two different jobs: one works from the back office and knows the whole building, the other works on your floor and handles the last few steps. Kubernetes storage works almost exactly this way, and the standard that describes it is called the Container Storage Interface, or CSI.
This post teaches CSI architecture from the very beginning. You do not need to be a storage expert or to have written a driver. We start with the problem that CSI solved, then meet the standard itself, then open the two halves of every driver, the controller plugin and the node plugin, and follow a volume from request to running container and back. After that we cover topology, snapshots, failure handling, security, monitoring and common mistakes. Every new term is defined, compared with something from daily life, and used again in the next section.
1The Problem: Storage Code Inside Kubernetes Itself
Before CSI existed, every storage system had to be built into Kubernetes. That created a painful bottleneck.
Containers forget, storage remembers
A container’s own filesystem disappears when the container is replaced. That is fine for a stateless web server, but a database needs its data to survive restarts, rescheduling and node failures. The answer is a volume: a piece of storage that lives outside the container and can be attached to whichever node runs the Pod. Volumes can be network disks from a cloud, shared file systems, local drives, or storage appliances in a data centre.
The in-tree era
In the early years, the code that knew how to talk to each storage system was part of the main Kubernetes source code. These were called in-tree volume plugins, because they lived inside the Kubernetes code tree. Support for a cloud disk, a file protocol or an appliance meant a new chunk of code inside Kubernetes itself, built and released together with everything else.
Imagine a phone manufacturer that must build every possible charger plug directly into the phone body. Every new plug shape means redesigning and re-shipping the phone. A far better design is a standard socket, so that any charger maker can build a compatible cable without touching the phone.
Release lock-step
A storage vendor fixing a bug had to wait for the next Kubernetes release and then for clusters to upgrade, which could take months.
Core code bloat
Vendor-specific code inside the main project made it larger, harder to test and harder to secure.
Shared fate
A crash or vulnerability in one storage plugin could affect core components such as the kubelet, since they ran in the same process.
No level playing field
Only vendors with code in the main project were first-class. Everyone else had weaker, awkward options.
CSI is not a single storage product, and it is not part of the Kubernetes code. It is a specification, a contract written down so that Kubernetes, and other orchestrators, can talk to any storage system the same way. Vendors and communities implement the contract in drivers.
2Storage Objects Refresher: Claims, Volumes and Classes
A short tour of the Kubernetes objects that drivers serve, so the rest reads easily.
The three everyday objects
PersistentVolumeClaim
A request for storage written by an application owner: “I need ten gigabytes, readable and writable by one node.” It does not say how or where.
PersistentVolume
The cluster’s record of one real piece of storage, with its size, access mode and the identifier the storage system uses for it.
StorageClass
A menu entry created by the platform team: it names a driver and a set of options, such as disk type, replication level or encryption.
A claim is your request form at the building office. A StorageClass is the price list showing “small locker, large locker, climate-controlled locker.” A PersistentVolume is the actual locker with its number written in the building register.
Dynamic provisioning
In the old manual style, an administrator pre-created volumes and users claimed them. In dynamic provisioning, nobody pre-creates anything. When a claim names a StorageClass, the system asks the driver named in that class to create a fresh volume on demand, and then registers it as a PersistentVolume that binds to the claim. This is what makes self-service storage possible, and it is the main workflow CSI drivers support.
Reclaim policy
Every dynamically created volume carries a rule for what happens when its claim is deleted. Delete means the real storage is destroyed too. Retain means the PersistentVolume and its data are kept for an administrator to deal with. Choosing between them is a data-safety decision, not a technical detail, and we revisit it in the anti-patterns chapter.
Binding mode
A StorageClass also chooses when a volume is created. With immediate binding, the volume is created as soon as the claim appears. With wait-for-first-consumer binding, creation is postponed until a Pod that uses the claim has been scheduled, so the volume can be created in the right place. Chapter 11 explains why this matters.
Application teams write claims and rarely care about drivers. Platform teams write StorageClasses and choose drivers. Driver authors implement the CSI contract. CSI keeps these three groups from needing to know each other’s details.
3What CSI Actually Is
A written contract between an orchestrator and a storage driver, spoken over a simple local channel.
A specification shared across systems
The Container Storage Interface is an open specification that describes a small set of operations a storage driver must offer: create a volume, delete a volume, attach it to a machine, mount it at a path, take a snapshot, and so on. It was designed together by several container orchestrators and storage vendors, so a driver written once can in principle serve more than one orchestrator. Kubernetes is the most prominent user.
The language of the contract: gRPC
The operations are defined as gRPC calls, a widely used way for programs to call functions in other programs over a connection. On a single node, the program that asks and the driver that answers talk through a local file-like endpoint called a Unix domain socket. There is no network involved for that conversation, only a socket file on disk, which keeps it fast and limits who can talk to the driver.
CSI is like a standard menu of requests a customer can make to a courier company, written in a common language. Any courier that honours the menu can be hired by any customer. The internal vans, routes and warehouses of each courier stay private.
Three services in one contract
The specification groups its operations into three named services. A driver offers some or all of them, depending on what it can do.
Identity
“Who are you and what can you do?” Reports the driver’s name, version, whether it offers controller features, and whether it is healthy.
Controller
Operations that act on the storage system as a whole, such as creating, deleting, attaching, detaching, snapshotting and expanding volumes.
Node
Operations that act on one specific machine, such as preparing a volume on that machine and making it appear at a path for a Pod.
The split between Controller and Node services is the heart of the architecture. Some storage work can be done from anywhere in the cluster, for example asking a cloud API to create a disk. Other work can only be done on the very machine where the Pod runs, for example mounting a disk into the filesystem. CSI gives each kind its own service, and each service runs in the place where it makes sense.
Optional capabilities
Not every driver can do everything. A simple network file system may never need an attach step. A local disk driver may not offer snapshots. Each driver therefore reports a list of capabilities through the Identity and Controller and Node services, and Kubernetes only calls the operations that the driver says it supports. This keeps the contract small for simple drivers and rich for advanced ones.
4Architecture Overview: Controller Plugin and Node Plugin
Two kinds of running programs, placed in two different spots of the cluster.
Two halves of one driver
A CSI driver is usually delivered as two deployments of the same driver software. The controller plugin runs a small number of copies somewhere in the cluster, serves the Identity and Controller services, and talks to the storage system’s management interface, often a cloud API. The node plugin runs one copy on every node that can use the storage, serves the Identity and Node services, and performs the machine-level work of preparing and mounting volumes.
The controller plugin is the back-office team with access to the building register. It allocates lockers and records ownership. The node plugin is the porter on each floor. The back office never visits your floor, and the porter never changes the register. Each is only responsible for their part of the job.
flowchart LR
subgraph CP["Control plane side of the driver"]
PR["external-provisioner"]
AT["external-attacher"]
SN["external-snapshotter"]
RS["external-resizer"]
CD["Driver: Identity and Controller services"]
PR --> CD
AT --> CD
SN --> CD
RS --> CD
end
CD --> ST[("Storage system API")]
subgraph ND["Every node"]
KL["Kubelet"]
RG["node-driver-registrar"]
NDD["Driver: Identity and Node services"]
KL --> NDD
RG --> KL
end
NDD --> ST
Why not one program?
It would seem simpler to ship one program that does everything. But the two halves have very different needs. Controller work needs credentials for the storage system and does not need access to any node’s filesystem. Node work needs deep access to the node’s filesystem and devices and should not carry broad cloud credentials. Separating them follows the principle of least privilege, scales the right part in the right way, and lets each half be upgraded or restarted independently.
Benefits of the split
- Credentials for the storage system live only in the controller part
- The node part gets only the filesystem power it needs
- Controller work is done once per cluster, node work once per node
- Each half scales and fails independently
Costs of the split
- More moving parts to deploy and monitor
- Problems can hide at the hand-off between the two halves
- Versions of the halves and the helper programs must stay compatible
Many drivers use a single container image for both halves and switch roles through settings or startup options. What matters conceptually is not the image count but the two roles, the places they run and the privileges each one holds.
5The Services and Their Operations in Detail
What each service can be asked to do, in plain language.
Identity service
The Identity service answers three questions. Plugin info returns the driver’s name and version. The name is important because it must match the provisioner name written in StorageClasses. Plugin capabilities tells callers whether the driver offers controller features and whether volumes can be reached only from some parts of the cluster. Probe reports whether the driver is ready to work, and is used by health checks.
Controller service
Create and delete volume
Creating allocates real storage of a given size and options and returns the driver’s own volume identifier. Deleting destroys it. Both must be safe to repeat, because callers retry after failures.
Publish and unpublish at controller level
Often called attach and detach. These make a volume available to a specific node from the storage side, for example by attaching a cloud disk to a virtual machine. They exist only for storage that needs this step.
Snapshots
Create, delete and list point-in-time copies of a volume. A new volume can later be created from a snapshot.
Expand volume
Make an existing volume larger on the storage side. The filesystem on it may need separate expansion on the node.
Capacity and validation
Report how much room remains in a pool, and check that a volume can be used in a requested way.
Node service
Stage and unstage volume
Prepare the volume once on a node: find the device, create a filesystem if the volume is new, and mount it at a shared staging path. Unstage reverses this when no Pod on the node uses the volume any more.
Publish and unpublish at node level
Make the volume appear at a specific path for a specific Pod, usually by a bind mount from the staging path. Unpublish removes it when that Pod goes away.
Volume statistics and node expansion
Report how much space and how many files are used on the volume, and grow the filesystem after the storage side has been enlarged.
Node information
Return the node’s identity as the storage system knows it, its limits on the number of volumes, and where it sits in the topology of the cluster.
| Service | Runs where | Acts on | Typical examples |
|---|---|---|---|
| Identity | Both plugins | The driver itself | Name, capabilities, health |
| Controller | Controller plugin | The storage system as a whole | Create, delete, attach, detach, snapshot, expand |
| Node | Node plugin on each node | One machine’s filesystem and devices | Stage, publish, unpublish, unstage, stats |
The word publish appears in both the Controller and Node services, with different meanings. At controller level it means making the volume reachable by a node from the storage side. At node level it means making the volume appear inside a Pod’s directory. Mixing these up is a common source of confusion when reading logs.
6The Controller Plugin and Its Sidecars
The driver does not watch Kubernetes. A set of helper programs do that on its behalf.
The sidecar idea
The CSI driver itself knows nothing about Kubernetes objects. It only answers CSI calls. To connect Kubernetes to the driver, the Kubernetes storage community provides a set of generic helper programs called sidecars. Each one is a small controller that watches certain Kubernetes objects and translates changes into CSI calls to the driver, over a local socket shared in the same Pod. Driver authors reuse these helpers instead of writing Kubernetes logic themselves.
The driver is a specialist craftsman who speaks only the language of the standard menu. Each sidecar is a translator who reads the customer’s paperwork in Kubernetes language and turns it into menu requests for the craftsman. The craftsman never needs to learn the paperwork language.
external-provisioner
Watches claims that name a class using this driver. Calls create volume, then creates the PersistentVolume record. Also handles deletion according to the reclaim policy.
external-attacher
Watches VolumeAttachment objects and calls controller publish and unpublish so volumes are attached and detached from nodes.
external-snapshotter
Watches snapshot objects and calls snapshot operations, creating the matching records for the real snapshot.
external-resizer
Watches claims whose requested size grows and calls the expand operation on the storage side.
liveness probe helper
Checks the driver’s health by calling the probe operation and reports to Kubernetes health checks so unhealthy drivers get restarted.
health monitor and others
Optional helpers can watch volume health on the storage side and raise events, depending on driver and version support.
Informers and queues again
If you have read about how Kubernetes controllers work, these sidecars will feel familiar. Each one uses informers to watch objects, a work queue to process changes, and a reconcile step that calls the driver and writes results back. The same list-and-watch pattern, the same retries with back-off and the same level-based thinking apply. A sidecar that restarts simply re-lists and carries on.
High availability and leader election
The controller plugin is usually run with several replicas for availability. Only one replica of each sidecar should act at a time, otherwise two copies might create the same volume twice. The sidecars therefore use leader election, so that one copy is active and the others stand by. Because leader election is per sidecar, the active provisioner and the active attacher may in principle be in different replicas.
flowchart TD
K[("Kubernetes API objects")] -- "watch" --> S1["Sidecar: provisioner"]
K -- "watch" --> S2["Sidecar: attacher"]
S1 -- "CSI call over local socket" --> D["Driver container: Controller service"]
S2 -- "CSI call over local socket" --> D
D -- "management API call" --> X[("Storage system")]
D -- "result" --> S1
S1 -- "write PV and status" --> K
The controller plugin is an ordinary workload, often a Deployment in a system namespace. It does not need to run on special nodes unless the storage system’s management interface is only reachable from certain networks, which is a real consideration for on-premises storage.
7The Node Plugin and the Kubelet
The part that touches the machine: devices, filesystems and mount points.
Deployed on every node
The node plugin runs as a DaemonSet, which means Kubernetes keeps exactly one copy on each eligible node. Because it must create mounts that are visible to the kubelet and to containers, it needs special privileges and special access to the node: it normally runs as a privileged container and shares parts of the node’s directory tree with the host.
The kubelet is the caller
On the node side the caller is not a sidecar but the kubelet, the agent that starts Pods on that node. When a Pod needs a CSI volume, the kubelet calls the node plugin’s stage and publish operations over a Unix socket that the plugin has created in a known folder under the kubelet’s directory.
Registration
How does the kubelet find the driver’s socket? A helper sidecar called the node-driver-registrar runs beside the node plugin. It announces the driver to the kubelet through a registration socket that the kubelet watches. The kubelet then calls the driver’s node information operation, learns the node’s identity and limits in the storage system’s language, and records the driver in a per-node Kubernetes object. From that point the kubelet knows the driver is available on this node.
The node plugin starts and opens its socket
The driver container creates its CSI socket in a plugin folder under the kubelet’s directory.
The registrar announces the driver
It places a registration socket in the folder that the kubelet watches, naming the driver and where its socket lives.
The kubelet asks for identity and node information
It calls the driver to confirm its name and to learn the node’s identifier, volume limit and topology labels.
The kubelet records the driver for this node
It creates or updates a per-node record and the node’s labels, so the scheduler and controllers know that this node can use the driver.
Mount propagation
A subtle but vital detail is how mounts made by the node plugin become visible to the application container. A container normally has its own view of the filesystem. For this to work, the node plugin’s container shares the kubelet’s Pod directory with the host using a setting called bidirectional mount propagation, so that a mount made inside the plugin appears on the host and therefore in the application’s Pod directory. Without it, the plugin would mount the volume in its own private view and the application would see an empty folder.
Imagine the porter sets up your locker contents on a shelf, but the shelf is inside a glass cabinet that only the porter can see. Mount propagation is the hole in the cabinet that lets the apartment owner see and use what the porter placed. Without the hole, the locker looks empty from your side.
A node plugin with host-level mount access is powerful. It can see and change the node’s filesystem view. Treat its image, its configuration and its namespace as part of your trusted infrastructure, and never run untrusted drivers there.
8Kubernetes Objects That Describe a Driver
Besides claims and volumes, three more objects tell Kubernetes how a driver behaves.
CSIDriver: the driver’s settings
A CSIDriver object is a cluster-wide record that declares how Kubernetes should treat a driver. It is optional but very useful. It can say whether volumes need an attach step at all, whether the kubelet should pass Pod details to the driver when mounting, how file ownership should be applied to volumes, whether the driver supports tracking available capacity, and which lifecycle modes it supports, such as persistent volumes or short-lived ephemeral ones.
CSINode: what each node can do
A CSINode object exists for each node. It lists the drivers registered on that node, the node’s identifier in each driver’s world, the topology keys that describe where the node sits, and sometimes the maximum number of volumes the node can hold for each driver. The scheduler uses this to avoid placing a Pod on a node that has reached its volume limit.
VolumeAttachment: the attach request
A VolumeAttachment object records the intent to attach a particular volume to a particular node. It is created by Kubernetes when a Pod using a volume is scheduled to a node, and is watched by the attacher sidecar. Its status shows whether attachment succeeded, and any error. It is the single most useful object to inspect when a Pod is stuck waiting for its disk.
| Object | Scope | Created by | Used for |
|---|---|---|---|
| CSIDriver | Cluster | Driver installer or administrator | Declares driver behaviour and options |
| CSINode | One per node | Kubelet, after registration | Tracks drivers, node identity, topology and limits |
| VolumeAttachment | One per volume and node | Kubernetes attach logic | Records and reports attach and detach progress |
| CSIStorageCapacity | Per class and location | Driver helper | Publishes remaining capacity for smarter scheduling |
The CSIDriver object is the porter agency’s service brochure. CSINode is each floor’s staff list and what they can carry. VolumeAttachment is the written work order: “move locker 42 contents to floor 7.”
Why the attach step may not exist
Not all storage needs attach and detach. A cloud block disk must be attached to a specific virtual machine before it can be used. A network file system, on the other hand, is simply mounted over the network by any node that knows the address. A driver declares which style it is. When no attach is needed, Kubernetes skips the VolumeAttachment step completely and goes straight to node-level stage and publish, which makes startup of such Pods faster.
When capacity tracking is enabled, the driver side publishes how much room is left per class and location. The scheduler can then avoid nodes whose nearby storage is full, rather than discovering the shortage only after the Pod lands and provisioning fails.
9The Full Lifecycle: From Claim to Running Container
Follow one volume through every component, from the first request to a mounted folder.
The story in order
An application owner creates a claim naming a StorageClass. The provisioner sidecar sees it, asks the driver to create a volume, and records a PersistentVolume. Kubernetes binds the claim to that volume. When a Pod using the claim is scheduled to a node, Kubernetes creates a VolumeAttachment. The attacher sidecar asks the driver to attach the volume to that node. The kubelet then waits for attachment to finish, asks the node plugin to stage the volume, then to publish it into the Pod’s directory, and finally starts the containers with the folder in place.
sequenceDiagram
participant U as User
participant API as Kubernetes API
participant PR as Provisioner sidecar
participant CD as Controller driver
participant AT as Attacher sidecar
participant KL as Kubelet
participant ND as Node driver
U->>API: Create claim with a StorageClass
PR->>API: Watch sees new claim
PR->>CD: CreateVolume
CD-->>PR: Volume ID
PR->>API: Create PersistentVolume, claim binds
U->>API: Create Pod that uses the claim
API->>API: Scheduler picks a node
API->>API: Attach logic creates VolumeAttachment
AT->>CD: ControllerPublishVolume to that node
CD-->>AT: Attached
AT->>API: Mark VolumeAttachment attached
KL->>ND: NodeStageVolume
ND-->>KL: Staged at global path
KL->>ND: NodePublishVolume
ND-->>KL: Mounted into Pod directory
KL->>KL: Start containers
Reading the diagram
The top half belongs to the cluster-wide side: creating the volume and binding it. The middle is the scheduler and the attach request. The bottom is the node’s own work. Notice that the controller driver is called twice, once to create and once to attach, while the node driver is called twice as well, to stage and to publish. Each call is a small, separate, retryable step, and failure at any point leaves the system in a state from which it can continue.
From the application owner’s view, all of this looks like a claim turning from Pending to Bound and a Pod moving from ContainerCreating to Running. The events on the claim and the Pod are the breadcrumb trail that shows which of the steps is currently active.
10Stage Versus Publish, and the Reverse Journey
Why the node side has two steps, and what happens when a Pod goes away.
Why two node steps
A single volume can be used by several Pods on the same node at the same time, for instance when a shared volume is used by two Pods. The expensive, once-per-volume work, such as finding the device and creating a filesystem, should be done once. The cheap, per-Pod work, which is placing the volume into one Pod’s directory, should be done for each Pod. So stage does the expensive part once per node and volume and mounts the volume at a shared staging path. Publish then makes a lightweight bind mount from the staging path into each Pod’s own directory.
Staging is the porter bringing a large cabinet up to your floor and unlocking it, which takes effort and is done once. Publishing is putting a labelled shelf of that cabinet in front of each flat that has a right to use it, which is quick and done per household.
The reverse journey
When a Pod is deleted, the order reverses. Unpublish removes the bind mount for that Pod. When the last Pod on the node using the volume is gone, unstage unmounts the staging path. If the volume is no longer needed on that node, the attach logic removes the VolumeAttachment and the attacher asks the driver to detach. Finally, if the claim itself is deleted and the reclaim policy is Delete, the provisioner asks the driver to delete the real volume.
Containers stop
The Pod’s processes finish and release the files they were using.
NodeUnpublish
The Pod’s own mount is removed from its directory.
NodeUnstage
If no other Pod on the node uses the volume, the shared staging mount is removed.
ControllerUnpublish
The volume is detached from the node on the storage side, so it can be used elsewhere.
DeleteVolume, only when the claim is gone and policy allows
The storage is destroyed if the reclaim policy is Delete, or left intact if it is Retain.
Detaching a disk before the node has cleanly unmounted it risks data corruption. The orderly sequence exists to flush data and release the device first. Forcing steps out of order, for example by deleting finalizers by hand, can skip that protection.
11Topology, Scheduling and Binding Modes
A volume’s location constrains where the Pod can run, and the Pod’s location constrains where the volume should be created.
Storage has a location
Many storage systems are tied to a place. A cloud block disk lives in one availability zone and can only be attached to machines in that same zone. A local disk lives on one specific node. If a volume is created in zone A and the Pod is later scheduled into zone B, the Pod can never mount it and will be stuck. CSI handles this with topology: drivers describe where nodes are and where volumes can be reached from, using labels such as zone.
The two binding modes revisited
With immediate binding, the volume is created as soon as the claim appears, before the scheduler has chosen a node for the Pod. The driver has no idea where the Pod will go, so it picks a location on its own. The scheduler is then forced to place the Pod where the volume is. This can leave a Pod unschedulable if that zone is full.
With wait-for-first-consumer binding, creation is postponed. The scheduler first picks a node considering everything else, such as CPU, memory and affinity, and also considering the topology that the driver’s storage supports. Only then is the volume created in the chosen node’s location, passed to the driver as a preferred topology. This is the recommended mode for location-bound storage.
flowchart TD
C["Claim with wait-for-first-consumer class"] --> P["Claim stays pending, no volume yet"]
P --> S["Pod created, scheduler evaluates nodes"]
S --> N["Scheduler picks a node in zone B"]
N --> T["Chosen node's topology sent to provisioner"]
T --> V["Driver creates the volume in zone B"]
V --> B["Claim binds, Pod proceeds to attach and mount"]
Immediate binding is like reserving a locker in a random wing of a huge campus before you know which office you will work in. Delayed binding waits until you know your office, then reserves a locker in the same wing.
Allowed topologies
A StorageClass can also restrict volumes to certain locations, for example only two of three zones. This is useful when storage exists only in some places or when data should stay in specific regions. The scheduler and provisioner both honour the restriction.
Volume limits per node
Machines can only hold so many attached volumes. A driver reports the limit through the node information call, and Kubernetes stores it in the node’s record. The scheduler then counts volumes per node and avoids exceeding the limit, so Pods are not sent to a node that has no room for another disk.
For any storage that is bound to a zone, node or rack, use delayed binding unless you have a specific reason not to. It removes a whole class of Pods that never start because their disk is in the wrong place.
12Capabilities: Snapshots, Clones, Expansion and Access Modes
What a driver can offer beyond create and mount, and how each feature maps onto the two plugins.
Snapshots
A snapshot is a point-in-time copy of a volume. Kubernetes models it with separate objects: a request for a snapshot, a record of the real snapshot, and a class that names the driver. Snapshots are handled by a snapshot controller in the cluster together with the external-snapshotter sidecar in the controller plugin. A snapshot is a storage-side operation, so it needs only the controller service, and the node plugin takes no part.
Cloning and restoring
A new claim can name an existing snapshot or even an existing claim as its data source. The provisioner passes that source to the driver’s create operation, and the driver builds the new volume from it. This makes it quick to create test copies of databases or to restore after a mistake, depending on how efficiently the storage system implements copying.
Expansion
Growing a volume has two halves. The controller side makes the storage bigger. The node side then grows the filesystem inside it so the extra room is usable. Some drivers can do both while the volume stays mounted, called online expansion, while others need the Pod to be stopped first. Shrinking is not supported.
The owner edits the claim to a larger size
The class must allow expansion for this to be accepted.
The resizer sidecar calls expand on the controller service
The real storage is enlarged by the storage system.
The kubelet calls expand on the node service
The filesystem on the volume is grown to fill the new space.
The claim reports the new size
Applications can use the extra room, sometimes after a restart.
Access modes and volume modes
Read-write once
One node may mount it for reading and writing. Typical for block disks.
Read-write once Pod
Stricter: only a single Pod may use the volume at a time, even on one node.
Read-only many
Many nodes may mount it read-only, useful for shared reference data.
Read-write many
Many nodes may write at once. Needs storage designed for sharing, such as a network file system.
Separately, a volume can be presented as a filesystem, which is a mounted folder, or as a raw block device, which the application manages itself, common for databases that want direct control. The node plugin handles the difference at publish time.
A claim asking for read-write many on a driver that only supports single-node access will not be satisfied, or will behave unpredictably. Always check which access modes your driver and class really support before designing around them.
Ephemeral volumes
CSI also supports volumes that live and die with a single Pod. In the generic style, the Pod’s own definition carries a claim template, and a claim is created and removed along with the Pod, going through the normal provisioning path. Some drivers also support a lighter inline style, where the Pod names the driver directly and the node plugin does the work without any claim. These are useful for scratch space and for injecting data such as secrets from an external store.
13Idempotency, Retries and Failure Recovery
Distributed systems fail halfway through jobs. CSI is designed so that doing a step twice is always safe.
Idempotent by contract
An operation is idempotent when repeating it has the same effect as doing it once. The CSI specification requires this for most calls. If a create-volume request is repeated with the same name, the driver must return the same volume, not create a second one. If a delete is repeated for a volume that is already gone, it must succeed rather than fail. This rule makes retries safe, and retries are the main recovery tool in the whole system.
Pressing a lift call button five times does not summon five lifts. The system treats the repeated press as the same request. A good storage driver treats a repeated “create locker number 42” in the same way.
Names make idempotency possible
The provisioner gives each create request a stable name derived from the claim’s unique identity. If the provisioner crashes after the driver created the volume but before it saved the result, the next attempt uses the same name, and the driver can recognise that the volume already exists and return it. Without stable names, such a crash would leak an orphaned volume.
Timeouts and in-progress states
Storage operations can be slow. A sidecar sets a timeout on each call. If a call times out, the sidecar does not know whether the operation completed, so it retries, and the driver should answer with the current state, or with an “operation in progress” signal if the previous attempt is still running. Drivers protect themselves with locks so that two concurrent calls about the same volume do not collide.
Sidecar crash mid-operation
On restart it re-lists objects and retries, and idempotent calls make the retry safe.
Driver restart on a node
Existing mounts keep working because they are kernel mounts. New stage and publish calls wait until the driver is back and re-registered.
Node crash with attached disks
The disks remain attached to the lost node until the system decides it is safe to detach them, which can delay Pod recovery on another node.
Storage API outage
Calls fail and are retried with back-off. Running Pods are mostly unaffected, but new claims and attachments wait.
Once a volume is mounted into a running Pod, the driver is not on the data path for most drivers. The kernel handles reads and writes directly. A driver outage mainly affects creating, attaching, mounting and resizing, not the files that Pods are already using.
The hard case: a node that disappears
If a node vanishes suddenly, its attached disks cannot be cleanly unmounted. Kubernetes waits for a safety period before force-detaching, because attaching a read-write disk to a second node while the first might still be writing risks serious corruption. This is a deliberate trade of availability for safety. Newer features allow an administrator to mark a node as truly shut down, which allows faster, safe recovery. Always follow the documented procedure for your version instead of removing objects by hand.
14Deployment Layout, High Availability and Upgrades
How the pieces are packaged in a real cluster, and how to change them without trouble.
The typical layout
| Part | Workload type | Replicas | Main containers |
|---|---|---|---|
| Controller plugin | Deployment, sometimes a StatefulSet | Two or three for availability, with leader election | Driver, provisioner, attacher, snapshotter, resizer, liveness helper |
| Node plugin | DaemonSet | One per eligible node | Driver, node-driver-registrar, liveness helper |
| Snapshot controller | Deployment, one set per cluster | Two with leader election | Cluster-wide snapshot controller, shared by drivers |
| Driver settings | CSIDriver object | One per driver | Declarative options |
Installation approaches
Most drivers are installed with a packaged bundle, such as a Helm chart, an operator or a set of manifests published by the driver’s maintainers. These bundle the workloads, the access rules, the CSIDriver object and the default StorageClasses. Managed Kubernetes services often offer the main cloud driver as an add-on that the provider installs and updates for you. Using the provider’s own add-on is generally the safest choice where available.
Upgrades
Read the release notes and compatibility table
Driver, sidecars and Kubernetes versions must be compatible. Skipping this step is the most common cause of upgrade trouble.
Test in a staging cluster
Run provisioning, attach, mount, snapshot and expansion tests with realistic workloads.
Upgrade the controller plugin first
Roll its replicas one at a time, so leader election keeps at least one copy active.
Roll the node plugin gradually
Use a conservative rolling strategy, so only a few nodes are without a registered driver at a time. Existing mounts keep working through the restart.
Verify and watch
Check claim binding times, attachment success and error rates, and keep a rollback path ready.
The snapshot object definitions and the snapshot controller are shared by every driver in the cluster. Installing mismatched versions from two different driver bundles can break snapshots for all of them. Manage these shared pieces deliberately, once, not as part of each driver’s install.
15CSI Migration: Retiring the Old In-Tree Plugins
How Kubernetes moved existing users from built-in volume code to drivers without breaking their clusters.
The goal
Once CSI matured, the project set out to remove the old in-tree volume code. But thousands of clusters already had claims and volumes created through the old plugins, with old-style names in their specifications. Forcing everyone to rewrite them would have been disruptive. The solution was CSI migration: a translation layer inside Kubernetes that quietly redirects calls meant for an old in-tree plugin to the matching CSI driver.
Imagine a company replacing all its old phone extensions with a new system. Rather than asking everyone to learn new numbers, the old numbers are forwarded to the new system automatically. Callers keep dialling the same way, and the forwarding rule handles the rest.
How it works in practice
When migration is active for a volume type, the existing in-tree volume specification is translated on the fly into its CSI form, and the CSI driver does the actual work. Users do not edit their existing objects. For this to work, the matching CSI driver must be installed and healthy in the cluster. Over several releases, migration was enabled by default for the major cloud disk and file plugins, and most of the old in-tree code has since been removed from the project.
What this means for you
Install the driver before upgrading across the removal
If your Kubernetes upgrade crosses the version where an in-tree plugin was removed, the matching CSI driver must already be running, or volumes of that type will stop working.
Prefer the CSI provisioner name in new classes
New StorageClasses should name the CSI driver directly rather than the old in-tree provisioner name, so they do not depend on the translation layer.
Check feature differences
Some options available in the old plugin are named or handled differently in the CSI driver. Review class parameters and test snapshot, expansion and topology behaviour after the move.
Which plugins are migrated, enabled by default or already removed depends on your Kubernetes version. Always consult the release notes and your provider’s documentation for the exact status in your environment.
16Real-World Driver Families and Use Cases
Where CSI drivers show up, and how the controller and node roles differ between them.
Cloud block disks
The most common type. The controller plugin calls the cloud provider’s API to create, attach and detach disks. The node plugin finds the attached device, formats it the first time, and mounts it. Disks are tied to a zone and normally allow single-node read-write access, so delayed binding and topology matter a lot.
Cloud and network file systems
Drivers for managed file shares allow many nodes to read and write at once. Often no attach step exists. The controller plugin creates shares or directories, and the node plugin simply mounts the network path. Good for shared content and for workloads that need read-write many.
Enterprise storage arrays
Vendors of on-premises arrays provide drivers that talk to the array’s management interface from the controller plugin and use block or file protocols on the node plugin. Network reachability from the controller plugin to the array is a key design concern.
Distributed storage systems running inside the cluster
Cluster-native storage systems provide their own driver, so that Pods get replicated volumes carved from the disks of the cluster’s own nodes. The storage system is itself a workload, which makes startup order and failure planning more subtle.
Local volume drivers
Drivers that manage disks on each node offer very high speed and low latency, with the cost that data is tied to a node. They rely on topology to keep Pods on the right node, and need a plan for node loss.
Secrets and configuration drivers
Some drivers use inline ephemeral volumes to present secrets from an external vault as files inside a Pod, with no claim and no persistent storage at all. Only the node plugin does the work.
Backup and data protection tools
Backup products rely on snapshot support in the driver to take consistent copies of volumes, and then move the copy off to separate storage.
| Family | Needs attach step? | Typical access mode | Topology sensitivity |
|---|---|---|---|
| Cloud block disk | Yes | Read-write once | High, zone bound |
| Network file system | Usually no | Read-write many | Low |
| Local volume | No | Read-write once | Very high, node bound |
| Inline ephemeral secrets | No | Read-only | None |
17Performance and Scalability
Storage is rarely fast to create, so most tuning is about concurrency and avoiding pile-ups.
Where time goes
Creating a cloud disk may take several seconds. Attaching can take longer. Formatting a large volume adds more. Mounting is quick, but a volume with a huge number of files may be slow to apply ownership changes at mount time. Most delays are inside the storage system or the machine, not in Kubernetes itself, so it is important to know which stage is slow before you tune anything.
Tuning options
Sidecar concurrency
Each sidecar processes a limited number of items at once. Raising the worker count helps with large bursts, for instance creating hundreds of claims, but must be balanced against the storage API’s rate limits.
API rate limits
Cloud APIs throttle callers. Heavy parallel creates or attaches may be rejected and retried, so spread bursts out and watch throttling errors.
Ownership change cost
Applying file group ownership on mount can walk every file in a big volume. Newer options let the driver or kernel handle ownership more cheaply, so choose the policy that fits the driver.
Resource requests
Give the controller and node plugins enough CPU and memory. A starved plugin answers slowly and looks like a slow storage system.
Per-node volume limits
Set limits that match the machine’s real attachment capacity so the scheduler avoids placing Pods that can never attach their disks.
Scale effects
In a cluster with thousands of nodes, a node plugin on every node means thousands of registrations and thousands of per-node records. The records are small, but a rolling restart of the whole node plugin DaemonSet can briefly cause a storm of re-registrations. Pace rollouts, and make sure the API server and the driver’s own back-end can absorb the burst.
When a large job or a StatefulSet creates many claims at once, the provisioner and the cloud API can become the bottleneck, leaving Pods waiting a long time. Stagger large rollouts or pre-provision volumes for predictable bursts.
18Security Considerations
Storage drivers hold keys to data and to hosts. They deserve careful hardening.
Two privilege profiles
The two plugins are powerful in different ways. The controller plugin holds credentials for the storage system, so a compromise could let an attacker create, read through snapshots, or delete volumes across the whole environment. The node plugin holds host-level filesystem access, so a compromise could expose or change other Pods’ data on that node. Treat both as high-value targets.
Least-privilege credentials
Give the controller plugin only the storage permissions it needs, scoped to this cluster’s resources. Prefer short-lived workload identities over long-lived keys stored in the cluster.
Protected namespace
Run driver workloads in a dedicated system namespace with tight access to edit them, and with explicit exemptions for the privileged settings the node plugin needs.
Trusted images
Pin versions, verify image sources and scan images. A driver image runs with high privilege on every node.
Narrow access rules for sidecars
Each sidecar needs only specific rights on specific objects. Review the access rules shipped in bundles instead of accepting broad ones.
Secrets in classes
Some drivers need extra credentials per class or per volume, for example for a storage array. A StorageClass can point to a Kubernetes Secret and the sidecars pass its contents to the driver. Keep these Secrets in a locked-down namespace, give the sidecars permission to read only those specific Secrets, and rotate them on a schedule.
Data at rest and in transit
Encryption on the storage side
Many drivers let a class request encryption, with keys managed by the storage system or a key service. Make this a standard class option for sensitive data, not an afterthought.
Isolation between tenants
A claim in one namespace should never reach a volume of another. Use namespaces, access rules and, where needed, separate classes and separate storage accounts per tenant.
Snapshot access
Snapshots contain full copies of data. Restrict who may create claims from snapshots, because restoring is a way to read data that the original owner protected.
File ownership policy
When a Pod mounts a volume, the group ownership of its files may need adjusting so the Pod’s user can read and write. The driver’s declared policy controls whether Kubernetes does this and how. Choosing the wrong policy can either block access or recursively change ownership on large volumes, which is slow and may surprise applications that expect existing ownership to remain.
Anyone who can delete a claim with a Delete reclaim policy can destroy the underlying data. Limit deletion rights on claims that hold important data, and use Retain together with backups for volumes that must survive mistakes.
19Monitoring, Logging and Troubleshooting
How to see what the driver is doing, and how to find the stuck step when a Pod will not start.
Signals worth watching
Operation duration and errors by call
Sidecars and the kubelet expose timings and error counts for each CSI call. Watching them per call shows exactly which operation slows or fails.
Pending claims and their age
Claims that stay pending for long point to provisioning problems, topology mismatches or missing classes.
Stuck attachments
VolumeAttachment objects that stay unattached or are stuck terminating are a strong sign of storage-side or node-side trouble.
Volume usage
The kubelet reports used and free bytes and inodes for each volume. Alert before volumes fill, since a full disk causes application failures.
Plugin health and restarts
Liveness results and restart counts for controller and node plugins reveal crash loops and misconfiguration.
Capacity
Remaining capacity in the storage pool, so provisioning does not fail for lack of room.
A troubleshooting path for a Pod stuck waiting for its volume
Read the Pod and claim events
Events name the failing step and often quote the driver’s error message. Start here every time.
Is the claim bound?
If it is pending, look at the class name, the provisioner logs, topology constraints and storage capacity.
Does a VolumeAttachment exist and is it attached?
If missing, the Pod may not yet be scheduled. If present but unattached, check the attacher logs and the storage system.
Is the node plugin registered on that node?
Check the per-node record for the driver. A missing entry means the registrar or driver failed on that node.
Read the node plugin and kubelet logs
Errors from stage or publish, such as device not found, filesystem errors or mount failures, show up here.
Check the storage system itself
Quotas, throttling, permissions and outages on the storage side explain many failures that look like Kubernetes problems.
Common symptoms and likely causes
| Symptom | Likely cause | Where to look |
|---|---|---|
| Claim pending, no events from the provisioner | Class names a driver that is not installed or the provisioner is down | Class provisioner name, provisioner Pod logs |
| Claim pending with a topology error | No zone satisfies both the Pod and the class restrictions | Allowed topologies, node labels, binding mode |
| Pod stuck in container creating, attach never completes | Storage API failure or the disk is still attached elsewhere | VolumeAttachment status, attacher logs, cloud console |
| Mount fails on one node only | Node plugin unhealthy, missing tools or kernel support on that node | Node plugin logs on that node, kubelet logs |
| Application sees an empty folder | Mount propagation misconfigured in the node plugin | Plugin Pod volume mounts and propagation setting |
| Expansion shows new size on claim but not in the Pod | Filesystem expansion pending on the node | Claim conditions, node plugin logs, Pod restart need |
Raise driver log levels only while investigating. Log each CSI call with the volume identifier, the node identifier and the outcome, so a single volume’s journey can be followed across sidecar, driver and kubelet logs.
20Design Patterns and Best Practices
Habits that make storage boring and reliable.
Patterns that work
A small, well-named set of classes
Offer a few clear classes, such as standard, fast, shared and encrypted, with documented behaviour and costs. Too many classes confuse teams and multiply testing.
Delayed binding for location-bound storage
Make wait-for-first-consumer the default for any class whose volumes are tied to a zone or node.
Expansion enabled by default
Allow volume expansion in classes from day one. Growing later is easy, but enabling it afterwards for existing classes requires care.
Snapshots plus tested restores
Schedule snapshots for important data and practise restoring them. A snapshot that has never been restored is only a hope.
Platform-owned driver lifecycle
Let one team own driver installation, upgrades and compatibility checks, with a published support matrix, rather than letting each team install their own.
Spread stateful Pods across failure domains
Use topology spread rules so replicas of a database land in different zones, each with its own zonal volume, so one zone’s failure does not take them all.
Do
- Check driver, sidecar and Kubernetes version compatibility before upgrading
- Use delayed binding for zone-bound or node-bound storage
- Monitor pending claims, stuck attachments and volume usage
- Back up data independently of the cluster
- Run several controller replicas with leader election
- Give plugins appropriate CPU and memory requests
Do not
- Assume every driver supports every access mode or feature
- Delete finalizers or attachments by hand to unstick Pods without understanding why
- Restart all node plugins at once during maintenance
- Give the controller plugin broader storage credentials than needed
- Treat Delete reclaim policy as harmless for important data
- Store critical data without a snapshot and restore plan
21Common Mistakes and Anti-Patterns
Failures that appear again and again in real clusters.
Problem
A zone-bound StorageClass uses immediate binding, so volumes are created before Pods are scheduled.
Why It’s Harmful
The volume may land in a zone that has no free capacity for the Pod, or where the Pod’s other constraints cannot be met. The Pod then stays pending permanently, and the volume cannot move.
Correct Approach
Use wait-for-first-consumer binding so the volume is created in the zone the scheduler chose for the Pod.
Problem
Important production data sits on volumes with the Delete reclaim policy and no backups.
Why It’s Harmful
Deleting a claim, a namespace or a whole StatefulSet by mistake destroys the real storage immediately and irrecoverably.
Correct Approach
Use Retain for critical data, restrict delete rights, schedule snapshots and keep backups outside the cluster.
Problem
Operators remove finalizers from stuck VolumeAttachment or volume objects to make errors disappear.
Why It’s Harmful
The objects vanish but the real disk may still be attached to the old node, or mounted there. A new Pod on another node may then fail, or worse, two nodes may write to one disk and corrupt it.
Correct Approach
Find out why detach is stuck, check the storage system and the node, and use the documented recovery procedure for your version.
Problem
The node plugin is deployed without the correct mount propagation or without the registrar sidecar, based on a trimmed-down sample.
Why It’s Harmful
Either the driver never registers on nodes, or mounts exist in the plugin’s private view and applications see empty folders, which looks like data loss.
Correct Approach
Use the maintainers’ published manifests or chart as the baseline, and change only the settings that you understand.
Problem
Teams request read-write many access on a block-disk driver, assuming it will behave like shared storage.
Why It’s Harmful
Either provisioning fails, or two Pods on different nodes cannot mount the volume, or an unsafe sharing arrangement leads to corruption in applications that do not coordinate writes.
Correct Approach
Use a shared file system driver when many writers are needed, and keep block disks for single-writer workloads.
Problem
Upgrading Kubernetes across the removal of an in-tree plugin without first installing the matching CSI driver.
Why It’s Harmful
Existing volumes of that type can no longer be attached or mounted, causing widespread Pod failures right after the upgrade.
Correct Approach
Read the release notes before every upgrade, install and verify the CSI driver beforehand, and test with a few volumes in a staging cluster.
Problem
Plugins run without resource requests or limits, and node plugin upgrades are rolled out across all nodes at once.
Why It’s Harmful
Starved plugins respond slowly and time out, and a simultaneous restart leaves many nodes unable to stage or publish volumes at the same moment, stalling Pod starts cluster-wide.
Correct Approach
Set sensible requests for both plugins, use a conservative rolling strategy for the node DaemonSet, and upgrade in small batches.
22Advantages, Disadvantages and Trade-offs
An honest balance sheet for the CSI approach.
Advantages
- One standard way to use any storage system, without changing Kubernetes
- Drivers ship, fix and upgrade independently of Kubernetes releases
- Driver crashes are isolated from core components such as the kubelet
- Clear division of privilege between cluster-level and node-level work
- Rich shared features such as snapshots, clones, expansion and topology awareness
- Reusable sidecars, so driver authors only write storage logic
Disadvantages / Trade-offs
- More components to install, version, secure and monitor
- Privileged node plugins widen the attack surface of every node
- Feature support varies a lot between drivers, so portability is partial
- Version compatibility between driver, sidecars and Kubernetes needs attention
- Failures at hand-off points between sidecars, driver and kubelet can be hard to trace
- Storage still has physical limits such as zones, attach counts and API rate limits
Choosing a storage style for a workload
| Workload | Good fit | Why |
|---|---|---|
| Single-instance database needing durable storage | Cloud block disk with delayed binding | Fast, durable, single writer |
| Replicated database with its own replication | Local volumes or block disks, spread across zones | Application handles redundancy, storage focuses on speed |
| Shared content read and written by many Pods | Network file system driver | Read-write many by design |
| Scratch space during a job | Generic ephemeral volume | Created and cleaned with the Pod |
| Secrets from an external vault | Inline ephemeral CSI volume | No persistent storage needed |
| Data that needs point-in-time recovery | Driver with snapshot support plus tested restores | Fast copies and clean restores |
23Frequently Asked Questions
Questions that beginners and experienced engineers keep asking.
The specification is separate and shared with other orchestrators. Kubernetes includes the machinery that talks CSI, such as the kubelet’s volume handling and the API objects, but the drivers and most sidecars live outside the core code and are released on their own schedules.
Because the work happens in two different places. Some actions affect the storage system as a whole and can run anywhere, others must run on the machine that will use the volume. Splitting them gives each the right privileges and scale, and keeps broad storage credentials away from every node.
Sidecars are generic helper controllers that watch Kubernetes objects and call the driver’s CSI operations. This keeps drivers free of Kubernetes-specific logic, so one driver can work with several orchestrators, and driver authors write only storage code.
Stage prepares a volume once per node, such as finding the device, formatting it and mounting it at a shared path. Publish makes it appear inside one Pod’s directory, usually by a bind mount from the staging path. This lets many Pods on one node share one prepared volume.
No. Block disks usually do, because they must be connected to a machine first. Network file systems typically do not, because nodes simply mount a network path. The driver declares which style it uses in its CSIDriver object.
Mounted volumes usually keep working because the kernel handles the data path. What stops is new work: creating, attaching, mounting, resizing and snapshotting. Once the driver returns and re-registers, those operations resume.
Binding only means a volume exists for the claim. The Pod may still be waiting for scheduling constraints, for the attach to complete, or for the node plugin to stage and publish. Look at the Pod’s events and at the VolumeAttachment to see which step is active.
Because location-bound volumes must be created where the Pod will run. Delayed binding waits for the scheduler’s decision and then creates the volume in the chosen node’s zone, avoiding Pods that can never mount a volume created somewhere else.
Not directly. A zonal volume cannot be attached across zones. The usual approach is to take a snapshot and create a new volume from it in the target zone, if the driver supports snapshots and cross-zone restore.
Not without understanding why it is stuck. Removing it hides the symptom while the real disk may still be attached or mounted elsewhere, which risks corruption. Investigate the node, the storage side and the documented recovery steps first.
It is a translation layer that redirects the old in-tree volume plugins to matching CSI drivers so existing objects keep working. You need to ensure the matching CSI driver is installed before your cluster version crosses the point where the old plugin is disabled or removed. Check the release notes for your version.
Yes, and this is common. Each driver has its own name, its own controller and node plugins and its own classes, and claims pick one through the class. Shared pieces such as the snapshot controller and snapshot definitions must be installed once for the whole cluster.
24Summary and Key Takeaways
The Container Storage Interface replaced storage code built into Kubernetes with a standard contract spoken over local gRPC sockets. A driver offers up to three services: Identity for self-description, Controller for whole-system actions such as create, delete, attach, snapshot and expand, and Node for machine-level actions such as stage, publish, unpublish and unstage. In practice that becomes two deployments. The controller plugin runs a few replicas with generic sidecars that watch Kubernetes objects and call the driver, and holds the storage credentials. The node plugin runs on every node, is called by the kubelet, registers itself through a registrar sidecar, and holds host-level mount access. Supporting objects such as CSIDriver, CSINode and VolumeAttachment describe driver behaviour, node capabilities and attach progress.
Return to the apartment building from the start. The back office allocates your locker and records its number. The porter on your floor brings it to your door. Neither does the other’s job, each is repeatable and safe to ask twice, and a clear register lets anyone see where things stand. If you remember that picture, and choose delayed binding for location-bound storage, protect both privilege profiles, monitor each step and test your restores, CSI storage becomes predictable and quietly reliable.
Key Takeaways
- CSI is a contract, not a product — one standard interface over local gRPC lets any storage system plug into Kubernetes without changing Kubernetes, ending the old in-tree model.
- Controller plugin versus node plugin — cluster-level actions like create, attach and snapshot run in a few replicas with storage credentials, while machine-level stage and publish run on every node with host access.
- Sidecars translate Kubernetes to CSI — provisioner, attacher, snapshotter, resizer and registrar watch objects or the kubelet and call the driver, so drivers contain only storage logic.
- The lifecycle is a chain of small, idempotent steps — create, bind, schedule, attach, stage, publish and then the reverse, each safe to retry because stable names and idempotent calls prevent duplicates.
- Location matters — use delayed binding and topology for zone-bound or node-bound storage, and respect per-node volume limits and access modes.
- Protect both privilege profiles — least-privilege storage credentials for the controller, trusted images and a guarded namespace for the privileged node plugin, plus careful reclaim policies and deletion rights.
- Operate it with evidence — monitor pending claims, stuck attachments, per-call durations and volume usage, follow the troubleshooting path from events to attachments to node logs, and never strip finalizers to hide a symptom.