CSI Framework And Mount Lifecycle
This page describes the behavior implemented in curvine-csi on the current main branch, not an older Helm-based design.
Core Modelâ
Curvine CSI has three moving parts:
| Component | Source | Responsibility |
|---|---|---|
| Controller service | pkg/csi/controller.go | Validates provisioning parameters, creates or deletes Curvine directories, and returns the final VolumeContext |
| Node service | pkg/csi/node.go | Starts or reuses FUSE, computes host mount paths, and bind-mounts the right subdirectory into pods |
| FUSE process manager | pkg/csi/fuse_manager.go | Launches /opt/curvine/curvine-fuse with the generated mount path and pass-through FUSE flags |
Mount Modesâ
pkg/csi/driver.go reads MOUNT_MODE:
standalone: default for node pods and the recommended modeembedded: FUSE runs inside the CSI pod
Operationally:
standaloneisolates FUSE lifecycle from CSI pod restarts by running FUSE in dedicated standalone pods.embeddedis simpler, but restarting or upgrading the CSI node pod also interrupts the FUSE process.
The shipped manifests reflect that split:
deploy/daemonset.yaml:MOUNT_MODE=standalonedeploy/deployment.yaml:MOUNT_MODE=embedded
Dynamic Provisioning Flowâ
1. StorageClass Validationâ
ValidateStorageClassParams in pkg/csi/validator.go enforces:
master-addrsmust be present and parse ashost:port,host:port,...fs-pathdefaults to/when omittedpath-typedefaults toDirectory- legacy user-supplied
mnt-pathis still accepted, but the code path now auto-generates mount paths and treats manualmnt-pathas compatibility behavior
The validator also accepts a set of optional FUSE tuning keys and forwards them to the node-side FUSE start logic.
2. Controller Outputâ
For dynamic volumes, CreateVolume in pkg/csi/controller.go generates:
cluster-id = sha256(master-addrs)[:8]
volumeHandle = {cluster-id}@{fs-path}@{pv-name}
curvine-path = {fs-path}/{pv-name}
It then stores these values in VolumeContext for later node-side operations.
path-type controls directory creation:
Directory: parent path and final volume path must already existDirectoryOrCreate: missing directories are created by the controller
Node-Side Mount Generationâ
Cluster IDâ
pkg/csi/volume_handle.go derives:
cluster-id = first 8 hex chars of SHA256(master-addrs)
This ID is part of the dynamic volumeHandle.
Mount Keyâ
The node service and helper functions derive a separate mount-key from:
master-addrs + fs-path
That value is then used to generate the host FUSE mount point:
/var/lib/kubelet/plugins/kubernetes.io/csi/curvine/{mount-key}/fuse-mount
This is the key point for reuse: the shared FUSE process is grouped by master-addrs + fs-path, not by PVC name.
FUSE Reuse Rulesâ
Dynamic Volumesâ
For dynamic provisioning, volumes created from the same:
master-addrsfs-path
reuse the same node-side FUSE mount on a given Kubernetes node. Each PVC still gets its own curvine-path underneath that shared mount.
Static PVsâ
The static PV example uses:
- arbitrary
volumeHandle - required
master-addrs - required
curvine-path
Static PVs do not need the structured dynamic volumeHandle. When fs-path is omitted, the node code falls back to / before generating the mount key. That means static PV reuse is effectively keyed by master-addrs + / unless you add an explicit fs-path attribute yourself.
This behavior is inferred from the current node.go implementation, where missing fs-path is replaced with / before mount-key generation.
How NodePublishVolume Finds The Pod Subpathâ
NodePublishVolume in pkg/csi/node.go computes the host subpath differently depending on the mounted root:
- If
fs-path == /, the host subpath ismnt-path + curvine-path - If
fs-path != /, the host subpath ismnt-path + relative(curvine-path, fs-path)
Examples from the implementation comments:
fs-path="/", curvine-path="/pvc-abc"
=> host subpath = <mnt-path>/pvc-abc
fs-path="/test-data", curvine-path="/test-data/pvc-abc"
=> host subpath = <mnt-path>/pvc-abc
This is why multiple PVCs can share one FUSE process while still landing in different directories.
FUSE Command Constructionâ
pkg/csi/fuse_manager.go starts FUSE as:
/opt/curvine/curvine-fuse --master-addrs ... --fs-path ... --mnt-path ...
It always injects:
--master-addrs--fs-path--mnt-path
and then appends any CSI-supplied FUSE tuning flags.
The curvine-fuse CLI in the main reference exposes these documented flags directly:
--io-threads--worker-threads--mnt-per-task--clone-fd--fuse-channel-size--stream-channel-size--direct-io--cache-readdir--entry-timeout--attr-timeout--negative-timeout--max-background--congestion-threshold--node-cache-size--node-cache-timeout--mnt-number
The validator also still accepts several older keys such as auto-cache, kernel-cache, master-hostname, master-rpc-port, and master-web-port. Those keys are not separately documented on the current curvine-fuse CLI page, so treat them as compatibility behavior rather than a stable public contract.
Lifecycle Summaryâ
- The controller validates StorageClass parameters and creates
curvine-path. - The controller returns
VolumeContextcontainingmaster-addrs,fs-path, andcurvine-path. - The node plugin computes
cluster-id,mount-key, andmnt-path. - If a compatible FUSE process already exists for that
master-addrs + fs-pathpair, the node reuses it. - Otherwise, the node starts a new
curvine-fuseprocess. - The node bind-mounts the correct subdirectory into the pod.
- When reference count reaches zero,
NodeUnstageVolumestops the unused FUSE mount.
Static Versus Dynamic PVsâ
| Mode | Required attributes | Controller behavior | Node behavior |
|---|---|---|---|
| Dynamic PVC via StorageClass | master-addrs, optional fs-path, optional path-type | Generates volumeHandle and curvine-path | Mounts fs-path, then bind-mounts the generated subdirectory |
| Static PV | master-addrs, curvine-path | No directory naming convention is generated for you | Mounts root by default if fs-path is omitted, then bind-mounts curvine-path |
Operational Guidanceâ
- Prefer
standalonefor production because CSI restarts do not kill the business FUSE process. - Set
fs-pathexplicitly in StorageClasses even though the code defaults it to/. That makes reuse behavior and directory layout predictable. - Do not depend on manual
mnt-pathassignment in new deployments; the main branch implementation already auto-generates it.