Kubernetes v1.21 [alpha](disabled by default)CSI volume health monitoring lets a CSI
driver report health problems with a volume or with its storage backend directly to
Kubernetes. The driver reports through CSI RPCs, and Kubernetes surfaces the reports on
three status fields:
PersistentVolumeClaim
.status.healthStatus, Pod
.status.volumeHealth, and
CSINode .status.storageHealth.
Automation can watch these durable status fields instead of
having to reconstruct volume health from ephemeral
Events or from vendor-specific
dashboards.
CSIVolumeHealth feature gate has existed since Kubernetes v1.21, but the
mechanism described on this page is a redesign that replaces the original
alpha implementation. See Limitations for what changed.The CSI spec defines four RPCs for health reporting:
ControllerListVolumeHealth and
ControllerGetVolumeHealth, for controller-observed, per-volume health.NodeGetVolumeHealth, for node-observed, per-volume
health, and NodeGetStorageHealth, for the health of the storage backend as seen
from that node.A driver only needs to implement the RPCs it wants to support, and advertises support
through CSI plugin capabilities. A driver that implements none of these RPCs is never
probed, and reporting stays dormant for that driver. A driver that advertises the
controller LIST_VOLUME_HEALTH capability must also advertise GET_VOLUME_HEALTH;
the csi-external-health-monitor-controller sidecar enforces this requirement.
Each health report carries a status drawn from a small, machine-parseable set of
values, together with a driver-defined reason and an optional human-readable
message:
PersistentVolumeClaim.status.healthStatus
and Pod.status.volumeHealth): Inaccessible, DataLoss, Degraded.CSINode.status.storageHealth):
StorageUnreachable, StorageDegraded.The node-side and controller-side reports are independent: a volume can be
Inaccessible from one node that lost its data path to the backend while the
controller plugin still reports the volume as healthy, and vice versa.
For volumes that use a CSI driver supporting the node-side NodeGetVolumeHealth RPC,
the kubelet periodically calls that
RPC for each CSI volume it has mounted for a Pod, and writes the result to
pod.status.volumeHealth, keyed by the volume name from pod.spec.volumes. The probe
interval is the kubelet's volumeStatsAggPeriod setting (the
--volume-stats-agg-period command line flag).
apiVersion: v1
kind: Pod
# ...
status:
volumeHealth:
- name: my-volume
healthConditions:
- status: Inaccessible
reason: VolumeNotFound
message: "volume not found on the storage backend"
lastTransitionTime: "2026-07-20T12:00:00Z"
The kubelet only writes pods/status, a subresource it is already authorized to
update for Pods bound to its own node, so no new authorization is required for this
field.
For each CSI driver registered on a node that supports NodeGetStorageHealth, the
kubelet periodically calls that RPC and writes the result to
csinode.status.storageHealth, keyed by driver name.
apiVersion: storage.k8s.io/v1
kind: CSINode
# ...
status:
storageHealth:
- name: csi.example.com
healthConditions:
- status: StorageUnreachable
reason: NetworkPartition
message: "data path to the storage backend is unreachable from this node"
A StorageHealthCondition entry can optionally scope itself to a specific
accessMode or volumeMode, for backends that degrade asymmetrically (for example, a
network problem that affects ReadWriteMany access but not ReadWriteOnce).
Writing to csinodes/status is a new capability added by this feature: the
Node authorization mode and the
NodeRestriction admission plugin only allow a kubelet to patch the CSINode object
that matches its own node, and only while the CSIVolumeHealth
feature gate is
enabled.
Controller-observed volume health is written to persistentvolumeclaim.status.healthStatus
by the csi-external-health-monitor-controller sidecar that runs alongside a CSI
driver's controller plugin. The sidecar calls ControllerListVolumeHealth (or falls
back to ControllerGetVolumeHealth per volume) and writes the result:
apiVersion: v1
kind: PersistentVolumeClaim
# ...
status:
healthStatus:
healthConditions:
- status: Inaccessible
reason: VolumeNotFound
message: "volume not found on the storage backend"
lastTransitionTime: "2026-07-20T12:00:00Z"
No node ever writes this field; only the sidecar, running in the control plane, does. This keeps a compromised or misbehaving node from being able to influence what other users of the cluster see on a PVC.
Whether this path is available for a given driver depends on that driver, and its
deployment of the csi-external-health-monitor-controller sidecar, having adopted the
new controller RPCs.
Volume health monitoring is controlled by a single
feature gate,
CSIVolumeHealth, on both kube-apiserver and the kubelet:
kube-apiserver, enabling the feature gate allows the new status fields to be
written and read; disabling it drops the fields on the next write to the object,
while preserving values already stored.Controller-side monitoring, which populates persistentvolumeclaim.status.healthStatus,
does not have its own feature gate. Deploying the csi-external-health-monitor-controller
sidecar alongside your CSI driver's controller plugin is itself the controller-side
opt-in.
Enabling the feature gate does not, by itself, cause any health information to appear: a CSI driver also has to advertise and implement the corresponding RPCs. Check your CSI driver's documentation to see which of the four RPCs, if any, it supports.
The kubelet exposes a csi_node_storage_health_status gauge metric, labeled by
driver_name, status, and reason, with a value of 1 for each storage-backend
condition currently reported for a driver on that node.
CSIVolumeHealth feature gate (available
starting in Kubernetes v1.21) reported abnormal volume conditions using Kubernetes
Events and a kubelet_volume_stats_health_status_abnormal metric. That mechanism
has been replaced by the status fields and RPCs described on this page, and no
longer exists.