January 10, 2018·4 min read·engineeringkubernetesstoragecsi

CSI separates storage drivers from Kubernetes releases

Kubernetes 1.9 introduces an alpha CSI integration. Its node-side implementation shows how external drivers fit into the existing volume lifecycle.

Storage integration has made the Kubernetes release process part of a storage vendor's delivery process. An in-tree volume plugin ships inside Kubernetes binaries, so changes to that plugin depend on changes to Kubernetes itself. The January 10 CSI introduction explains how the alpha implementation in Kubernetes 1.9 changes that arrangement. Vendors can package external drivers in containers while applications continue to request storage through PersistentVolumeClaims.

I value Kubernetes for the way controllers converge declared state toward actual state. Extending that model to independently delivered storage drivers makes sense to me. It preserves the application's storage declarations while moving vendor implementation work outside the core project. The useful question is how much coordination Kubernetes still needs to perform after that separation.

The node still has a volume lifecycle

The Kubernetes 1.9.0 CSI mounter implementation provides a concrete answer. Kubernetes retains a CSI adapter inside its volume machinery. The adapter implements the existing mounter and unmounter interfaces, and it calls a CSI client when storage must become available at a pod's mount path. Externalizing the driver therefore does not remove the kubelet's responsibility for preparing the pod's volumes.

The implementation begins setup by checking whether the target directory is already mounted. An existing mount causes setup to return successfully without another publish operation. That check matters because setup describes a required state which may already exist. Repeating the operation should not require a second mount over the first one. This is a small example of why reconciliation needs knowledge of current state even when another process performs the storage operation.

Setup then extracts the CSI source from the volume specification. Before publishing the volume, it checks the supported interface version and probes the driver. Either failure stops setup. A running container alone is consequently insufficient evidence that a CSI volume can mount. The kubelet must be able to communicate with a driver that accepts the expected protocol and responds to the probe.

The code also obtains attachment metadata through a Kubernetes VolumeAttachment object. Its lookup derives the attachment identity from the volume handle, driver name and node name. When the mounter has no cached volume information, it fetches that object through the alpha storage API and takes the attachment metadata from its status. A failure to retrieve the attachment prevents the subsequent node publish call. The node-side operation depends on recorded attachment state as well as access to the external process.

Mounting has local state as well as API state

Before invoking NodePublishVolume, the mounter creates the target directory and saves a small JSON file. That file records the volume handle and driver name along with other identifiers used during teardown. The setup path treats failure to save this information as an error. It attempts to remove the mount directory and returns without publishing the volume.

This ordering explains a practical property of the implementation. A mount requires enough information on disk for a later teardown to identify it. The Kubernetes API can describe the volume, but the local cleanup path also needs the identity of the external driver and its volume. Persisting those details before publishing connects setup to cleanup across separate calls into the volume implementation.

The publish call passes the volume identifier, read-only setting and target directory to the CSI client. It also supplies the access mode, attachment metadata and volume attributes. In this alpha implementation, volume attributes come from a JSON-encoded PersistentVolume annotation named csi.kubernetes.io/volume-attributes. The decoder returns an error if that annotation contains invalid JSON. Driver-specific data therefore has a concrete representation which the adapter must decode before it can request a mount.

For example, the annotation decoder accepts this shape, with the illustrative key interpreted by the driver itself.

metadata:
  annotations:
    csi.kubernetes.io/volume-attributes: '{"example-key":"example-value"}'

This fragment documents the annotation format in the version-pinned source. It is not a complete PersistentVolume definition or a promise that an arbitrary driver recognizes the example key. The adapter converts the encoded object into string attributes and passes them onward. Understanding that distinction helps separate an annotation parsing failure from a driver rejecting a parameter.

Alpha behavior is visible in the implementation

The mount call supplies ext4 directly. A nearby comment identifies filesystem selection as work still needed. The code also defaults the access mode to ReadWriteOnce, then uses the first declared access mode when the list exists. These details put limits on what a successful alpha integration demonstrates. An interface capable of carrying storage operations does not imply that this Kubernetes adapter already expresses every storage choice.

The announcement's alpha limitations also exclude block volumes and scheduler topology awareness. Those restrictions affect which workloads can use this integration. A filesystem-based demonstration cannot establish suitability for a workload which needs a raw block device or coordinated placement with zonal storage. CSI's independent driver delivery is valuable even while those Kubernetes integration capabilities remain incomplete.

I regard the local teardown implementation as equally relevant to the design. It checks whether the directory is mounted, loads the saved volume information and constructs a client for the recorded driver when needed. After a supported-version check, it calls NodeUnpublishVolume. Cleanup proceeds only after that call succeeds. An external driver must participate in removing the mount as well as creating it.

The directory-removal helper checks mount state again before deleting paths. It removes the mount directory and associated volume data only when the directory is no longer mounted. That keeps the adapter's local bookkeeping tied to the filesystem state it can observe. The external process changes who performs the storage operation, but Kubernetes still owns the ordering and error handling around the pod's mount directory.