Helm Chart Reference¶
This page describes what the meshstor-csi Helm chart deploys and the operational shape of each component. Pair it with the charts/meshstor-csi/README.md, which is auto-generated from values.yaml annotations and lists every key.
Chart Coordinates¶
| Field | Value |
|---|---|
| Name | meshstor-csi |
| Source | oci://ghcr.io/meshstor/charts/meshstor-csi |
| Repository | https://github.com/meshstor/meshstor-csi (chart at charts/meshstor-csi/) |
| Minimum Kubernetes | 1.27.0 |
appVersion |
Pins the default image tag — when image.tag is empty, the chart's appVersion is used. |
OCI installs require Helm 3.8.0 or newer. See Installation for the install/upgrade commands.
Resources Installed¶
A default install (crds.enabled=true, rbac.create=true, serviceAccount.*.create=true, default storageClasses) creates:
| Kind | Name(s) | Notes |
|---|---|---|
CustomResourceDefinition |
meshstorvolumes.csi.meshstor.io, meshstornodedevices.csi.meshstor.io |
Annotated helm.sh/resource-policy: keep when crds.keep=true (default), so helm uninstall leaves them — see CRD Lifecycle. |
CSIDriver |
io.meshstor.csi.mesh |
attachRequired: false, podInfoOnMount: false. |
StatefulSet |
<release>-controller |
1 replica, system-cluster-critical. |
DaemonSet |
<release>-node |
Every Linux node, system-node-critical, hostNetwork: true. |
ServiceAccount |
<release>-controller, <release>-node |
Split per role so IRSA / Workload Identity can be attached to one. |
ClusterRole + ClusterRoleBinding |
<release>-controller, <release>-node |
Cluster-scoped CR access, PV/PVC, events, storage classes. |
Role + RoleBinding |
<release>-controller (release namespace) |
coordination.k8s.io/leases for csi-resizer leader election. |
StorageClass |
meshstor-replicated (default), meshstor-local-storage |
Configurable via storageClasses — see Default StorageClasses. |
(templated extraObjects) |
user-supplied | Each entry is rendered through tpl against the release context. |
Resource names are derived from the release name. Override with --set fullnameOverride=… to run multiple instances side-by-side.
Controller Pod¶
A single-replica StatefulSet with four containers sharing a socket via emptyDir.
| Container | Image | Purpose |
|---|---|---|
csi-plugin |
ghcr.io/meshstor/meshstor-csi |
The driver binary, started with --role=controller. Handles CreateVolume, DeleteVolume, ListVolumes, GetCapacity, and the controller-side reconciliation. |
csi-provisioner |
registry.k8s.io/sig-storage/csi-provisioner |
Watches PVCs and calls CreateVolume. No leader election — duplicating the controller would issue duplicate provision calls, which is why the StatefulSet is hardcoded to one replica. |
csi-resizer |
registry.k8s.io/sig-storage/csi-resizer |
Watches PVC capacity edits and calls ControllerExpandVolume. Uses leader election (--leader-election); the lease lives in the release namespace. |
liveness-probe |
registry.k8s.io/sig-storage/livenessprobe |
Exposes /healthz for the kubelet probe on port 9909. |
| Property | Default | Why |
|---|---|---|
replicas |
1 |
csi-provisioner has no leader election in the driver pipeline. |
priorityClassName |
system-cluster-critical |
Ensures controller stays scheduled during cluster pressure. |
containerSecurityContext (csi-plugin) |
runAsNonRoot, runAsUser: 65532, readOnlyRootFilesystem, drop: [ALL] |
The controller only talks to the Kubernetes API. PSA restricted compatible. |
tolerations |
key: CriticalAddonsOnly |
Lands on dedicated control-plane / addon nodes if you isolate them. |
nodeSelector |
kubernetes.io/os: linux |
Prevents scheduling onto Windows nodes. |
terminationGracePeriodSeconds |
300 |
Must exceed worst-case in-flight DeleteVolume. |
Node Pod (DaemonSet)¶
One pod per Linux node. hostNetwork: true — NVMe-oF connections bind to the node's real IPs, and the host paths the driver needs (/dev, /sys/kernel/config, kubelet dir) are mounted in.
| Container | Image | Purpose |
|---|---|---|
csi-plugin |
ghcr.io/meshstor/meshstor-csi |
The driver binary, started with --role=node. Handles NodeStageVolume, NodePublishVolume, partition lifecycle, MD RAID assembly, NVMe-oF target/host. |
node-driver-registrar |
registry.k8s.io/sig-storage/csi-node-driver-registrar |
Registers the driver with the kubelet's plugin registration directory. |
liveness-probe |
registry.k8s.io/sig-storage/livenessprobe |
Exposes /healthz for the kubelet probe on port 9909. |
| Property | Default | Why |
|---|---|---|
containerSecurityContext (csi-plugin) |
privileged: true |
Runs mdadm, nvme-cli, mount(2); bind-mounts /dev and /sys/kernel/config. Cannot be reduced without losing functionality. |
hostNetwork |
true |
NVMe-oF target/host need real node IPs; required for both TCP and RDMA transports. |
| Mount propagation | Bidirectional on the kubelet dir, HostToContainer on /sys/kernel/config |
Bidirectional propagation lets the kubelet see mount(2) results from inside the container. |
tolerations |
operator: Exists |
Lands on every node, including tainted ones. |
updateStrategy.rollingUpdate.maxUnavailable |
1 |
One node at a time during rollouts. Increase only if you're confident workloads can tolerate multiple node plugins down simultaneously. |
priorityClassName |
system-node-critical |
The node plugin is on the data path; eviction would unmount volumes. |
terminationGracePeriodSeconds |
300 |
Must exceed worst-case in-flight mdadm --create + mkfs.xfs + NVMe-oF connect retries, otherwise a rolling upgrade can SIGKILL mid-orchestration. |
| Host paths | /dev, /sys/kernel/config, /run/udev, kubelet dir, plugin/registration subdirs |
All required; do not remove. |
The node pod also runs a preStop that deletes its CSI socket, so kubelet rebinds cleanly on restart.
Container Images¶
The driver and four sidecars are configured separately so a sidecar bump is one focused PR.
| Component | Default registry | Default image | Tag policy |
|---|---|---|---|
| Manager (driver) | ghcr.io |
meshstor/meshstor-csi |
image.tag falls back to chart appVersion. |
csi-provisioner |
registry.k8s.io |
sig-storage/csi-provisioner |
Pinned in sidecars.provisioner.image.tag. |
csi-resizer |
registry.k8s.io |
sig-storage/csi-resizer |
Pinned in sidecars.resizer.image.tag. |
node-driver-registrar |
registry.k8s.io |
sig-storage/csi-node-driver-registrar |
Pinned in sidecars.nodeDriverRegistrar.image.tag. |
livenessprobe |
registry.k8s.io |
sig-storage/livenessprobe |
Pinned in sidecars.livenessprobe.image.tag. |
pullPolicy defaults to IfNotPresent for every image. imagePullSecrets accepts a string, a list of names, or a list of {name: …} dicts; the chart references existing secrets — it does not create them from username/password.
CRD Lifecycle¶
| Value | Default | Effect |
|---|---|---|
crds.enabled |
true |
Chart renders the two CRDs into templates/crds/. Set to false to manage CRDs out-of-band (Argo CD pre-sync hook, kubectl apply, etc.). |
crds.keep |
true |
Adds helm.sh/resource-policy: keep to each CRD. helm uninstall leaves the CRDs in place, so existing MeshStorVolume and MeshStorNodeDevice resources survive a reinstall. |
Setting crds.keep=false is destructive
Without the keep annotation, helm uninstall deletes the CRDs, which cascades to delete every MeshStorVolume in the cluster — and that destroys the underlying data path state. Only use keep=false if you also keep your own out-of-band backups of the CRs and partitions.
Helm does not upgrade CRDs on helm upgrade — they are installed only on the first helm install. Releases that change CRD schema include reapply instructions in the release notes.
Default StorageClasses¶
The chart ships with two StorageClasses; storageClasses is a list, so you can add your own or set it to [] to skip creation entirely.
| Name | Default? | Replication | Use case |
|---|---|---|---|
meshstor-replicated |
yes | replicaCount: 2, stripeWidth: 1 (RAID1) |
General-purpose replicated block storage. |
meshstor-local-storage |
no | replicaCount: 1, stripeWidth: 1 (RAID1 with placeholder) |
Local-only with relocation support. See replicaCount=1 positioning. |
Both default to reclaimPolicy: Delete, allowVolumeExpansion: true, and memberMissingTimeout: "900". Per-StorageClass tunables are in StorageClass Parameters.
Two fields are intentionally not exposed per StorageClass:
volumeBindingModeis hardcoded toWaitForFirstConsumer. The driver doesn't consume CSI topology hints today, but binding deferral still avoids zombie PVs from PVCs that are never claimed.allowedTopologiespasses through to the rendered StorageClass but is not honored by the driver — placement is driven byinternal/nodeinfo's scoring algorithm, not by Kubernetes topology. Setting it does not constrain where MeshStor places replicas.
Cluster Defaults¶
| Value | Default | Effect |
|---|---|---|
defaultRelocationReservePercent |
10 |
Each node keeps roughly this percent of its raw capacity free so a failed node's RAID-10 replicas can be relocated to survivors. Set to 0 to disable the gate cluster-wide. |
| Per-node override | meshstor.io/relocation-reserve-percent label |
Overrides the cluster default for a single node. |
The value is passed to both controller and node binaries via --default-relocation-reserve-percent.
RBAC¶
rbac.create=true (default) renders a ClusterRole for the controller and one for the node, plus their bindings, plus a Role/RoleBinding pair in the release namespace for csi-resizer's leader-election leases.
The controller ClusterRole holds the union of node-side rules plus PVC, PV, pod, event, and storage class verbs needed by csi-provisioner. The node ClusterRole is generated from _generated-rules-node.tpl and tracks the verbs the node plugin needs against the MeshStor CRDs.
Set rbac.create=false if you manage RBAC out-of-band (sealed-secrets, vault, GitOps).
Common Overrides¶
The full list lives in the chart README. The values most often tuned in production:
| Override | Purpose |
|---|---|
image.tag |
Pin a specific manager image instead of tracking appVersion. |
imagePullSecrets |
Reference an existing pull secret for private registries. |
defaultRelocationReservePercent |
Adjust the cluster-wide reserve gate for relocation capacity. |
controller.resources / node.resources |
Set CPU/memory requests and limits. Empty by default. |
controller.tolerations / node.tolerations |
Re-target the controller off CriticalAddonsOnly nodes, or restrict the DaemonSet to a subset. |
node.kubeletDir |
Change from /var/lib/kubelet if your distribution uses a different path (k0s, microk8s, talos). |
storageClasses |
Replace the bundled StorageClasses with your own list, or set to [] to install none. |
crds.enabled / crds.keep |
GitOps-managed CRDs, or accept CRD deletion on uninstall (destructive — see CRD Lifecycle). |
extraObjects |
Add NetworkPolicies, ConfigMaps, alert rules, or extra StorageClasses without forking the chart. Each entry is tpl-rendered. |
Security Posture¶
| Component | Privilege | Pod Security Admission |
|---|---|---|
| Controller pod | privileged: false, runAsNonRoot, readOnlyRootFilesystem, drop: [ALL] |
restricted compatible. |
| Node pod (csi-plugin) | privileged: true |
privileged (required — mdadm/nvme-cli, host paths). |
| Node pod (sidecars) | allowPrivilegeEscalation: false, readOnlyRootFilesystem, drop: [ALL] |
Unprivileged inside the privileged pod. |
Both pods set seccompProfile: RuntimeDefault, which the PSA baseline policy requires for privileged pods. If you label the release namespace with PSA enforcement, use enforce=privileged for namespaces hosting the node DaemonSet — the controller alone fits restricted, but the node pod cannot.
What's Next¶
- Installation — install and upgrade commands
- StorageClass Parameters — tunables exposed through StorageClass
parameters - Compatibility — supported Kubernetes versions, kernel requirements, distributions
- Prerequisites — node-side setup the chart does not handle (udev rule, drive selection labels)