Simplyblock Operator
The simplyblock Kubernetes operator provides a declarative, Kubernetes-native interface for managing simplyblock storage infrastructure. Instead of using the CLI, administrators can define storage clusters, storage nodes, pools, and logical volumes as Kubernetes Custom Resource Definitions (CRDs). The operator continuously reconciles the desired state with the actual state of the simplyblock cluster.
Overview
The operator manages the following Custom Resource Definitions (CRDs):
| CRD | Short Name | Description |
|---|---|---|
StorageCluster |
- | Creates and manages a simplyblock storage cluster |
StorageNodeSet |
- | Fleet-level declarative management of storage nodes across workers |
StorageNode |
- | Represents a single backend storage node instance (auto-created) |
StorageNodeOps |
- | One-shot operational action targeting a single storage node |
StoragePool |
- | Creates and manages storage pools |
Task |
- | Monitors cluster tasks and their status |
StorageBackup |
- | Creates a one-time backup of a PVC to S3 |
BackupRestore |
- | Restores a backup into a new PVC |
BackupPolicy |
- | Defines an automated backup schedule for PVCs |
All CRDs use the API group storage.simplyblock.io/v1alpha1.
For the complete generated field reference, see Simplyblock Operator Reference.
Auto-Managed CSI Credentials
The cluster identifier is the StorageCluster resource name (metadata.name). The operator uses that name when
creating the backend cluster and the cluster credential Secret.
When a StorageCluster is created or becomes active, the operator automatically creates or updates the
simplyblock-csi-secret-v2 Secret in the operator's namespace with the cluster's credentials. This Secret is
consumed by the CSI driver and requires no manual management. When the cluster is deleted, the operator removes
the cluster's entry from the Secret automatically.
Storage Nodes
Storage node management uses three separate CRDs with distinct responsibilities. Together, they form a three-tier model:
StorageNodeSet ──► declares which workers to use and how to configure them
│ (fleet-level, declarative)
▼ creates
StorageNode ──► represents one backend storage node instance
│ (per-worker, read-mostly, auto-created by the operator)
▲ targeted by
StorageNodeOps ──► drives a single one-shot operation to completion
(shutdown / restart / suspend / resume / remove)
StorageNodeSet
The StorageNodeSet resource is the single point of configuration for a fleet of storage nodes. It declares which
Kubernetes workers to enroll, how to configure them (image versions, NUMA topology, device filtering, per-node
overrides), and how many nodes to add in parallel.
The operator creates one StorageNode CR per enrolled worker (and per configured NUMA socket when
socketsToUse has more than one entry). Those child CRs are managed automatically and must not be created or
deleted manually.
apiVersion: storage.simplyblock.io/v1alpha1
kind: StorageNodeSet
metadata:
name: simplyblock-node
namespace: simplyblock
spec:
clusterName: simplyblock-cluster
workerNodes:
- worker-1.example.com
- worker-2.example.com
- worker-3.example.com
The complete set of StorageNodeSet fields is available in StorageNodeSet reference.
StorageNode
The StorageNode resource represents a single backend storage node instance. One StorageNode CR is created
automatically by the operator for each (worker, NUMA socket) combination declared in a StorageNodeSet. These
CRs are read-mostly: their spec is set at creation and is effectively immutable.
kubectl get storagenodes -n simplyblock
NAME WORKER SOCKET NODEIDX UUID STATUS HEALTH AGE
simplyblock-node-worker-1.example.com-s0-n0 worker-1.example.com 0 0 a1b2c3d4-... online true 10m
simplyblock-node-worker-2.example.com-s0-n0 worker-2.example.com 0 0 b2c3d4e5-... online true 8m
simplyblock-node-worker-3.example.com-s0-n0 worker-3.example.com 0 0 c3d4e5f6-... online true 6m
StorageNode Overrides
spec.overrides allows any field from the parent StorageNodeSet to be tuned on a per-node basis. Overrides win
over fleet defaults. They can be set in two ways:
- Via
StorageNodeSet.spec.nodeConfigs: the operator propagates the matching entry to theStorageNodeCR automatically. - Directly on a manually created
StorageNodeCR: useful for fine-grained control over a single node, for example, during expansion.
Overrides Reference
| Field | Type | Description |
|---|---|---|
spdkImage |
string | SPDK image override (e.g., for phased rollouts of a new image version). |
spdkProxyImage |
string | SPDK proxy image override. |
spdkSystemMemory |
string | SPDK huge-page memory allocation (e.g., 4G, 512M). Useful for nodes with less RAM. |
journalManager |
object | Journal manager tuning (count, percentPerDevice). |
pcieAllowList |
[]string | PCIe addresses allowed for this node. |
pcieDenyList |
[]string | PCIe addresses excluded on this node. |
pcieModel |
string | PCI model string filter for this node. |
driveSizeRange |
string | Drive size range filter (e.g., 100G-2T). |
deviceNames |
[]string | Explicit NVMe namespace names (e.g., ["nvme0n1","nvme1n1"]). |
enableCpuTopology |
bool | Topology-aware CPU scheduling override. |
reservedSystemCPU |
string | CPUs reserved for system workloads (e.g., 0,1). |
failureDomain |
int | Failure-domain group index (≥ 1). Required when the cluster has enableFailureDomains=true. |
expand |
bool | Mark this node as a cluster-expansion add (triggers rebalancing on the backend). |
Use Cases
Different memory allocation per node
Some nodes may have less RAM. Override spdkSystemMemory to cap huge-page allocation:
# StorageNodeSet.spec.nodeConfigs
nodeConfigs:
low-ram-worker.example.com:
spdkSystemMemory: "2G"
Failure domain assignment
Required when the StorageCluster has enableFailureDomains: true. Assign each worker to a domain so the
cluster can maintain fault tolerance across racks or availability zones:
nodeConfigs:
worker-rack-a-1.example.com:
failureDomain: 1
worker-rack-a-2.example.com:
failureDomain: 1
worker-rack-b-1.example.com:
failureDomain: 2
worker-rack-b-2.example.com:
failureDomain: 2
Expansion add (manual StorageNode CR)
When creating a StorageNode CR manually for cluster expansion, set expand: true so the backend applies
rebalancing rather than treating it as a fresh node. Combine with any other node-specific tuning:
apiVersion: storage.simplyblock.io/v1alpha1
kind: StorageNode
metadata:
name: simplyblock-node-vm15-expansion
namespace: simplyblock
spec:
storageNodeSetRef: simplyblock-node
workerNode: vm15.simplyblock3.localdomain
socketIndex: 0
overrides:
expand: true
spdkSystemMemory: "4G"
failureDomain: 2
Device filtering per node
Use different device selection strategies per node when hardware is mixed across workers:
nodeConfigs:
nvme-only-worker.example.com:
deviceNames:
- nvme0n1
- nvme1n1
pcie-filter-worker.example.com:
pcieAllowList:
- "0000:01:00.0"
- "0000:02:00.0"
driveSizeRange: "1.7T-2T"
The complete set of StorageNode fields is available in StorageNode reference.
StorageNodeOps
The StorageNodeOps resource drives a single one-shot operation against one StorageNode. It is analogous to a
Kubernetes Job, in that the requested action is executed by the operator, the outcome is recorded, and the CR is
left in a terminal state. Only one StorageNodeOps may be active for a given StorageNode at a time.
apiVersion: storage.simplyblock.io/v1alpha1
kind: StorageNodeOps
metadata:
name: restart-worker-1
namespace: simplyblock
spec:
storageNodeRef: simplyblock-node-worker-1.example.com-s0-n0
action: restart
apiVersion: storage.simplyblock.io/v1alpha1
kind: StorageNodeOps
metadata:
name: drain-worker-1
namespace: simplyblock
spec:
storageNodeRef: simplyblock-node-worker-1.example.com-s0-n0
action: remove
Supported Actions
| Action | Expected outcome after success |
|---|---|
shutdown |
Node transitions to offline. |
restart |
Node transitions back to online. |
suspend |
Node transitions to suspended. |
resume |
Node transitions back to online. |
remove |
Node is drained, all volumes migrated, node deleted from backend. |
migrate |
Node is relocated to a different Kubernetes worker, promoted. |
The complete set of StorageNodeOps fields is available in StorageNodeOps reference.
Migrating a Storage Node to a Different Worker (migrate)
The migrate action relocates a storage node to a different Kubernetes worker without removing it from the
cluster. Unlike remove, the node retains its backend UUID, its data partitions, and its logical-volume
assignments, and no VolumeMigration CRs are created or volumes moved between nodes. The backend rebalance
triggered by the final promote redistributes load automatically.
apiVersion: storage.simplyblock.io/v1alpha1
kind: StorageNodeOps
metadata:
name: migrate-worker-1
namespace: simplyblock
spec:
storageNodeRef: simplyblock-node-worker-1.example.com-s0-n0
action: migrate
targetWorkerNode: worker-5.example.com
migrate-specific spec fields:
| Field | Type | Description |
|---|---|---|
targetWorkerNode |
string | Kubernetes worker hostname to relocate the node to. Required for migrate, immutable. |
reattachVolume |
bool | Reattach volumes during the restart phase. |
newSsdPcie |
[]string | Additional NVMe PCIe addresses to bind on the target host (passed as new_ssd_pcie to the backend). |
Sub-phases for migrate:
| SubPhase | Description |
|---|---|
Preparing |
Operator clones per-node config to the target worker, labels it into the storage plane, and waits until the storage-node-api pod is Ready and reachable. |
Restarting |
Operator issues a control-plane restart pointing at the target host. Waits for the node to leave online (restart started) and return to online (restart finished). |
Promoting |
Operator issues /promote on the relocated node, triggering a cluster rebalance. StorageNodeSet.workerNodes is updated to replace the source worker with the target. |
Pinned Volume Behavior During remove
PVCs annotated with simplyblock.io/pinned-volume affect the remove drain flow:
- If the annotation value is a valid storage node UUID (different from the node being drained), the volume is migrated to that specific node and the drain proceeds normally.
- If the annotation value is empty, not a UUID, or self-referencing (pointing to the node being drained),
drain is blocked and a
PinnedVolumeBlockingevent is emitted naming the affected PVC.
To migrate a pinned volume to a specific node, set the annotation to the target node UUID before draining:
kubectl annotate pvc <pvc-name> -n <namespace> \
simplyblock.io/pinned-volume=<target-storage-node-uuid> --overwrite
See Pinned Volume Migration During Node Removal for full details.
Storage Pool
The StoragePool resource creates and manages storage pools. When a pool becomes active, the operator automatically
creates a Kubernetes StorageClass named simplyblock-<namespace>-<clusterName>-<poolName>. The StorageClass is deleted
when the pool is deleted.
apiVersion: storage.simplyblock.io/v1alpha1
kind: StoragePool
metadata:
name: production-pool
namespace: simplyblock
spec:
clusterName: production
capacityLimit: "10T"
qos:
iops: 100000
throughput:
readWrite: 2048
read: 1024
write: 1024
The complete set of StoragePool fields is available in StoragePool reference.
Auto-Created StorageClass
The pool identifier is the StoragePool resource name (metadata.name). The operator uses that name as the backend pool
name and as the pool_name CSI StorageClass parameter.
When the pool reaches an active state, the operator creates a StorageClass with:
- Name:
simplyblock-<namespace>-<clusterName>-<poolName> - Provisioner:
csi.simplyblock.io - VolumeBindingMode:
WaitForFirstConsumer - ReclaimPolicy:
Delete - AllowVolumeExpansion:
true
The cluster_id and pool_name parameters are set automatically. Any fields specified in
spec.storageClassParameters are merged in as additional CSI driver parameters.
Because Kubernetes StorageClass parameters are immutable after creation, the StorageClass is created once and left unchanged if it already exists. To change parameters, delete the pool and recreate it with updated values.
The StorageClass is deleted when the pool is deleted.
Snapshot Cloning
When a volume is cloned from a snapshot, the clonedFromSnapshot and sourceSnapshotName fields in its status entry identify the origin. These fields are read-only and set by the backend at creation time, so they cannot be specified in the Lvol spec.
To see which volumes in a pool are snapshot clones:
kubectl get simplyblocklvol cluster-volumes -n simplyblock -o jsonpath='{.status.lvols[?(@.clonedFromSnapshot!="")].lvolName}'
Task
The Task resource provides visibility into cluster tasks (migrations, rebalancing, etc.).
apiVersion: storage.simplyblock.io/v1alpha1
kind: Task
metadata:
name: cluster-tasks
namespace: simplyblock
spec:
clusterName: production
taskID: "abc123" # optional: filter to a specific task
The complete set of Task fields is available in Task reference.
StorageBackup
The StorageBackup resource creates a one-time backup of a PVC to the S3-compatible storage endpoint configured
in the StorageCluster. For backup configuration prerequisites, see
Backup and Recovery.
apiVersion: storage.simplyblock.io/v1alpha1
kind: StorageBackup
metadata:
name: my-backup
namespace: simplyblock
spec:
clusterName: production
pvcRef:
name: my-pvc
The complete set of StorageBackup fields is available in StorageBackup reference.
BackupRestore
The BackupRestore resource restores a StorageBackup into a new PVC. The backup may be directed to a
different pool or storage node, but must be restored within the same namespace as the BackupRestore object.
apiVersion: storage.simplyblock.io/v1alpha1
kind: BackupRestore
metadata:
name: my-restore
namespace: simplyblock
spec:
clusterName: production
backupRef:
name: my-backup
pvcTemplate:
metadata:
name: restored-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Warning
BackupRestore can only restore a PVC to the same namespace as the restore object.
The complete set of BackupRestore fields is available in BackupRestore reference.
BackupPolicy
The BackupPolicy resource defines an automated backup schedule with retention settings. Policies are attached
to PVCs using the simplybk/backup-policy Kubernetes annotation, which causes StorageBackup objects to be
created automatically on schedule. Removing the annotation detaches the policy, and updating it switches the PVC
to the new policy.
apiVersion: storage.simplyblock.io/v1alpha1
kind: BackupPolicy
metadata:
name: my-policy
namespace: simplyblock
spec:
clusterName: production
maxVersions: 10
maxAge: "7d"
schedule: "15m,4 60m,11 24h,7"
Attach the policy to a PVC:
kubectl annotate pvc my-pvc -n simplyblock simplyblock.io/backup-policy=my-policy
The schedule format is a space-separated list of interval,count pairs. For example, 15m,4 60m,11 24h,7 means:
take a backup every 15 minutes (keep the 4 most recent), every 60 minutes (keep 11), and every 24 hours (keep 7).
The complete set of BackupPolicy fields is available in BackupPolicy reference.