API Reference
Packages
metrics.simplyblock.io/v1alpha2
Package v1alpha2 holds the measurements simplyblock serves under metrics.simplyblock.io: the metrics of a logical volume, a storage cluster, a storage node, a storage device, and a storage pool.
A kind here is a sample rather than a stored object. It is computed when a client reads it, so a read returns the current numbers and there is nothing to retain. All five resources are namespaced and answer get and list only: no create, update, delete, or watch, and no field of these types is one a client sets.
v1alpha2 is the group's only version.
Resource Types
- LogicalVolumeMetrics
- StorageClusterMetrics
- StorageDeviceMetrics
- StorageNodeMetrics
- StoragePoolMetrics
LogicalVolumeMetrics
StorageClusterMetrics
StorageDeviceMetrics
StorageNodeMetrics
StoragePoolMetrics
storage.simplyblock.io/v1alpha1
Package v1alpha1 contains API Schema definitions for the simplyblock v1alpha1 API group.
Resource Types
ReplicationOps
ReplicationOps is a one-shot user-driven CR for imperative replication operations: failover (planned or unplanned) and failback. The operator drives the backend calls to completion and records per-volume outcomes in status.results. Only one ReplicationOps may be active per ReplicationPolicy at a time, enforced via ReplicationPolicy.status.activeOpsRef.
Example:
apiVersion: storage.simplyblock.io/v1alpha1
kind: ReplicationOps
metadata:
name: string
spec:
action: string
scope: string
ref: string
sourceClusterID: string
deleteSource: boolean
status:
phase: string
subphase: string
message: string
startedAt: Time
completedAt: Time
results:
- slotRef: string
status: string
detail: string
targetLvolID: string
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha1 |
||
kind string |
ReplicationOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec ReplicationOpsSpec |
|||
status ReplicationOpsStatus |
ReplicationOpsResult
ReplicationOpsResult holds the outcome for a single volume in a ReplicationOps.
Appears in: - ReplicationOpsStatus
Example:
slotRef: string
status: string
detail: string
targetLvolID: string
| Field | Description | Default | Validation |
|---|---|---|---|
slotRef string |
SlotRef is the name of the ReplicationSlot CR. | ||
status string |
Status is the outcome for this volume. | Enum: [succeeded skipped failed] |
|
detail string |
Detail is an optional human-readable note (error message or skip reason). | Optional: {} |
|
targetLvolID string |
TargetLvolID is the UUID of the volume on the target cluster (failover only). | Optional: {} |
ReplicationOpsSpec
ReplicationOpsSpec defines the desired state of a ReplicationOps.
Appears in: - ReplicationOps
Example:
action: string
scope: string
ref: string
sourceClusterID: string
deleteSource: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
action string |
Action is the operation to perform. Immutable. failover: unplanned — promote target clone, source may be down. failback: restore source as primary after a prior failover. migration: planned cutover — calls replication_commit per volume; both clusters stay up. State progression: replicating → cutover_pending → cutover_done. |
Enum: [failover failback migration] Required: {} |
|
scope string |
Scope controls which volumes are affected. Immutable. target: all volumes across every policy that uses the named ReplicationPair. policy: all volumes managed by the named ReplicationPolicy CR. volume: a single ReplicationSlot (planned or unplanned per-volume operation). |
Enum: [target policy volume] Required: {} |
|
ref string |
Ref is the name of the resource identified by Scope: a ReplicationPair name for scope=target, a ReplicationPolicy name for scope=policy, or a ReplicationSlot name for scope=volume. Immutable. |
Required: {} |
|
sourceClusterID string |
SourceClusterID is used for failback only. Omit to recover to the original source. | Optional: {} |
|
deleteSource boolean |
DeleteSource instructs the backend to delete the old volume after a successful cutover. For migration it deletes the original source volume; for failback it deletes the failed-over volume on the target cluster. |
Optional: {} |
ReplicationOpsStatus
ReplicationOpsStatus holds the observed state of a ReplicationOps.
Appears in: - ReplicationOps
Example:
phase: string
subphase: string
message: string
startedAt: Time
completedAt: Time
results:
- slotRef: string
status: string
detail: string
targetLvolID: string
| Field | Description | Default | Validation |
|---|---|---|---|
phase string |
Phase is the current lifecycle phase of this operation. | Enum: [Pending Running Succeeded Failed] Optional: {} |
|
subphase string |
Subphase describes what the operation is currently doing within the phase (e.g. "TriggeringFailover", "UpdatingSlotStatuses", "ReleasingLock"). |
Optional: {} |
|
message string |
Message is a human-readable description of the current phase. | Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation began. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when the operation finished (successfully or not). | Optional: {} |
|
results ReplicationOpsResult array |
Results holds a per-volume summary of the operation outcome. | Optional: {} |
ReplicationPair
ReplicationPair defines the source and target clusters for a replication relationship. It is reusable configuration — multiple ReplicationPolicies may reference the same pair to replicate volumes between the same two clusters with different schedules or retention. The operator ensures the corresponding backend ReplicationTarget exists and stores its ID in status.backendTargetID for use by ReplicationPolicy resources.
Example:
apiVersion: storage.simplyblock.io/v1alpha1
kind: ReplicationPair
metadata:
name: string
spec:
sourceCluster: string
targetCluster: string
status:
ready: boolean
backendTargetID: string
message: string
conditions:
- Condition
activeOpsRef: string
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha1 |
||
kind string |
ReplicationPair |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec ReplicationPairSpec |
|||
status ReplicationPairStatus |
ReplicationPairSpec
ReplicationPairSpec defines the source and target clusters for a replication relationship.
Appears in: - ReplicationPair
Example:
sourceCluster: string
targetCluster: string
| Field | Description | Default | Validation |
|---|---|---|---|
sourceCluster string |
SourceCluster is the name of the local StorageCluster (the replication source). | Required: {} |
|
targetCluster string |
TargetCluster is the name or UUID of the remote cluster (the replication target). Immutable after creation. |
Required: {} |
ReplicationPairStatus
ReplicationPairStatus holds the observed state of a ReplicationPair.
Appears in: - ReplicationPair
Example:
ready: boolean
backendTargetID: string
message: string
conditions:
- Condition
activeOpsRef: string
| Field | Description | Default | Validation |
|---|---|---|---|
ready boolean |
Ready is true when the backend ReplicationTarget has been created and is available. | Optional: {} |
|
backendTargetID string |
BackendTargetID is the UUID of the backend ReplicationTarget resource. | Optional: {} |
|
message string |
Message provides a human-readable description of the current state. | Optional: {} |
|
conditions Condition ⧉ array |
Conditions holds standard Kubernetes condition types. | Optional: {} |
|
activeOpsRef string |
ActiveOpsRef is the name of the ReplicationOps currently holding the target-scope lock on this pair. Only one scope=target ReplicationOps may be active per pair at a time. |
Optional: {} |
ReplicationPolicy
ReplicationPolicy defines the replication schedule and retention for volumes replicated between the clusters defined by a ReplicationPair. A StorageClass or PVC references a policy via the storage.simplyblock.io/replication-policy annotation. The operator automatically creates one ReplicationSlot per bound PVC. Deletion is blocked while any ReplicationSlots reference this policy.
Example:
apiVersion: storage.simplyblock.io/v1alpha1
kind: ReplicationPolicy
metadata:
name: string
spec:
pairRef: string
mode: string
interval: string
snapshotRetention: integer
status:
ready: boolean
backendPolicyID: string
slotCount: integer
activeOpsRef: string
conditions:
- Condition
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha1 |
||
kind string |
ReplicationPolicy |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec ReplicationPolicySpec |
|||
status ReplicationPolicyStatus |
ReplicationPolicySpec
ReplicationPolicySpec defines the desired replication schedule and retention.
Appears in: - ReplicationPolicy
Example:
pairRef: string
mode: string
interval: string
snapshotRetention: integer
| Field | Description | Default | Validation |
|---|---|---|---|
pairRef string |
PairRef is the name of the ReplicationPair that defines the source and target clusters. Multiple ReplicationPolicies may reference the same pair with different schedules. |
Required: {} |
|
mode string |
Mode controls replication semantics. failover: target is a DR standby; volumes are read-only on the target. migration: planned online cutover to the target cluster. |
failover | Enum: [failover migration] Optional: {} |
interval string |
Interval is how often a replication snapshot is taken (e.g. "5m", "1h"). | 5m | Optional: {} |
snapshotRetention integer |
SnapshotRetention is the minimum number of snapshots to retain on the target. | 3 | Minimum: 2 Optional: {} |
ReplicationPolicyStatus
ReplicationPolicyStatus holds the observed state of a ReplicationPolicy.
Appears in: - ReplicationPolicy
Example:
ready: boolean
backendPolicyID: string
slotCount: integer
activeOpsRef: string
conditions:
- Condition
| Field | Description | Default | Validation |
|---|---|---|---|
ready boolean |
Ready is true when the backend ReplicationPolicy has been created. | Optional: {} |
|
backendPolicyID string |
BackendPolicyID is the UUID of the backend ReplicationPolicy resource. | Optional: {} |
|
slotCount integer |
SlotCount is the number of ReplicationSlot CRs currently managed by this policy. | Optional: {} |
|
activeOpsRef string |
ActiveOpsRef is the name of the currently running ReplicationOps CR. Empty when no operation is in progress. |
Optional: {} |
|
conditions Condition ⧉ array |
Conditions holds standard Kubernetes condition types. | Optional: {} |
ReplicationSlot
ReplicationSlot tracks the live replication state for a single PVC. One ReplicationSlot is created per PVC by the PVCAnnotationWatcher controller when a PVC references a ReplicationPolicy via annotation. It is owned by its PVC, so deleting the PVC cascades deletion and triggers a backend detach via the slot finalizer. The ReplicationSlot reconciler drives all backend calls: attach, monitor, cutover, failover, and detach.
Example:
apiVersion: storage.simplyblock.io/v1alpha1
kind: ReplicationSlot
metadata:
name: string
spec:
policyRef: string
pvcRef: string
volumeID: string
status:
state: string
direction: string
sourceLvolID: string
targetLvolID: string
targetNQN: string
lastReplicatedAt: Time
message: string
conditions:
- Condition
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha1 |
||
kind string |
ReplicationSlot |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec ReplicationSlotSpec |
|||
status ReplicationSlotStatus |
ReplicationSlotSpec
ReplicationSlotSpec defines the identity of a per-volume replication slot.
Appears in: - ReplicationSlot
Example:
policyRef: string
pvcRef: string
volumeID: string
| Field | Description | Default | Validation |
|---|---|---|---|
policyRef string |
PolicyRef is the name of the ReplicationPolicy governing this slot. Immutable. | Required: {} |
|
pvcRef string |
PVCRef is the name of the PVC being replicated. Immutable. | Required: {} |
|
volumeID string |
VolumeID is the backend lvol UUID of the source volume. Immutable. Format: " |
Required: {} |
ReplicationSlotStatus
ReplicationSlotStatus holds the observed state of a ReplicationSlot.
Appears in: - ReplicationSlot
Example:
state: string
direction: string
sourceLvolID: string
targetLvolID: string
targetNQN: string
lastReplicatedAt: Time
message: string
conditions:
- Condition
| Field | Description | Default | Validation |
|---|---|---|---|
state string |
State is the current replication state for this slot. | Enum: [attaching replicating cutover_pending cutover_done failed_over detaching error] Optional: {} |
|
direction string |
Direction is which side of the replication relationship this cluster holds. | Enum: [source target] Optional: {} |
|
sourceLvolID string |
SourceLvolID is the UUID of the source volume on the source cluster. | Optional: {} |
|
targetLvolID string |
TargetLvolID is the UUID of the replicated volume on the target cluster. | Optional: {} |
|
targetNQN string |
TargetNQN is the NVMe NQN on the target cluster (populated after failover). | Optional: {} |
|
lastReplicatedAt Time ⧉ |
LastReplicatedAt is the timestamp of the last successful replication snapshot. | Optional: {} |
|
message string |
Message provides a human-readable description of the current state. | Optional: {} |
|
conditions Condition ⧉ array |
Conditions holds standard Kubernetes condition types. | Optional: {} |
storage.simplyblock.io/v1alpha2
Package v1alpha2 is the current version of the storage.simplyblock.io API group and the version the cluster stores. It declares the kinds that run simplyblock on Kubernetes: entity kinds such as StorageCluster, StorageNode, StoragePool, and StorageDevice, whose spec is the desired state simplyblock converges on, and the Ops kind beside each of them, which requests one imperative operation against its subject and reports how far that operation got.
Several of these kinds also exist as v1alpha1, under property names that have since been renamed. A manifest written against v1alpha1 is still accepted and converted, and a client that asks for v1alpha1 gets the older spelling back. A kind introduced in v1alpha2 has no v1alpha1 spelling.
Resource Types
- ClusterDeploymentConfig
- ControlPlane
- ControlPlaneOps
- NFSExport
- OperatorOps
- PersistentVolumeOps
- SimplyblockDriver
- StorageBackup
- StorageBackupOps
- StorageBackupPolicy
- StorageCluster
- StorageClusterOps
- StorageDevice
- StorageDeviceOps
- StorageNode
- StorageNodeOps
- StoragePool
- StoragePoolOps
- StorageSiteDeployment
- TestFailover
- VolumeGroupSnapshotOps
AttachedClaim
AttachedClaim is one claim the policy currently covers, in Kubernetes terms rather than in the control plane's.
Appears in: - StorageBackupPolicyStatus
Example:
name: string
persistentVolumeName: string
lvolID: string
attachedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name is the PersistentVolumeClaim's name in this namespace. | Required: {} |
|
persistentVolumeName string |
PersistentVolumeName is the volume behind it. | Optional: {} |
|
lvolID string |
LvolID is the logical volume the control plane attached the policy to. It is what a detach addresses, and it is recorded per claim so that a claim rebound onto a new volume is detached from the old one rather than silently left attached. |
Optional: {} |
|
attachedAt Time ⧉ |
AttachedAt is when the claim started matching the selector. | Optional: {} |
BackupCopy
BackupCopy is the copy itself: what it is, what it cost, and what it depends on.
Appears in: - StorageBackupStatus
Example:
backupID: string
s3ID: integer
size: integer
previousBackupID: string
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
backupID string |
BackupID is the control plane's identifier for the copy. | Optional: {} |
|
s3ID integer |
S3ID is the object behind the copy in the store. It is what somebody reconciling a bill against a bucket listing matches on. |
Optional: {} |
|
size integer |
Size is the copy's size in bytes, which is what a bill tracks. | Minimum: 0 Optional: {} |
|
previousBackupID string |
PreviousBackupID names the backup this one is incremental against, where the control plane reports one. It makes the chain legible, which matters because deleting a backup another depends on is not obviously safe. |
Optional: {} |
|
startedAt Time ⧉ |
StartedAt and CompletedAt bound how long the copy took. They are the backup's own reported interval rather than transitions the operator observed, because the stream coalesces and a small backup can be complete before the operator ever saw it start. |
Optional: {} |
|
completedAt Time ⧉ |
Optional: {} |
BackupSource
BackupSource is what the volume was when the copy was taken. It is written once and never updated: the pool a volume was in when it was backed up is a fact about the backup, and rewriting it when the volume moves would destroy the only record of where the data came from. A restore reads it to know what it is restoring.
Appears in: - StorageBackupStatus
Example:
claimName: string
claimNamespace: string
persistentVolumeName: string
poolName: string
poolUUID: string
lvolID: string
lvolName: string
fsType: string
snapshotID: string
snapshotName: string
nodeID: string
clusterUUID: string
| Field | Description | Default | Validation |
|---|---|---|---|
claimName string |
ClaimName and ClaimNamespace are the PersistentVolumeClaim the copy was taken from. They are the pair a user recognizes, and the reason the claim rather than the volume is the printed column. |
Optional: {} |
|
claimNamespace string |
Optional: {} |
||
persistentVolumeName string |
PersistentVolumeName is the PV that was copied. | Optional: {} |
|
poolName string |
PoolName and PoolUUID are the StoragePool the volume was in. A restore names its own target pool rather than defaulting to this one, because a backup discovered from a shared store may name a pool this cluster does not have. |
Optional: {} |
|
poolUUID string |
Optional: {} |
||
lvolID string |
LvolID and LvolName identify the logical volume that was copied. | Optional: {} |
|
lvolName string |
Optional: {} |
||
fsType string |
FSType is the filesystem the volume was formatted with, which a restore needs in order to produce a mountable claim. |
Optional: {} |
|
snapshotID string |
SnapshotID and SnapshotName identify the snapshot the backup was taken from. |
Optional: {} |
|
snapshotName string |
Optional: {} |
||
nodeID string |
NodeID is the storage node the copy was read from. It belongs to the source rather than to the copy: it says which machine held the data, which is what somebody correlating a backup against a node failure needs. |
Optional: {} |
|
clusterUUID string |
ClusterUUID is the cluster the volume lived in, which differs from the backup's own cluster when another cluster wrote it. |
Optional: {} |
BackupSpec
BackupSpec parameterizes the Backup action and is ignored by the others.
Appears in: - ControlPlaneOpsSpec
Example:
blobStore: string
backupName: string
| Field | Description | Default | Validation |
|---|---|---|---|
blobStore string |
BlobStore is the destination, in the form the FoundationDBBackup CRD takes it. The operator copies it through rather than interpreting it, since the backup is the FoundationDB operator's to perform. |
Required: {} |
|
backupName string |
BackupName is the FoundationDBBackup to create or trigger. Absent uses the one already configured for the cluster, and fails when there is none and no name to create. |
Optional: {} |
BackupStoreSpec
BackupStoreSpec is the S3 location a cluster's backups live in, and the credentials to reach it. It is a location and nothing else: how backups are taken and what they contain are the control plane's, and this block only says where they go. It is both the target copies are written to and the inventory the operator walks to produce StorageBackup objects.
The whole block is mutable. A cluster can be created without a store and given one later, and what changes when it changes is which backups have objects, since the object set is derived from the location rather than accumulated.
Appears in: - ClusterTemplate - StorageClusterSpec
Example:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
| Field | Description | Default | Validation |
|---|---|---|---|
endpoint string |
Endpoint is the S3 endpoint, for example, https://s3.example.com. | Pattern: ^https?://[a-zA-Z0-9.-]+(:[0-9]\{1,5\})?(/.*)?$ Required: {} |
|
bucket string |
Bucket is the bucket backups are written to and read from. | Required: {} |
|
region string |
Region is the bucket's region, for endpoints that do not imply one. | Optional: {} |
|
credentialsSecretRef LocalObjectReference ⧉ |
CredentialsSecretRef names the Secret holding the access key and the secret key. It is a reference rather than the values, because a spec is readable by anybody who can read the object. |
Required: {} |
BaselineColdStartPolicy
Underlying type: string
BaselineColdStartPolicy selects what happens to a node with fewer than BaselineMinSamples samples in the window: a freshly onboarded node, or one whose probe sidecar has just started.
Validation: - Enum: [Defer PartialWindow]
Appears in: - VolumeAutoPlacementSettings
| Field | Description |
|---|---|
Defer |
BaselineColdStartDefer omits an under-sampled node from the cycle: it is neither a migration source nor a target until it has accumulated enough samples, which avoids acting on a noisy baseline. |
PartialWindow |
BaselineColdStartPartialWindow computes the baseline from whatever samples exist, accepting a noisier baseline early on so that rebalancing engages sooner. It is the default. |
BaselineStrategy
Underlying type: string
BaselineStrategy selects how the per-node latency baseline, the denominator of the rebalancing deviation signal, is derived.
Validation: - Enum: [Benchmark RollingWindow]
Appears in: - VolumeAutoPlacementSettings
| Field | Description |
|---|---|
Benchmark |
BaselineStrategyBenchmark uses the one-shot fio measurement taken on a fresh cluster and frozen on the node's status. It is simple and tends to read too low, because an idle cluster is far faster than a loaded one and every loaded node then shows a large deviation. |
RollingWindow |
BaselineStrategyRollingWindow derives the baseline from a rolling window of the probe sidecar's latency series, using an outlier-rejecting estimator. It reflects each node's recent operating latency rather than an idle measurement, and is the default. |
CancelTaskSpec
CancelTaskSpec parameterizes the CancelTask action.
Appears in: - StorageClusterOpsSpec
Example:
taskID: string
| Field | Description | Default | Validation |
|---|---|---|---|
taskID string |
TaskID names the entry of StorageCluster.status.tasks to cancel, by the control plane's identifier for it rather than by its position in the list. |
Required: {} |
CapacityThresholdSpec
CapacityThresholdSpec is one capacity alarm level, as a figure for used capacity and one for provisioned capacity.
Appears in: - StorageClusterSpec
Example:
capacity: integer
provisionedCapacity: integer
| Field | Description | Default | Validation |
|---|---|---|---|
capacity integer |
Capacity is the used-capacity threshold. | Optional: {} |
|
provisionedCapacity integer |
ProvisionedCapacity is the provisioned-capacity threshold. | Optional: {} |
ClusterDeploymentConfig
ClusterDeploymentConfig is a whole simplyblock deployment written down as one reviewable document: the environment, the cluster, and the node sets with their workers, interfaces, and devices. An administrator reviews it, approves it, and the operator expands it into a StorageCluster and its StorageNodes.
It is ephemeral. Everything the expansion produces is self-describing, so the document can be edited or deleted once it has been expanded, and nothing reads it afterward.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: ClusterDeploymentConfig
metadata:
name: string
spec:
approved: boolean
environment: KubernetesEnvironment
hostOS:
distro: '^[a-z0-9][a-z0-9._-]*$'
family: HostOSFamily
edgeCluster: boolean
clusterRef: string
cluster:
name: string
maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
enableDriveFormat: boolean
enableJournalDevice: boolean
socketsToUse:
- string
nodesPerSocket: integer
initContainerResources: ResourceRequirements
tolerations:
- Toleration
containerResources: ResourceRequirements
nodeProvisioningBudget: integer
enableChecksumValidation: boolean
enableAtomicity4K: boolean
stripe:
dataChunks: integer
parityChunks: integer
fabricType: string
openshift:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
ports:
nvmf: integer
rpc: integer
nodeAgent: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
backup:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
kms:
vault:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
images:
nodeAgent:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
spdk:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
spdkProxy:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
nodeSets:
- name: string
groups:
- name: string
workers:
- string
mgmtInterface: string
dataInterfaces:
- string
devices:
nvme:
- '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
block:
- '^/dev/[a-zA-Z0-9._/-]+$'
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
status:
phase: ClusterDeploymentConfigPhase
step: KubeSnapshot
clusterRef: string
nodeRefs:
- string
message: string
observedGeneration: integer
expansionStartedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
ClusterDeploymentConfig |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec ClusterDeploymentConfigSpec |
|||
status ClusterDeploymentConfigStatus |
ClusterDeploymentConfigPhase
Underlying type: string
ClusterDeploymentConfigPhase is where the operator has got to with this document.
Validation: - Enum: [Draft Expanding Expanded Failed]
Appears in: - ClusterDeploymentConfigStatus
| Field | Description |
|---|---|
Draft |
ClusterDeploymentConfigPhaseDraft is an unapproved document. It is validated on every reconcile and expanded on none. |
Expanding |
ClusterDeploymentConfigPhaseExpanding is an approved document being turned into a cluster and its nodes. |
Expanded |
ClusterDeploymentConfigPhaseExpanded is a document whose objects exist. |
Failed |
ClusterDeploymentConfigPhaseFailed is a document the expansion refused or could not finish. |
ClusterDeploymentConfigSpec
ClusterDeploymentConfigSpec is a whole simplyblock deployment as one reviewable document.
The third rule is the device class one. A document describes one cluster and a cluster is built out of one class of backend storage, so every group of every node set names the same member of its DeviceSelection. The expansion reads the class off them and stamps it onto the cluster it creates, which is why the document carries no field for it.
Appears in: - ClusterDeploymentConfig
Example:
approved: boolean
environment: KubernetesEnvironment
hostOS:
distro: '^[a-z0-9][a-z0-9._-]*$'
family: HostOSFamily
edgeCluster: boolean
clusterRef: string
cluster:
name: string
maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
enableDriveFormat: boolean
enableJournalDevice: boolean
socketsToUse:
- string
nodesPerSocket: integer
initContainerResources: ResourceRequirements
tolerations:
- Toleration
containerResources: ResourceRequirements
nodeProvisioningBudget: integer
enableChecksumValidation: boolean
enableAtomicity4K: boolean
stripe:
dataChunks: integer
parityChunks: integer
fabricType: string
openshift:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
ports:
nvmf: integer
rpc: integer
nodeAgent: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
backup:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
kms:
vault:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
images:
nodeAgent:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
spdk:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
spdkProxy:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
nodeSets:
- name: string
groups:
- name: string
workers:
- string
mgmtInterface: string
dataInterfaces:
- string
devices:
nvme:
- '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
block:
- '^/dev/[a-zA-Z0-9._/-]+$'
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
| Field | Description | Default | Validation |
|---|---|---|---|
approved boolean |
Approved is the review gate. A document is expanded only once it is set, and is validated but otherwise inert before that, which is what makes reviewing a wrong document safe. It is defaulted and serialized rather than omitted when false, and the two are the same requirement read twice. A reviewer has to see the gate they are being asked to open, and the rules above have to find the field they read: a bool omitted when false is a key the apiserver never stores, so a rule reading it fails rather than reading false, and the first rule guarding approval denied every approval there could ever be. The has() guards are what carry documents written before the default existed. |
false | Optional: {} |
environment KubernetesEnvironment |
Environment is the Kubernetes distribution this deployment targets. It is a shorthand the expansion spends: it sets enableKubeletConfiguration, enableCpuTopology, and openShiftCluster on the cluster the document produces, after which nothing reads it again. The worker's host OS is not among them and is stated in hostOS, because a distribution decides what Kubernetes does to a machine and not which packages the machine has. |
Enum: [Vanilla OpenShift Rancher K3s Talos] Optional: {} |
|
hostOS HostOSSpec |
HostOS is the operating system the workers run, which decides what the host itself offers rather than what Kubernetes does to it. The expansion spends the distro on StorageCluster.spec.storageNodes.ubuntuHost and carries the family for the reviewer reading the document. |
Optional: {} |
|
edgeCluster boolean |
EdgeCluster states that this is an edge deployment. An edge deployment differs from a datacenter one in topology and scale rather than in kind, so it is the same StorageCluster with fewer, smaller nodes. |
Optional: {} |
|
clusterRef string |
ClusterRef names an existing StorageCluster this document adds nodes to. Absent means the document creates the cluster in Cluster. Setting it to a cluster that does not exist, or leaving it absent when one already does, is refused rather than reconciled. The maximum is what a StorageCluster name may be and not the 253 an object name may be: a reference between the two names nothing that can exist. |
MaxLength: 63 Optional: {} |
|
cluster ClusterTemplate |
Cluster is the StorageCluster to create. Ignored when ClusterRef is set. | Optional: {} |
|
images DeploymentImages |
Images are the container images this deployment pins. Unstated, each field the expansion would write keeps its own default. |
Optional: {} |
|
nodeSets NodeSet array |
NodeSets are the nodes the deployment is made of. At least one, and at most 64. |
MaxItems: 64 MinItems: 1 Required: {} |
ClusterDeploymentConfigStatus
ClusterDeploymentConfigStatus is the observed state of the document.
Appears in: - ClusterDeploymentConfig
Example:
phase: ClusterDeploymentConfigPhase
step: KubeSnapshot
clusterRef: string
nodeRefs:
- string
message: string
observedGeneration: integer
expansionStartedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase ClusterDeploymentConfigPhase |
Phase is the operator's own view of the document. | Enum: [Draft Expanding Expanded Failed] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the expansion machine within Expanding. | Optional: {} |
|
clusterRef string |
ClusterRef names the StorageCluster the expansion produced or added to. It is a record rather than a dependency: nothing resolves it after expansion, which is what makes the document safe to delete. |
Optional: {} |
|
nodeRefs string array |
NodeRefs names the StorageNode objects the expansion created, for the same reason. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the document moves, and never a log. On a Draft it is what validation found, which is what a reviewer reads before approving. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
|
expansionStartedAt Time ⧉ |
ExpansionStartedAt is when the expansion machine was born, which is the first reconcile after the document was approved. A document may sit as a draft for as long as a review takes, so this is not creationTimestamp and the difference is the whole point: how long a deployment takes is measured from the moment somebody said yes. It is the start of the expansion_duration_seconds metric, whose histogram needs an instant that survives the operator restarting mid-expansion, which nothing in memory and no step deadline supplies. |
Optional: {} |
ClusterPortsSpec
ClusterPortsSpec is where a cluster's storage nodes listen.
The three are one block here and three fields on the StorageCluster, which is the one place the document deliberately does not mirror the cluster's shape. They are the same decision taken once — which ports this deployment's nodes bind — and a reviewer reads them together or not at all, where the cluster carries them flat because that is what shipped.
Every member is immutable on the cluster: a node binds its ports when it starts and the control plane hands them out from the bases it was given at cluster create, so the document is the only place any of them can be stated.
Each carries the control plane's own default, so a document that names the block shows a reviewer the three numbers a cluster will actually run with rather than three blanks they would have to know the backend to fill in.
Appears in: - ClusterTemplate
Example:
nvmf: integer
rpc: integer
nodeAgent: integer
| Field | Description | Default | Validation |
|---|---|---|---|
nvmf integer |
NVMf is the base of the NVMe-oF port range every node binds. It expands into StorageCluster.spec.nvmfBasePort. |
4420 | Maximum: 65535 Minimum: 1024 Optional: {} |
rpc integer |
Rpc is the base of the RPC port range every node binds. It expands into StorageCluster.spec.rpcBasePort. |
8080 | Maximum: 65535 Minimum: 1024 Optional: {} |
nodeAgent integer |
NodeAgent is the port each node's agent API listens on. It expands into StorageCluster.spec.snodeApiPort, and it is named for the component rather than for that field: the agent is what spec.images.nodeAgent pins and what the storage-node DaemonSet runs. |
50001 | Maximum: 65535 Minimum: 1024 Optional: {} |
ClusterTask
ClusterTask is one asynchronous job the control plane is running, as of the last frame of the task stream. It is a window rather than a record: a task that reaches a terminal outcome leaves status.tasks, and what remains of it is an event.
It carries no progress figure and no creation date, because the control plane reports neither, which is why the list is in the control plane's order rather than newest first.
Appears in: - StorageClusterStatus
Example:
id: string
type: string
status: string
retry: integer
| Field | Description | Default | Validation |
|---|---|---|---|
id string |
ID is the control plane's identifier, and it is how a CancelTask operation names the task. A position in the list is not an identity, because the next frame may order it differently. |
Required: {} |
|
type string |
Type is what kind of job it is, in the control plane's own spelling: the value is the backend's rather than this API's. |
Optional: {} |
|
status string |
Status is the control plane's own status string, and it is why this entry carries no phase: the operator adds nothing to what the backend reports. Its values are new, running, suspended, and done, of which only the first three appear here. |
Optional: {} |
|
retry integer |
Retry is how many times the control plane has restarted this task. It is the one number that separates a task that is slow from one that is failing, and it is the closest thing the schema has to progress. |
Minimum: 0 Optional: {} |
ClusterTemplate
ClusterTemplate is the StorageCluster the expansion creates, where it creates one. It carries the layout fields a cluster cannot change later, so that a reviewer sees them before the cluster exists rather than after.
Appears in: - ClusterDeploymentConfigSpec - StorageSiteDraft
Example:
name: string
maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
enableDriveFormat: boolean
enableJournalDevice: boolean
socketsToUse:
- string
nodesPerSocket: integer
initContainerResources: ResourceRequirements
tolerations:
- Toleration
containerResources: ResourceRequirements
nodeProvisioningBudget: integer
enableChecksumValidation: boolean
enableAtomicity4K: boolean
stripe:
dataChunks: integer
parityChunks: integer
fabricType: string
openshift:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
ports:
nvmf: integer
rpc: integer
nodeAgent: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
backup:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
kms:
vault:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name is the StorageCluster's name, and is therefore held to what such a name may be rather than to what an object name may be. A longer value is a document the API server accepts and a CreatingCluster step that can never succeed, since the cluster it would write is one the API server refuses. |
MaxLength: 63 Required: {} |
|
maxSubsystemCount integer |
MaxSubsystemCount is the maximum number of NVMe-oF subsystems each storage node of this cluster serves. Required, because the StorageCluster's own field is, and no StorageNode carries a copy of it. |
Maximum: 75 Minimum: 10 Required: {} |
|
vcpuCount integer |
VCPUCount is the number of vCPUs allocated to SPDK on each storage node of this cluster. It is stated here and nowhere below, because the control plane assumes it uniform across a cluster's nodes; CreatingNodes copies it into every StorageNode.spec.config.sizing it writes. Required, because the StorageCluster's own field is. The floor is 4 rather than a hardware limit: a node must carry one core beyond this budget for the system, and the control plane's core layout assigns no NVMe-oF poller core at all for a 2-vCPU budget. |
Minimum: 4 Required: {} |
|
minHugePagesSize string |
MinHugePagesSize is the smallest huge-page allocation each storage node of this cluster makes: 100G or 1T, where a bare number is gigabytes. Like VCPUCount it is the cluster's and is copied onto every node the expansion writes. Omitted, each node uses the computed minimum. |
MaxLength: 32 Optional: {} |
|
enableDriveFormat boolean |
EnableDriveFormat formats every device the document names before a storage node takes it, which is how a drive carrying anything already is made usable. It says what is wanted rather than how, because the how differs by device class: an NVMe device is formatted to a 4K block size, and a logical block device has its signatures wiped. One field covers both, so a document does not have to know which class the expansion will resolve it to. It is on the document rather than defaulted further down because it is destructive and the document is what somebody approves. A reviewer reading a draft has to see that the drives it lists will be formatted, and be able to strike it before approving; the cluster's own field is immutable once the cluster exists, so a default nobody saw could not be undone either. |
Optional: {} |
|
enableJournalDevice boolean |
EnableJournalDevice dedicates the smallest NVMe device on each of this deployment's workers to the journal manager, instead of carving a journal partition out of every device. It is here rather than on a node set because it is immutable on the cluster it lands on, for the reason SocketsToUse is: the on-disk layout a fleet was built with is not one a later document can vary. It also costs a drive of capacity per node, which is a trade a reviewer approves rather than one a default makes for them. |
Optional: {} |
|
socketsToUse string array |
SocketsToUse restricts the deployment to selected NUMA sockets, and empty means socket 0 alone. With NodesPerSocket it decides how many storage nodes each worker runs, so a group of two workers on a two-socket layout expands to four nodes. It is here rather than on a node set because it is immutable on the cluster it lands on: the layout a fleet was built with is not one a later document can vary, and a reviewer should see it before the cluster exists. |
MaxItems: 16 items:MaxLength: 16 Optional: {} |
|
nodesPerSocket integer |
NodesPerSocket is how many storage nodes run per NUMA socket. See SocketsToUse, which it multiplies. |
Maximum: 8 Minimum: 1 Optional: {} |
|
initContainerResources ResourceRequirements ⧉ |
InitContainerResources sizes both of the storage node's init containers, and expands into the cluster's own spec.storageNodes.initContainerResources. They are sized apart from the container because they do a different job and are gone before it starts: one writes the node's env file and the other runs node_configure.py once, so what they need is a short burst rather than the footprint of a process that runs for the node's life. Stating either half replaces both, as with containerResources, and it is a pointer for the same reason. |
Optional: {} |
|
tolerations Toleration ⧉ array |
Tolerations are what the storage-node pods tolerate, and they expand into the cluster's own spec.storageNodes.tolerations. A fleet that dedicates machines to storage taints them, which is what keeps everything else off. The DaemonSet that lands on those machines has to tolerate the taint or it schedules nowhere, and a document that could not say so described a deployment that does not start: the correction was an edit to the cluster the document had just created, on a field the document owns everywhere else. A growth document states none. It names a cluster rather than describing one, and that cluster already carries what its storage nodes tolerate. |
MaxItems: 32 Optional: {} |
|
containerResources ResourceRequirements ⧉ |
ContainerResources sizes the storage-node container, and expands into the cluster's own spec.storageNodes.containerResources. The container it sizes is the node's management API rather than SPDK, which runs in a pod of its own: what outgrows the default is a node answering for many subsystems, not a node moving more data. It is on the document because a deployment is where a fleet's sizing is decided, and a cluster written from a document that could not say so had to be edited afterward on a field the document owns everywhere else. Stating either half replaces both. The defaults apply to a cluster that states neither requests nor limits, so a document stating requests alone produces a container with no limits rather than one with the default limits, and a memory limit is what has the kubelet evict a leaking agent rather than losing the worker. It is a pointer because a resource block is a struct, and a struct with omitempty is serialized whether or not anything is in it: as a value, every document a discovery run writes would carry an empty containerResources that says nothing and that a reviewer has to decide about. |
Optional: {} |
|
nodeProvisioningBudget integer |
NodeProvisioningBudget is how many workers the expansion may have in the node-add process at once. It expands into the cluster's own spec.storageNodes.nodeProvisioningBudget, whose meaning it shares: the cap is counted by distinct worker, so a two-socket host spends one of the budget, and a worker hosting a FoundationDB pod is sequential whatever the budget says. It is on the document because a document is what states the size of a deployment, and a deployment of thirty workers added one at a time is the difference between an afternoon and a week. Omitted, the cluster's default of one applies, which is the serial behavior. |
Minimum: 1 Optional: {} |
|
enableChecksumValidation boolean |
EnableChecksumValidation turns on inline CRC validation of every I/O, for silent-data-error protection. It is on the document because it is immutable on the cluster it lands on: the backend bakes the checksum method into each device when the cluster is created and never re-applies it, so a cluster created without this is one nobody can turn it on for. A deployment that wants its data checked has to say so here or not at all. |
Optional: {} |
|
enableAtomicity4K boolean |
EnableAtomicity4K enforces 4K write atomicity on every device this deployment names, which is what lets checksum validation run on devices whose logical block size is under the data plane's 4K minimum. It is the route to checked I/O on a device that cannot be reformatted: a logical block device's block size is fixed by the drive, and some NVMe devices offer no 4K format either. Where a device can be reformatted, EnableDriveFormat is the other route and this is unnecessary. It is an enforcement because the question is often unanswerable. A SATA drive presenting 512-byte logical blocks over a 4K physical sector reports 512 and nothing more, and a kernel older than 6.11 publishes no atomic write attributes at all. Where a device does answer, the storage node's report carries it, and a reviewer approves this against that rather than against a vendor's datasheet -- because enforcing a guarantee the hardware does not keep is how a torn write becomes a checksum that silently disagrees with it. It means nothing unless EnableChecksumValidation is set, which is the cluster's own rule and is left to the cluster to enforce. |
Optional: {} |
|
stripe StripeSpec |
Stripe is the erasure-coding layout. | Optional: {} |
|
fabricType string |
FabricType is the storage fabric. | MaxLength: 32 Optional: {} |
|
openshift OpenShiftSpec |
OpenShift is what this deployment states because it runs on OpenShift. It expands into StorageCluster.spec.storageNodes.openshift, whose shape it shares, and it is read only for a document whose environment is OpenShift: the environment is what says which distribution this is, and the block is what that distribution needs said beyond it. |
Optional: {} |
|
ports ClusterPortsSpec |
Ports are where this cluster's storage nodes listen. Unstated, and for each member left unstated, the cluster's own defaults decide. |
Optional: {} |
|
enableFailureDomains boolean |
EnableFailureDomains opts the cluster into failure-domain mode, in which every group must label the fault group its workers belong to. |
Optional: {} |
|
enableNodeAffinity boolean |
EnableNodeAffinity has the data plane serve an erasure-coded volume's I/O from the local node's own devices where it can, before crossing the network. It is not Kubernetes affinity, and the name is the one place this API invites that reading: nothing about it schedules a pod, labels a worker, or places a volume's primary node. The control plane carries it into the cluster map it pushes to each node, where it sets the local node's index, and what changes is which copy of a chunk is read. Co-locating a workload with the primary node of its volume is a separate mechanism and is not configured here. It is on the document because it is immutable on the cluster: the control plane takes it at cluster create and never re-applies it, so this is the only moment it can be set at all. |
Optional: {} |
|
backup BackupStoreSpec |
Backup is where this cluster's backups live, and it expands into StorageCluster.spec.backup unchanged. It is here for the reason KMS is: a store stated on the document is present when the cluster is created rather than patched in afterward by whoever remembers. Unlike most of what this template carries, the field it fills is mutable, so a document that states none costs nothing permanent. A cluster can be given a store whenever there is one to give. The Secret it names is not resolved at admission. It is a core object a deployment legitimately creates alongside the document or after it, and the cluster's own creation is where its absence is reported. |
Optional: {} |
|
kms KMSSpec |
KMS selects where the cluster stores volume encryption keys. Stating it on the document is what makes it present when the cluster is created, where setting it on the StorageCluster afterward races with that creation. |
Optional: {} |
ControlPlane
ControlPlane is the simplyblock control plane for one Kubernetes cluster:
FoundationDB together with the management API, either installed by the
operator or already existing. It is a singleton named simplyblock, and it is
the root of the ownership spine: nothing else in this API group reconciles
meaningfully before it reports Available.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: ControlPlane
metadata:
name: string
spec:
source:
local:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
foundationDB:
replicas: integer
storageClassName: string
resources: ResourceRequirements
nodeSelector:
string: string
replicas: integer
resources: ResourceRequirements
tolerations:
- Toleration
nodeSelector:
string: string
tls:
enableTLS: boolean
enableMutualTLS: boolean
provider: ControlPlaneTLSProvider
adminTokenSecretRef: LocalObjectReference
observability:
enableMonitoring: boolean
secretRef: LocalObjectReference
managed:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
credentialsSecretRef: LocalObjectReference
caBundleSecretRef: LocalObjectReference
storageNodeImage: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
status:
phase: ControlPlanePhase
step: KubeSnapshot
endpoint: string
version: string
lastChecked: Time
components:
- name: string
desired: integer
ready: integer
essential: boolean
activeOpsRef: string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
ControlPlane |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
Optional: {} |
|
spec ControlPlaneSpec |
Optional: {} |
||
status ControlPlaneStatus |
Optional: {} |
ControlPlaneComponentStatus
ControlPlaneComponentStatus is one workload of a managed control plane and how much of it is running. The phase is the worst verdict across these and the readiness probe, and only an essential component at zero ready can make it Unavailable.
Appears in: - ControlPlaneStatus
Example:
name: string
desired: integer
ready: integer
essential: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name is the workload's name, as applied. | Required: {} |
|
desired integer |
Desired is how many replicas the component should have. For the component carrying its own operator it is that resource's own count, because a FoundationDBCluster reports quorum rather than replicas. |
Minimum: 0 Optional: {} |
|
ready integer |
Ready is how many of them are. | Minimum: 0 Optional: {} |
|
essential boolean |
Essential states whether this component at zero ready makes the control plane Unavailable rather than Degraded. It is decided by the operator rather than by a user, and it is reported here so that a phase can be explained. |
Optional: {} |
ControlPlaneObservability
ControlPlaneObservability is whether the control plane provisions the monitoring stack, and the credential it does that with.
The control plane provisions it once, when the first storage cluster is created: it opens the Graylog input the log collector ships to, creates the Grafana user every cluster's dashboards use, and widens the OpenSearch result window. It records the decision with the deployment, so enabling monitoring after the first cluster exists sets the environment but does not provision anything.
Appears in: - LocalControlPlane
Example:
enableMonitoring: boolean
secretRef: LocalObjectReference
| Field | Description | Default | Validation |
|---|---|---|---|
enableMonitoring boolean |
EnableMonitoring provisions the monitoring stack when the first storage cluster is created. Unset is off. |
Optional: {} |
|
secretRef LocalObjectReference ⧉ |
SecretRef names a Secret in this namespace holding the monitoring stack's admin password under the key MONITORING_SECRET. Required when EnableMonitoring is set, and its name must not be empty. A pod naming a Secret that does not exist does not start. |
Optional: {} |
ControlPlaneOps
ControlPlaneOps is a single operation performed against the control plane. It runs to a terminal phase and stays afterward as the audit record of what was done, with which parameters, and how it ended. Only one may be active per control plane at a time, which the entity's status.activeOpsRef enforces.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: ControlPlaneOps
metadata:
name: string
spec:
controlPlaneRef: string
action: ControlPlaneOpsAction
abort: boolean
upgrade:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
restart:
components:
- string
backup:
blobStore: string
backupName: string
status:
phase: ControlPlaneOpsPhase
step: KubeSnapshot
message: string
backupRef: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
ControlPlaneOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec ControlPlaneOpsSpec |
|||
status ControlPlaneOpsStatus |
ControlPlaneOpsAction
Underlying type: string
ControlPlaneOpsAction is the operation a ControlPlaneOps performs. Every action acts on what the operator installed, so every action requires a managed control plane. An operation naming a control plane the operator did not install is rejected at creation rather than created and failed.
Validation: - Enum: [Restart Upgrade Backup]
Appears in: - ControlPlaneOpsSpec
| Field | Description |
|---|---|
Restart |
ControlPlaneOpsActionRestart recycles a wedged workload. Its scope is spec.restart.components, and an empty list recycles the whole control plane. |
Upgrade |
ControlPlaneOpsActionUpgrade moves the control plane to a new version and verifies afterward that the version it reports is the one asked for. |
Backup |
ControlPlaneOpsActionBackup asks FoundationDB for a backup outside whatever schedule exists. It asks rather than implements: the snapshot is the FoundationDB operator's to take. |
ControlPlaneOpsPhase
Underlying type: string
ControlPlaneOpsPhase is the operation's own progress.
Validation: - Enum: [Pending Running Succeeded Failed Aborted]
Appears in: - ControlPlaneOpsStatus
| Field | Description |
|---|---|
Pending |
ControlPlaneOpsPhasePending is an operation waiting for its target's lock. |
Running |
ControlPlaneOpsPhaseRunning is an operation holding the lock and working. |
Succeeded |
ControlPlaneOpsPhaseSucceeded is a finished operation that did what it said. |
Failed |
ControlPlaneOpsPhaseFailed is a finished operation that did not. |
Aborted |
ControlPlaneOpsPhaseAborted is an operation stopped on request, whose unwind has finished. |
ControlPlaneOpsSpec
ControlPlaneOpsSpec is one operation to perform against the control plane.
Everything except spec.abort is frozen once the object is admitted, which is what makes the status an audit of the request that ran rather than of whatever the object says now. The parameters are consumed several steps apart: Preflight reads spec.upgrade.image and Applying writes it, and Draining reads spec.restart.components before Restarting recycles them. An edit in between produces an operation that checked one thing and did another.
What is frozen is each parameter block's presence together with its contents.
Appears in: - ControlPlaneOps
Example:
controlPlaneRef: string
action: ControlPlaneOpsAction
abort: boolean
upgrade:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
restart:
components:
- string
backup:
blobStore: string
backupName: string
| Field | Description | Default | Validation |
|---|---|---|---|
controlPlaneRef string |
ControlPlaneRef names the ControlPlane this operation acts on, in this object's own namespace. The operation never owns its target, because deleting the record of an operation must not delete the control plane it operated on. |
Required: {} |
|
action ControlPlaneOpsAction |
Action is the operation to perform. Immutable, so that the status describes the operation that ran. |
Enum: [Restart Upgrade Backup] Required: {} |
|
abort boolean |
Abort asks a running operation to stop at its next step and unwind. It is the one field of this spec an update may change, because it is the one that is meant to be set after the operation started. Whether an abort is expressible from the current step is declared by that action's graph rather than checked here. |
Optional: {} |
|
upgrade UpgradeSpec |
Upgrade parameterizes action Upgrade and is ignored by the others. | Optional: {} |
|
restart RestartSpec |
Restart parameterizes action Restart and is ignored by the others. | Optional: {} |
|
backup BackupSpec |
Backup parameterizes action Backup and is ignored by the others. | Optional: {} |
ControlPlaneOpsStatus
ControlPlaneOpsStatus is the observed state of one control-plane operation.
Appears in: - ControlPlaneOps
Example:
phase: ControlPlaneOpsPhase
step: KubeSnapshot
message: string
backupRef: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase ControlPlaneOpsPhase |
Phase is the operation's own progress. | Enum: [Pending Running Succeeded Failed Aborted] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the running action's state machine. It is persisted before the side effect that step performs, so a step reported here is a step that started. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the operation moves, and never a log. |
Optional: {} |
|
backupRef string |
BackupRef names the FoundationDBBackup a Backup run created or triggered. The operation does not own it, because deleting the record of a backup must not delete the backup's configuration. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation acquired its target's lock. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when it reached a terminal phase. | Optional: {} |
ControlPlanePhase
Underlying type: string
ControlPlanePhase is where the operator has got to with this control plane.
Validation: - Enum: [Installing Available Degraded Unavailable]
Appears in: - ControlPlaneStatus
| Field | Description |
|---|---|
Installing |
ControlPlanePhaseInstalling is a control plane that has not worked yet. |
Available |
ControlPlanePhaseAvailable is one whose readiness probe passes and whose workload pods are settled. |
Degraded |
ControlPlanePhaseDegraded is one whose readiness probe passes while a management API or FoundationDB pod is restarting. It answers every request, so nothing holds on it and it exists to be read by a person. A control plane the operator does not manage never reaches it, because the operator owns no pods there to watch. |
Unavailable |
ControlPlanePhaseUnavailable is one whose readiness probe fails: it worked and stopped, which is a different situation from one that never started, and it is what downstream controllers hold on. The word claims only what the probe observed, since a failing probe cannot tell a crashed process from a wedged one or from a partition. |
ControlPlaneSource
ControlPlaneSource selects whether this cluster hosts its control plane or is managed by one elsewhere. Exactly one member is set, which is what makes the two modes siblings rather than two unrelated top-level fields.
Which member it is cannot change afterward, while the members themselves stay editable: changing spec.source.local.image is an ordinary edit, and it is what a ControlPlaneOps upgrade performs.
Appears in: - ControlPlaneSpec
Example:
local:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
foundationDB:
replicas: integer
storageClassName: string
resources: ResourceRequirements
nodeSelector:
string: string
replicas: integer
resources: ResourceRequirements
tolerations:
- Toleration
nodeSelector:
string: string
tls:
enableTLS: boolean
enableMutualTLS: boolean
provider: ControlPlaneTLSProvider
adminTokenSecretRef: LocalObjectReference
observability:
enableMonitoring: boolean
secretRef: LocalObjectReference
managed:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
credentialsSecretRef: LocalObjectReference
caBundleSecretRef: LocalObjectReference
storageNodeImage: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
| Field | Description | Default | Validation |
|---|---|---|---|
local LocalControlPlane |
Local is a control plane the operator installs. | Optional: {} |
|
managed ManagedControlPlane |
Managed is a control plane that already exists. | Optional: {} |
ControlPlaneSpec
ControlPlaneSpec is the desired state of the simplyblock control plane for one namespace.
Appears in: - ControlPlane
Example:
source:
local:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
foundationDB:
replicas: integer
storageClassName: string
resources: ResourceRequirements
nodeSelector:
string: string
replicas: integer
resources: ResourceRequirements
tolerations:
- Toleration
nodeSelector:
string: string
tls:
enableTLS: boolean
enableMutualTLS: boolean
provider: ControlPlaneTLSProvider
adminTokenSecretRef: LocalObjectReference
observability:
enableMonitoring: boolean
secretRef: LocalObjectReference
managed:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
credentialsSecretRef: LocalObjectReference
caBundleSecretRef: LocalObjectReference
storageNodeImage: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
| Field | Description | Default | Validation |
|---|---|---|---|
source ControlPlaneSource |
Source selects whether this cluster hosts its control plane or is managed by one elsewhere. Switching a live deployment between the two is not a reconfiguration, because the clusters and their volumes live in the FoundationDB behind the old one, so which mode is chosen is frozen at creation. What is inside the chosen mode stays editable. Both rules are declared on ControlPlaneSource rather than here. See the type for why. |
Required: {} |
ControlPlaneStatus
ControlPlaneStatus is the observed state of the control plane.
Appears in: - ControlPlane
Example:
phase: ControlPlanePhase
step: KubeSnapshot
endpoint: string
version: string
lastChecked: Time
components:
- name: string
desired: integer
ready: integer
essential: boolean
activeOpsRef: string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
phase ControlPlanePhase |
Phase is the operator's own view of the control plane. | Enum: [Installing Available Degraded Unavailable] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the installation machine within Installing. The rule repeats the ControlPlaneStep enum because a marker cannot reach a field of the shared snapshot type. |
Optional: {} |
|
endpoint string |
Endpoint is the resolved management API base URL, derived in the local case and echoed in the managed one. It is what every controller in the operator reads to reach the control plane, so that one object answers where it is. |
Optional: {} |
|
version string |
Version is the version the management API reports. | Optional: {} |
|
lastChecked Time ⧉ |
LastChecked is when the readiness probe last ran. | Optional: {} |
|
components ControlPlaneComponentStatus array |
Components is the per-component readiness the phase is derived from, one entry per workload the managed install applies. It is empty for a remote control plane, which has no components the operator owns. Without it a Degraded phase says that something is wrong and not what. |
Optional: {} |
|
activeOpsRef string |
ActiveOpsRef names the ControlPlaneOps currently allowed to act on this control plane. Empty when none is running. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the control plane moves, and never a log. On a failed probe it is the control plane's own error rather than a paraphrase of it. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
ControlPlaneTLS
ControlPlaneTLS is how a control plane this operator installs serves, and what it requires of the clients that reach it.
The field names are DriverTLS's, because they are the same three decisions about the same connection seen from its two ends, and a reader who knows one should not have to learn the other. What differs is the default: the CSI driver's fields were added to describe deployments that already existed and default off, and these default on. An installed control plane holds every cluster definition, node registration, and volume record, and is reached over the pod network by the operator, the CSI driver, and the metrics scrape alike, so plaintext is a decision to state rather than the state a deployment lands in by leaving the block out.
Appears in: - LocalControlPlane
Example:
enableTLS: boolean
enableMutualTLS: boolean
provider: ControlPlaneTLSProvider
| Field | Description | Default | Validation |
|---|---|---|---|
enableTLS boolean |
EnableTLS serves the management API over TLS. Unset is on. | true | Optional: {} |
enableMutualTLS boolean |
EnableMutualTLS additionally requires a caller to present a certificate of its own, rather than reaching the control plane anonymously over the encrypted connection EnableTLS alone provides. Ignored when EnableTLS is false, the same as on DriverTLS. Unset is on. |
true | Optional: {} |
provider ControlPlaneTLSProvider |
Provider issues the serving certificate. | cert-manager | Enum: [cert-manager OpenShift] Optional: {} |
ControlPlaneTLSProvider
Underlying type: string
ControlPlaneTLSProvider is who issues the control plane's serving certificate.
The two values are each product's own spelling rather than this group's PascalCase, because they are not this group's words: they are what internal/utils/tls.go already matches on and what the control-plane image reads out of SB_TLS_PROVIDER.
Validation: - Enum: [cert-manager OpenShift]
Appears in: - ControlPlaneTLS
| Field | Description |
|---|---|
cert-manager |
ControlPlaneTLSCertManager issues through cert-manager, from the CA the deployment's ClusterIssuer mints. |
OpenShift |
ControlPlaneTLSOpenShift issues through the OpenShift service CA, which signs from a Service annotation rather than from an object of its own. |
CreatorReference
CreatorReference names the object that created a PersistentVolumeOps.
It exists because a cluster-scoped object cannot be owned by a namespaced one: Kubernetes treats such a reference as unresolvable and garbage-collects the dependent. Core Kubernetes has the same situation twice and answers it the same way: a PersistentVolume names its claim through spec.claimRef and a VolumeSnapshotContent names its snapshot through spec.volumeSnapshotRef, both with a UID, and that shape carries over here unchanged.
Appears in: - PersistentVolumeOpsSpec
Example:
kind: string
namespace: string
name: string
uid: UID
| Field | Description | Default | Validation |
|---|---|---|---|
kind string |
Kind is the creating object's kind, which is StorageNodeOps for a drain. | Required: {} |
|
namespace string |
Namespace is where the creator lives. | Required: {} |
|
name string |
Name is the creator's object name. | Required: {} |
|
uid UID ⧉ |
UID is what makes the reference address one creator rather than one name: a creator deleted and recreated under the same name must not inherit the fan-out it did not issue, and cascade a delete over it. |
Required: {} |
DataRealignmentSettings
DataRealignmentSettings tunes the post-migration control-plane data realignment, which re-aligns the control plane's internal structures to where volumes now are and restores the fault-tolerance and node-affinity guarantees a move invalidated.
Whether it runs at all is StorageClusterSpec.DisableDataRealignment, a field of the spec rather than of this block: a toggle named for its subject repeats itself when the subject is also its parent.
Appears in: - VolumeMigrationSettings
Example:
interval: Duration
minMoves: integer
| Field | Description | Default | Validation |
|---|---|---|---|
interval Duration ⧉ |
Interval is a floor on the spacing between realignment requests, and not a ceiling on how long one takes: a realignment blocks every volume migration for as long as the control plane needs, measured at tens of minutes on a loaded cluster. The trigger annotation bypasses it. Defaults to 10m. |
Optional: {} |
|
minMoves integer |
MinMoves is how many volume moves accumulate before a realignment is requested. It defaults to 1, which makes migration and realignment alternate, and is the field to raise in order to batch: a higher value trades how promptly the structures are realigned for migration throughput. The trigger annotation bypasses it. |
Minimum: 1 Optional: {} |
DeploymentImages
DeploymentImages is every image a deployment pins, in one block.
All three are software that runs on a storage node, which is what the slot names say and what an earlier spelling of the first one hid. They are together rather than each beside the object it configures because pinning images is one decision taken once: an air-gapped installation overrides all three against its own registry, and a reviewer reading the document has one place to check what this deployment will run. The expansion is what spends them on two different objects, since the three fields they land on are not all on the same kind.
The control plane's own image is not here. A ClusterDeploymentConfig creates a StorageCluster and its StorageNodes and neither creates nor adopts the ControlPlane, so a slot for it would be a field the expansion has nowhere to write. It is ControlPlane.spec.source.local.image, and the CSI driver's is SimplyblockDriver.spec.image.
Appears in: - ClusterDeploymentConfigSpec
Example:
nodeAgent:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
spdk:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
spdkProxy:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
| Field | Description | Default | Validation |
|---|---|---|---|
nodeAgent ImageSpec |
NodeAgent is the image the storage-node DaemonSet runs: the agent the control plane drives a worker through, and the two init containers that configure the host before it starts. The expansion writes it to StorageCluster.spec.storageNodes, which is where the retired StorageNodeSet.spec.clusterImage went. It is the same artifact the control plane itself runs, which is why spec.storageNodes.image defaults to the ControlPlane singleton's: the agent and the tasks that call it are one codebase, and version skew between them is what breaks a node add. It is spent only where the document creates the cluster. A document that names an existing one in ClusterRef adds nodes to a DaemonSet that is already running under an image the cluster states, and this slot is ignored the same way Cluster is. |
Optional: {} |
|
spdk ImageSpec |
SPDK is the SPDK image, which the expansion writes onto every StorageNode.spec.config it creates rather than onto the cluster: the field is per node so that a later rollout can walk the fleet one machine at a time, and a document states the fleet's starting point. |
Optional: {} |
|
spdkProxy ImageSpec |
SPDKProxy is the SPDK proxy image, written per node for the reason SPDK is. | Optional: {} |
DeviceCapacity
DeviceCapacity is how big the device is. It carries no used size and no sample time, because a physical device cannot be resized: the number is a property of the hardware, it is written when the device is discovered, and nothing rewrites it. A device reporting a different size is a different device, which arrives as its own object.
What the device holds is the number that moves, and it is served from metrics.simplyblock.io as StorageDeviceMetrics rather than published here. A reading that changes continuously is not desired state, and keeping it in a status would write etcd on every sample and wake every watcher of the kind for it.
Appears in: - StorageDeviceStatus
Example:
totalBytes: integer
| Field | Description | Default | Validation |
|---|---|---|---|
totalBytes integer |
TotalBytes is the device's usable size, as the control plane reports it with the device. |
Minimum: 0 Optional: {} |
DeviceFilter
DeviceFilter narrows what a discovery run reports as a candidate device. It is an input to discovery and never appears in the document discovery writes: a ClusterDeploymentConfig carries the explicit list the filter produced, not the rule that produced it.
Every member narrows the devices of the class a run scans. Neither choosing that class nor waiving an availability condition is a member: both are statements about the run, spec.discover.enableLogicalBlockDevices and spec.discover.enablePartitionedDevices, because the first decides which kind of cluster the draft describes and the second widens what is reported. The filters come in two sets, one per class, and the rules on DiscoverSpec reject the set belonging to the class the run is not scanning.
Appears in: - DiscoverSpec
Example:
pcieAllowList:
- string
pcieDenyList:
- string
pcieModel: string
blockAllowList:
- '^/dev/[a-zA-Z0-9._/-]+$'
blockDenyList:
- '^/dev/[a-zA-Z0-9._/-]+$'
driveSizeRange: string
| Field | Description | Default | Validation |
|---|---|---|---|
pcieAllowList string array |
PcieAllowList restricts candidates to these PCI addresses. This and the two PCI filters below narrow the NVMe class alone, because a logical block device has no PCI address to match, so setting any of them on a run that scans the block class is rejected by the rule on this type. |
Optional: {} |
|
pcieDenyList string array |
PcieDenyList excludes these PCI addresses. On a fleet that is uniform about which slot holds the boot device, this is what keeps that device out of every group of every draft. |
Optional: {} |
|
pcieModel string |
PcieModel restricts candidates to devices whose PCI model string matches. | Optional: {} |
|
blockAllowList string array |
BlockAllowList restricts candidates to these device paths ("/dev/sdb"). This and BlockDenyList are the block class's half of the filter, and they require EnableLogicalBlockDevices for the same reason the PCI filters forbid it. |
items:Pattern: ^/dev/[a-zA-Z0-9._/-]+$ Optional: {} |
|
blockDenyList string array |
BlockDenyList excludes these device paths. On a fleet that boots from /dev/sda, this is the one entry that keeps the root disk out of every group of every draft. |
items:Pattern: ^/dev/[a-zA-Z0-9._/-]+$ Optional: {} |
|
driveSizeRange string |
DriveSizeRange restricts candidates by size ("100G-2T"). Unlike the per-class filters, it applies to whichever class the run is scanning. |
Optional: {} |
DeviceHardware
DeviceHardware identifies the part. These are what somebody walking into a datacenter with a failed drive needs, and none of them is recoverable from the control plane's device id alone. Every field is optional, because which ones a device has depends on how it is attached.
The design's devicePath is absent: the control plane reports no host path for a device, only the NVMe controller it hangs off, so the field would never be populated. NVMeController carries what is actually reported.
Appears in: - StorageDeviceStatus
Example:
pciAddress: string
serialNumber: string
model: string
nvmeController: string
| Field | Description | Default | Validation |
|---|---|---|---|
pciAddress string |
PCIAddress is the device's address on the host ("0000:5e:00.0"), where it has one. A logical block device may not: the address can belong to the controller it hangs off rather than to the device. |
Optional: {} |
|
serialNumber string |
SerialNumber is what is printed on the drive. | Optional: {} |
|
model string |
Model is the manufacturer's model string. | Optional: {} |
|
nvmeController string |
NVMeController is the controller the device is served through, in the control plane's spelling. |
Optional: {} |
DeviceSelection
DeviceSelection is the explicit list of storage devices a group's workers hand to simplyblock. It carries no filter of any kind: a document whose meaning depends on what the hardware turns out to be is not a document a reviewer can approve, so filtering happens in the discovery run that produces the list and what lands here is the result. It expands to the matching fields of StorageNode.spec.config, which carry the same meanings.
One member and not both. A cluster is built out of one class of backend storage, so a group hands over NVMe devices or logical block devices, and the rule below is the half of that a single group can be checked against. That every group of the document agrees is the spec's rule.
Appears in: - NodeGroup
Example:
nvme:
- '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
block:
- '^/dev/[a-zA-Z0-9._/-]+$'
| Field | Description | Default | Validation |
|---|---|---|---|
nvme string array |
NVMe names NVMe devices by PCI address ("0000:5e:00.0"). | MaxItems: 128 items:MaxLength: 32 items:Pattern: ^[0-9a-fA-F]\{4\}:[0-9a-fA-F]\{2\}:[0-9a-fA-F]\{2\}\.[0-9a-fA-F]$ Optional: {} |
|
block string array |
Block names logical block devices by path ("/dev/sdb"). It expands into the same config.deviceNames as NVMe, which takes a PCI address and a device path in one list. It is the alternative to NVMe rather than a companion of it: the two classes are not mixed within a cluster. |
MaxItems: 128 items:MaxLength: 255 items:Pattern: ^/dev/[a-zA-Z0-9._/-]+$ Optional: {} |
DiscoverSpec
DiscoverSpec parameterizes the Discover action.
Which workers a run inspects is stated one of two ways and never both: by name in Workers, or by label in NodeSelector. They are exclusive rather than intersected because the intersection of a name list and a label selector is a question nobody asks deliberately, and reading one as narrowing the other would make a run inspect fewer machines than either field says.
The device filters come in two sets, one per device class, and a run scans one class. The two class rules reject the set belonging to the class this run is not scanning, because a filter that will never be applied is one an administrator reads as having narrowed a draft that was never narrowed.
Appears in: - OperatorOpsSpec
Example:
configName: string
workers:
- string
nodeSelector:
string: string
tolerations:
- Toleration
enableControlPlaneNodes: boolean
enableLogicalBlockDevices: boolean
enablePartitionedDevices: boolean
forceJournalDevice: boolean
deviceFilter:
pcieAllowList:
- string
pcieDenyList:
- string
pcieModel: string
blockAllowList:
- '^/dev/[a-zA-Z0-9._/-]+$'
blockDenyList:
- '^/dev/[a-zA-Z0-9._/-]+$'
driveSizeRange: string
clusterRef: string
| Field | Description | Default | Validation |
|---|---|---|---|
configName string |
ConfigName is the ClusterDeploymentConfig to write. Absent generates one from the run's timestamp, so that a second discovery never overwrites the first, which may have been reviewed and corrected. |
Optional: {} |
|
workers string array |
Workers are the workers to inspect, by node name. It is the answer to inspecting two named machines, which a label selector can only express by labeling them first: a selector's entries are ANDed, so two hostnames in one selector match nothing at all. A named worker that does not exist, or that is declined for one of the reasons any worker is declined, is reported by the same event the selector path reports it by. Naming a worker is a statement about which machines to consider, not a claim that each of them will be used. |
MaxItems: 128 items:MaxLength: 253 Optional: {} |
|
nodeSelector object (keys:string, values:string) |
NodeSelector restricts which workers are inspected. Empty inspects every schedulable worker, and it is exclusive with Workers. |
Optional: {} |
|
tolerations Toleration ⧉ array |
Tolerations are what the probe pods tolerate, and what the draft states for the storage nodes it proposes. A probe is pinned to its worker with spec.nodeName rather than scheduled onto it, which bypasses the scheduler and not the taints: a NoSchedule taint still keeps the pod off, and a NoExecute taint evicts one that landed. A fleet that dedicates machines to storage taints them, so a run against one that tolerates nothing inspects nothing. They reach the draft as well, because the taints a run was allowed to probe through are the taints the cluster it proposes has to live with. Stating them in one place is what keeps a reviewer from approving a document whose DaemonSet schedules nowhere. |
MaxItems: 32 Optional: {} |
|
enableControlPlaneNodes boolean |
EnableControlPlaneNodes lets the run consider machines that run the API server and etcd. It is off by default because a storage node is a data path, and putting one on an etcd host is a placement almost nobody intends. The approval gate is a poor place to catch it: a fifty-worker draft is not a document anybody reads closely enough to spot three control-plane nodes in it. A combined three-node or single-node deployment is the case that wants it, and those are set up deliberately. There is no field beside it for infrastructure nodes, because those are used without asking: an OpenShift infra node is the tier a cluster's own infrastructure runs on, and simplyblock storage is infrastructure. A fleet with disks in its infra nodes meant those disks to be the storage, so a draft proposes them ahead of the workers rather than leaving them out. |
Optional: {} |
|
enableLogicalBlockDevices boolean |
EnableLogicalBlockDevices scans a worker's available logical block devices instead of its available NVMe devices. It selects the class rather than adding one, because the draft a run writes describes one cluster and a cluster is built out of one class. Unset scans NVMe, so that upgrading to 26.4 does not change what a discovery run reports. It is a statement about the run rather than a member of DeviceFilter, because it does not narrow the devices reported: it decides which class of them is looked at, which filters in DeviceFilter apply, and what ForceJournalDevice resolves, since the two classes lay out a journal differently. |
Optional: {} |
|
enablePartitionedDevices boolean |
EnablePartitionedDevices reports devices carrying a partition table alongside the available ones, for the administrator who knows the table is stale and intends to hand the device over anyway. It is the only one of the three availability conditions that can be waived: a mounted or otherwise busy device is never reported, because simplyblock taking it would corrupt whatever is using it. It is a statement about the run rather than a member of DeviceFilter for the reason EnableLogicalBlockDevices is: it waives an availability condition for whichever class is scanned, and so widens what is reported, where every member of the filter narrows it. |
Optional: {} |
|
forceJournalDevice boolean |
ForceJournalDevice makes the run dedicate a journal device even where the fleet's disks do not say which one. A run proposes a dedicated journal device when every worker hands over one disk smaller than its others, because a fleet built that way was built that way on purpose. Where several disks share the smallest size, nothing says which to take, and taking one spends a whole disk the fleet did not set aside: on a worker with ten 10 TB disks that is 10 TB, silently. So the run does not guess. What it does instead depends on the class. An NVMe cluster with no dedicated journal device carves a journal partition out of every device, so the run leaves the field unset, says so, and every disk stays storage. A logical block-device cluster has no such layout — the control plane refuses the partitioned journal for the class — so the run fails rather than writing a document that creates the cluster, formats its drives, and then fails every node_add. Setting this is what asks for one of the equal disks to be taken anyway, and the draft says the disk was taken rather than offered. It does not force the cases that are impossible rather than ambiguous. A worker handing over a single disk is refused with it or without it, because dedicating that disk leaves the worker nothing to store on, and so is a worker whose disks report no size. The tie it does resolve is broken by address, ascending, so two runs over one unchanged fleet propose the same document. |
Optional: {} |
|
deviceFilter DeviceFilter |
DeviceFilter narrows which of an inspected worker's devices reach the draft. Empty reports every device the worker advertises, including the one it boots from, which is what the approval gate then has to catch. |
Optional: {} |
|
clusterRef string |
ClusterRef names an existing StorageCluster the draft grows rather than creates. It is copied to the draft's own clusterRef, so that re-running discovery after an expansion produces a growth document naming the same cluster. Bounded at what a StorageCluster name may be, since a longer value names nothing that can exist. |
MaxLength: 63 Optional: {} |
DrainStatus
DrainStatus is the drain's progress over the volumes on the node being removed. Neither field takes omitempty: zero is meaningful for both, and a field that disappears at zero makes "nothing to move" and "not yet counted" the same wire value.
Appears in: - StorageNodeOpsStatus
Example:
volumesTotal: integer
volumesMigrated: integer
| Field | Description | Default | Validation |
|---|---|---|---|
volumesTotal integer |
VolumesTotal is the number of PV-managed volumes the drain has to move, written once at the end of Validating and not modified afterward. |
Minimum: 0 |
|
volumesMigrated integer |
VolumesMigrated is how many of them have completed. | Minimum: 0 |
DriverPNFS
DriverPNFS configures pNFS support, which makes the node plugin an NFS metadata server as well as an NVMe-oF initiator: on the host an export binds to, it makes a filesystem on the namespace, mounts it, and publishes it through the host's nfsd.
Turning it on is not sufficient. The host needs nfs-utils and a running nfsd, and a client host needs blkmapd, neither of which a pod can install. A host missing either fails visibly in the export's Assembling phase.
Appears in: - SimplyblockDriverSpec
Example:
enablePNFS: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
enablePNFS boolean |
EnablePNFS gives the node plugin the four host directories an export is assembled through. It is gated because a plugin not serving exports has no use for them. |
false | Optional: {} |
DriverTLS
DriverTLS configures whether this deployment's two plugins reach the control plane over TLS. Unset (every field at its zero value) is a plaintext data path, which is what every deployment ran as before this field existed.
The client-certificate Secret each plugin mounts is not named here. It is
<object name>-csi-controller-client-tls and
<object name>-csi-node-client-tls, derived from the object's name like
every other object this deployment creates, and a field naming them again
would be a second place for the two to disagree.
Appears in: - SimplyblockDriverSpec
Example:
enableTLS: boolean
enableMutualTLS: boolean
provider: DriverTLSProvider
| Field | Description | Default | Validation |
|---|---|---|---|
enableTLS boolean |
EnableTLS turns on TLS between both plugins and the control plane. | false | Optional: {} |
enableMutualTLS boolean |
EnableMutualTLS additionally requires each plugin to present a client certificate, rather than dialing the control plane anonymously over the encrypted connection EnableTLS alone provides. Ignored when EnableTLS is false, the same as the Helm value it replaces. |
false | Optional: {} |
provider DriverTLSProvider |
Provider selects where the CA bundle (and, with EnableMutualTLS, the client certificate) comes from. Required reading whenever EnableTLS is true: the two providers mount a differently shaped volume, and neither shape can be inferred from anything else on this object. |
cert-manager | Enum: [OpenShift cert-manager] Optional: {} |
DriverTLSProvider
Underlying type: string
DriverTLSProvider is where the TLS certificate on this connection comes from. The values are not this group's to spell: OpenShift and cert-manager are the two products, and the operator's own internal/utils package already carries these exact strings for the control plane's own SB_TLS_PROVIDER, so a driver and a control plane in the same namespace agree on the same word without a translation table between them.
Validation: - Enum: [OpenShift cert-manager]
Appears in: - DriverTLS
| Field | Description |
|---|---|
OpenShift |
DriverTLSProviderOpenShift is OpenShift's service-ca operator: a ConfigMap carrying the cluster CA, and a Secret an administrator provisions for each plugin's client certificate. |
cert-manager |
DriverTLSProviderCertManager is cert-manager: a ClusterIssuer already installed by this chart mints a Certificate per plugin, and the Secret it writes carries the CA bundle alongside the client keypair. |
FailureDomainIndex
FailureDomainIndex is one failure-domain label and the control plane's index for it.
Appears in: - StorageClusterStatus
Example:
name: string
index: integer
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name is the failure-domain label, as StorageNode.spec.config.failureDomain spells it ("rack-b"). |
MaxLength: 63 MinLength: 1 Required: {} |
|
index integer |
Index is the integer sent to the control plane for every node in the domain. |
Minimum: 0 Required: {} |
FoundationDBSpec
FoundationDBSpec is the sizing of the FoundationDB the operator installs.
Appears in: - LocalControlPlane
Example:
replicas: integer
storageClassName: string
resources: ResourceRequirements
nodeSelector:
string: string
| Field | Description | Default | Validation |
|---|---|---|---|
replicas integer |
Replicas is the number of coordinators. Three is the smallest count that survives one loss, which is why it is the default. |
3 | Minimum: 1 Optional: {} |
storageClassName string |
StorageClassName is the class the coordinators' volumes are provisioned from. It cannot be a class this operator provides, because the control plane has to exist before any simplyblock volume can. |
Optional: {} |
|
resources ResourceRequirements ⧉ |
Resources sets requests and limits for the coordinator pods. | Optional: {} |
|
nodeSelector object (keys:string, values:string) |
NodeSelector pins the database's own processes, and nothing else the control plane runs. Unset leaves them wherever the control plane as a whole is placed. It is stated apart from that placement because the database is the part whose host matters to something else: adding a storage node reboots its worker, and a worker running a FoundationDB process is added on its own however many nodes the cluster's provisioning budget allows at once. Confining the database to a few workers is what leaves the rest of the fleet free to be added together. It has to select at least as many workers as spec.source.local.foundationDB.replicas, because the worker is the database's fault domain. The anti-affinity between processes of one class is a preference rather than a requirement, so a narrower selector places the coordinators anyway, several to a worker, where one worker's loss takes more of them than the redundancy mode was configured to survive. |
Optional: {} |
HostOSFamily
Underlying type: string
HostOSFamily is the packaging tradition a Linux distribution belongs to.
It is the coarse half of what a host OS is, and the half most decisions are actually about: what differs between Ubuntu and Debian is rarely what a storage node needs, and what differs between Ubuntu and Rocky always is. There is no member for a host with no package manager: Talos and Flatcar are not a family with no name, they are machines where the question does not arise, and a document describing one leaves the family unstated.
Validation: - Enum: [Debian RedHat SUSE Alpine Arch]
Appears in: - HostOSSpec
| Field | Description |
|---|---|
Debian |
|
RedHat |
|
SUSE |
|
Alpine |
|
Arch |
HostOSSpec
HostOSSpec is the operating system a deployment's workers run.
It is a fact about the machines rather than about Kubernetes, which is why it is stated here and not derived from spec.environment: a fleet on OpenShift runs Red Hat Enterprise Linux CoreOS, and a fleet on K3s runs whatever its administrator installed. A discovery run fills it in from what the probes read, and fills it in only when every worker agrees, so a document that states one is a document whose fleet is uniform.
Appears in: - ClusterDeploymentConfigSpec
Example:
distro: '^[a-z0-9][a-z0-9._-]*$'
family: HostOSFamily
| Field | Description | Default | Validation |
|---|---|---|---|
distro string |
Distro is the distribution's os-release ID, lowercase and verbatim:ubuntu, rocky, rhel, talos. It is what the expansion reads. |
MaxLength: 63 Pattern: ^[a-z0-9][a-z0-9._-]*$ Optional: {} |
|
family HostOSFamily |
Family is the packaging tradition Distro belongs to. A discovery run concludes it from the distribution itself, or from the distributions its os-release says it is built on, which is what places a derivative this product has never heard of. It is left unstated for a host with no package manager. |
Enum: [Debian RedHat SUSE Alpine Arch] Optional: {} |
ImageSpec
ImageSpec is one container image and when to pull it, which is the pair every image in this product is stated as.
Both members are optional so that each can be stated without the other: an air-gapped deployment overrides the image and keeps the policy, and a development one keeps the image and pins the policy to IfNotPresent so a tag rebuilt in place is not picked up mid-deployment.
Appears in: - DeploymentImages
Example:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
| Field | Description | Default | Validation |
|---|---|---|---|
image string |
Image is the repository and tag, optionally digest-pinned. An empty value is not written downstream, so the field it would fill keeps its own default rather than being overridden with nothing. The registry set is the one every image field in this API is held to, which is what keeps a reviewed document from naming a build nobody published. |
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
|
imagePullPolicy PullPolicy ⧉ |
ImagePullPolicy is when that image is pulled. It defaults to Always, because every image this product ships by default is a moving tag and a node brought up after a release otherwise runs what its kubelet held. |
Always | Enum: [Always Never IfNotPresent] Optional: {} |
JournalManagerSpec
JournalManagerSpec tunes the journal managers on one storage node.
Appears in: - NodeGroup - StorageNodeConfig
Example:
count: integer
percentPerDevice: integer
| Field | Description | Default | Validation |
|---|---|---|---|
count integer |
Count is the number of journal managers to configure. The control plane requires at least 3. |
Minimum: 3 Optional: {} |
|
percentPerDevice integer |
PercentPerDevice is the share of each device given to the journal. | Maximum: 100 Minimum: 1 Optional: {} |
KMSSpec
KMSSpec selects where the cluster stores volume encryption keys. It is a block with one member per provider so that the providers are siblings, which is what makes choosing between them expressible.
Appears in: - ClusterTemplate - StorageClusterSpec
Example:
vault:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
| Field | Description | Default | Validation |
|---|---|---|---|
vault VaultKMS |
Vault stores keys in HashiCorp Vault. | Optional: {} |
KubernetesEnvironment
Underlying type: string
KubernetesEnvironment is the distribution a deployment targets. The values are the distributions' own names.
Validation: - Enum: [Vanilla OpenShift Rancher K3s Talos]
Appears in: - ClusterDeploymentConfigSpec - OperatorOpsStatus
| Field | Description |
|---|---|
Vanilla |
|
OpenShift |
|
Rancher |
|
K3s |
|
Talos |
LocalControlPlane
LocalControlPlane is a control plane this cluster hosts, installed and owned by the operator. Its objects carry a controller reference to the ControlPlane, so the ownership spine starts at a real edge rather than at a Helm release.
Appears in: - ControlPlaneSource
Example:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
foundationDB:
replicas: integer
storageClassName: string
resources: ResourceRequirements
nodeSelector:
string: string
replicas: integer
resources: ResourceRequirements
tolerations:
- Toleration
nodeSelector:
string: string
tls:
enableTLS: boolean
enableMutualTLS: boolean
provider: ControlPlaneTLSProvider
adminTokenSecretRef: LocalObjectReference
observability:
enableMonitoring: boolean
secretRef: LocalObjectReference
| Field | Description | Default | Validation |
|---|---|---|---|
image string |
Image is the management API and control-plane image. Must reference one of the trusted registries ( quay.io/simplyblock-io,docker.io/simplyblock, public.ecr.aws/simply-block); digest pinning(@sha256:...) is recommended. |
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Required: {} |
|
imagePullPolicy PullPolicy ⧉ |
ImagePullPolicy controls when that image is pulled. | IfNotPresent | Enum: [Always Never IfNotPresent] Optional: {} |
foundationDB FoundationDBSpec |
FoundationDB sizes the FoundationDB the management API stores its state in. | Optional: {} |
|
replicas integer |
Replicas is the number of management API instances. Two is what the chart ships and what the phases assume: a single instance makes Degraded unreachable for this component and every restart an outage. |
2 | Minimum: 1 Optional: {} |
resources ResourceRequirements ⧉ |
Resources sets requests and limits for the management API pods. | Optional: {} |
|
tolerations Toleration ⧉ array |
Tolerations are applied to every pod the operator installs for the control plane. |
Optional: {} |
|
nodeSelector object (keys:string, values:string) |
NodeSelector pins every pod the operator installs for the control plane. It is a selector rather than an affinity term because that is what the chart it replaces took, and a deployment migrating off the chart has the value already written down. |
Optional: {} |
|
tls ControlPlaneTLS |
TLS is how this control plane serves and what it asks of its callers. The block is defaulted to its own zero value rather than left absent, so that the field defaults inside it are applied to an object that does not mention TLS at all. |
{ } | Optional: {} |
adminTokenSecretRef LocalObjectReference ⧉ |
AdminTokenSecretRef names a Secret in this namespace holding a static admin bearer token this control plane accepts, under the token key, inaddition to this deployment's own Kubernetes identity (SB_K8S_ADMIN_SERVICE_ACCOUNTS). It is what lets a cluster this control plane manages remotely (spec.source.managed there, ManagedControlPlane.CredentialsSecretRef naming the same value) authenticate a CreateCluster call, since a Kubernetes TokenReview can never cross a cluster boundary. The Secret is projected into the management API container's environment with secretKeyRef, so this operator never itself reads the plaintext. Absent grants no credential beyond the operator's own service account. |
Optional: {} |
|
observability ControlPlaneObservability |
Observability connects this control plane to the monitoring stack installed beside it: Graylog, Grafana, and OpenSearch. Absent leaves monitoring off. |
Optional: {} |
ManagedControlPlane
ManagedControlPlane is a control plane somewhere else, which this cluster's storage is managed by rather than hosting. The operator installs nothing and owns nothing here: it resolves the endpoint, probes it, and reports.
The word is about what manages the storage clusters rather than about who runs the operator. A deployment in this mode registers its clusters with a control plane it does not host, so the fleet is administered from there.
Appears in: - ControlPlaneSource
Example:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
credentialsSecretRef: LocalObjectReference
caBundleSecretRef: LocalObjectReference
storageNodeImage: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
| Field | Description | Default | Validation |
|---|---|---|---|
endpoint string |
Endpoint is the management API's base URL. It is validated against the same outbound-URL guard every other outbound endpoint in this group uses, so a loopback or link-local address is rejected. |
Pattern: ^https?://[a-zA-Z0-9.-]+(:[0-9]\{1,5\})?(/.*)?$ Required: {} |
|
credentialsSecretRef LocalObjectReference ⧉ |
CredentialsSecretRef names a Secret in this namespace holding the bearer token the operator authenticates with. It is a reference rather than a field because a token in a spec is a token in every kubectl get -o yaml.Absent means the endpoint is reached without one, which is the in-cluster case: a control plane the Helm chart installed answers on a ClusterIP Service in this namespace and does not require a token for the readiness read. Naming a Secret that does not exist stays an error, because naming one is a statement that the control plane needs it. |
Optional: {} |
|
caBundleSecretRef LocalObjectReference ⧉ |
CABundleSecretRef names a Secret holding the CA certificate the endpoint is verified against. Absent means the system trust store. |
Optional: {} |
|
storageNodeImage string |
StorageNodeImage is the storage-node image a StorageCluster on this Kubernetes cluster defaults to when its own spec.storageNodes.image is unset. A local control plane's own spec.source.local.image doubles as this default (StorageNodeWorkloadReconciler.image), because a self-hosted deployment's control plane and its storage nodes are one release. A managed one is a different Kubernetes cluster's install and says nothing about what this cluster's storage nodes should run, so there is no equivalent to fall back to without this field -- every StorageCluster on a managed deployment must get an image from here or from its own spec. |
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
MetricsBackend
Underlying type: string
MetricsBackend selects where the rebalancer reads a node's I/O load from. The values are PascalCase, as every enum this group defines is; v1alpha1 spelled them lowercase and the conversion maps between the two.
Validation: - Enum: [ControlPlane Prometheus Uniform]
Appears in: - VolumeAutoPlacementSettings
| Field | Description |
|---|---|
ControlPlane |
|
Prometheus |
|
Uniform |
MetricsBackendUniform reports IOPS=1 for every node, which disables IOPS-based scoring while leaving capacity and volume-count balancing active. |
MigrateSpec
MigrateSpec parameterizes the Migrate action and is ignored by the others.
A migration is not a removal followed by an add: the node keeps its backend UUID, its partitions, and its logical-volume assignments, and what changes is the machine the SPDK process runs on. No PersistentVolumeOps is created.
Appears in: - StorageNodeOpsSpec
Example:
targetWorkerNode: string
newSsdPcie:
- string
| Field | Description | Default | Validation |
|---|---|---|---|
targetWorkerNode string |
TargetWorkerNode is the Kubernetes worker the node is relocated onto. | Required: {} |
|
newSsdPcie string array |
NewSsdPcie lists additional NVMe PCI addresses to bind on the target host, passed through to the control-plane restart as new_ssd_pcie and merged into the node's effective allow list so they survive a later rebuild. |
Optional: {} |
MigrateVolumeSpec
MigrateVolumeSpec parameterizes the Migrate action.
Appears in: - PersistentVolumeOpsSpec
Example:
targetNodeRef:
namespace: string
name: string
| Field | Description | Default | Validation |
|---|---|---|---|
targetNodeRef StorageNodeReference |
TargetNodeRef locates the StorageNode to move the volume's backing logical volume to. It names the Kubernetes object rather than the backend UUID, so that a migration can be written by hand without looking one up; the controller resolves the UUID from the node's status. The node's cluster must be the volume's, which the webhook checks rather than the type, because that is a fact about two other objects. The marker sits on the reference rather than on its fields, so the pair is immutable together and a target cannot be half-changed into a name in one cluster and a namespace in another. |
Required: {} |
MigrationConnection
MigrationConnection is one NVMe-oF path the migration published on the target.
The connect parameters travel with the address because the path is connected on a consuming host rather than here, and what is recorded has to be the connect that will actually be made: the host attaches every path with the same controller-loss timeout the CSI driver uses, which is not the hour the control plane answers with, and a record of the control plane's answer would describe a connect nobody performs.
Appears in: - MigrationStatus
Example:
nqn: string
address: string
port: integer
transport: string
nrIOQueues: integer
reconnectDelaySeconds: integer
ctrlLossTimeoutSeconds: integer
fastIOFailTimeoutSeconds: integer
keepAliveTimeoutSeconds: integer
| Field | Description | Default | Validation |
|---|---|---|---|
nqn string |
NQN is the subsystem the target answers this path for. | Optional: {} |
|
address string |
Address and Port are where it answers. | Optional: {} |
|
port integer |
Optional: {} |
||
transport string |
Transport is the fabric, which is TCP. | Optional: {} |
|
nrIOQueues integer |
Optional: {} |
||
reconnectDelaySeconds integer |
Optional: {} |
||
ctrlLossTimeoutSeconds integer |
CtrlLossTimeoutSeconds and FastIOFailTimeoutSeconds are pointers because zero is a choice ("fail I/O immediately") rather than a missing value. |
Optional: {} |
|
fastIOFailTimeoutSeconds integer |
Optional: {} |
||
keepAliveTimeoutSeconds integer |
Optional: {} |
MigrationStatus
MigrationStatus is everything about the migration rather than about the operation. It is durable working state: a controller that restarts mid-copy reads it to find the backend migration it started and the Jobs it has to clean up.
Appears in: - PersistentVolumeOpsStatus
Example:
migrationUUID: string
clusterUUID: string
poolUUID: string
volumeUUID: string
subsystemNQN: string
sourceNodeUUID: string
targetNodeUUID: string
continuedAt: Time
memberCount: integer
connections:
- nqn: string
address: string
port: integer
transport: string
nrIOQueues: integer
reconnectDelaySeconds: integer
ctrlLossTimeoutSeconds: integer
fastIOFailTimeoutSeconds: integer
keepAliveTimeoutSeconds: integer
validationJobs:
- namespace: string
name: string
node: string
succeeded: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
migrationUUID string |
MigrationUUID is the control plane's identifier for the copy. | Optional: {} |
|
clusterUUID string |
ClusterUUID, PoolUUID, and VolumeUUID are the three parts of the volume's CSI volume handle, recorded so that later steps address the backend without re-reading the PersistentVolume, and so that a failed operation says which volume it was working on after the volume is gone. |
Optional: {} |
|
poolUUID string |
Optional: {} |
||
volumeUUID string |
Optional: {} |
||
subsystemNQN string |
SubsystemNQN is the volume's NVMe-oF subsystem, which is what the migration is addressed by: the control plane migrates a subsystem rather than one volume inside it. |
Optional: {} |
|
sourceNodeUUID string |
SourceNodeUUID is where the volume was before the move, recorded so that a failure says what it was and not only what it was going to be. |
Optional: {} |
|
targetNodeUUID string |
TargetNodeUUID is the backend identifier resolved from spec.migrate.targetNodeRef, recorded so the later steps address the target without resolving the node object again. |
Optional: {} |
|
continuedAt Time ⧉ |
ContinuedAt is when this operation asked the control plane to start the copy, written before the call rather than after it. The order is what makes the request at-most-once, and that is the point. A repeated continue is the shape that has lost writes here: a read timeout on a call that had in fact committed made the operator retry a transfer, and the retry copied nothing while the source was unfrozen. So a recorded continue is never issued again, and an operation that crashed between this write and the call waits out its step's deadline instead — a rare stall, against a silent data loss. |
Optional: {} |
|
memberCount integer |
MemberCount is how many volumes the migrated NVMe-oF subsystem holds, as the control plane reports it. More than one member means the sibling volumes move along with the named one, so the count is both the operation's blast radius and the term the copy's deadline scales by. |
Minimum: 0 Optional: {} |
|
connections MigrationConnection array |
Connections are the paths the migration published on the target. Verifying confirms none of them is left connected. |
Optional: {} |
|
validationJobs ValidationJob array |
ValidationJobs are the Jobs started to check those paths. | Optional: {} |
NFSExport
NFSExport is one pNFS export: a volume, the host serving it, and the address clients mount.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: NFSExport
metadata:
name: string
spec:
volumeRef: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:[^:]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
exportPath: '^/var/lib/simplyblock/exports/[A-Za-z0-9._-]+$'
encrypted: boolean
sizeBytes: integer
status:
phase: NFSExportPhase
phaseDeadline: Time
mdsNodeName: string
mdsNodeIP: string
serviceAddress: string
allowedClients:
- string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
NFSExport |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec NFSExportSpec |
|||
status NFSExportStatus |
NFSExportPhase
Underlying type: string
NFSExportPhase is the lifecycle position of an export.
Validation: - Enum: [Pending Assembling Ready Degraded]
Appears in: - NFSExportStatus
| Field | Description |
|---|---|
Pending |
Pending is an export with no host bound yet: a wait, not a failure. |
Assembling |
Assembling is the bound host attaching, formatting, mounting, publishing. |
Ready |
Ready is an export a client can mount. |
Degraded |
Degraded is an export the operator will not act further on. Reached by declining to act: a stalled export is recoverable, a double-mounted one is not. |
NFSExportSpec
NFSExportSpec is the desired state. The bound host is deliberately not here: it is an observed binding the operator owns, so it lives in status where a user edit cannot race it.
Appears in: - NFSExport
Example:
volumeRef: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:[^:]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
exportPath: '^/var/lib/simplyblock/exports/[A-Za-z0-9._-]+$'
encrypted: boolean
sizeBytes: integer
| Field | Description | Default | Validation |
|---|---|---|---|
volumeRef string |
VolumeRef is the CSI volume handle of the volume this export serves, in the ordinary {clusterID}:{poolID}:{lvolID} form. It names no Kubernetes object, so it is validated by pattern rather than resolved by a webhook. It is the whole identity of the export. The backing namespace UUID and the NFS fsid are both the volume's own id, so they are read from here rather than stored beside it, where they could disagree with it. Deliberately not a form of its own. A pNFS volume is an lvol with an export in front of it, so its claim keeps the lvol's handle and every other CSI call -- snapshot, clone, expand -- addresses it without having to know an export is there. |
Pattern: ^[0-9a-f]\{8\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{12\}:[^:]+:[0-9a-f]\{8\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{12\}$ Required: {} |
|
exportPath string |
ExportPath is the server-side mount point. It carries the namespace, because a PVC name is unique only within one. |
Pattern: ^/var/lib/simplyblock/exports/[A-Za-z0-9._-]+$ Required: {} |
|
encrypted boolean |
Encrypted says the control plane stacks a crypto bdev under this volume, which makes an unwritten block read as pseudo-random plaintext rather than as zeros. Assembly needs it: without it an empty encrypted volume is mistaken for an occupied one, never formatted, and never mounts. No omitempty: the field is immutable once set (below), and the CRD enforces that by requiring it stay present once it is. The CSI controller's unstructured Create always writes it explicitly, so an unencrypted export's record already carries encrypted: false inetcd; a typed client's own writes have to carry it too; omitempty drops a false value from the JSON entirely, which reads to the CRD as the field being removed and every subsequent write from this type -- starting with the reconciler's own finalizer add -- is then refused, permanently, before the export can even begin assembling. |
Optional: {} |
|
sizeBytes integer |
SizeBytes is the capacity the backing volume was last grown to. It is not a request: the control plane has already resized the volume by the time this is written. It is here so that growing one bumps the record's generation, which is what tells the operator to re-assemble and run xfs_growfs on the host -- the one machine that can, and the one no CSI call ever reaches. |
Optional: {} |
NFSExportStatus
NFSExportStatus is what the export currently is.
Appears in: - NFSExport
Example:
phase: NFSExportPhase
phaseDeadline: Time
mdsNodeName: string
mdsNodeIP: string
serviceAddress: string
allowedClients:
- string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
phase NFSExportPhase |
Phase is the export's lifecycle position. | Enum: [Pending Assembling Ready Degraded] Optional: {} |
|
phaseDeadline Time ⧉ |
PhaseDeadline is when the current phase must be given up on. Persisted because an operator restarted mid-assembly would otherwise grant a fresh deadline on every pass and never time out. |
Optional: {} |
|
mdsNodeName string |
MDSNodeName is the Kubernetes node serving this export, which is the one running the csi-node pod that assembled it. It is not a StorageNode: an MDS reaches the volume over NVMe-oF exactly as a client does, so it need not hold any storage itself. The serialization point for one-MDS-per-export: no second host is a candidate until this field is rewritten. |
Optional: {} |
|
mdsNodeIP string |
MDSNodeIP is the bound MDS host's own address. Not what a client mounts: it is what the export's Service's EndpointSlice points at, so a client's mount address (ServiceAddress) does not have to change when this does. |
Optional: {} |
|
serviceAddress string |
ServiceAddress is the ClusterIP of the Service fronting this export, which is the address a client mounts. It outlives any one MDSNodeIP: the operator repoints the Service's EndpointSlice at whichever host is bound rather than changing this value, so a client's mount survives the export moving to a different host (design-pnfs-rwx.md §13.3). |
Optional: {} |
|
allowedClients string array |
AllowedClients is what goes into the exports(5) entry: the internal address of every node that could run a pod using this volume. |
Optional: {} |
|
message string |
Message is a human-readable note on the current phase. | Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the spec generation this status was computed from. | Optional: {} |
NodeGroup
NodeGroup is a set of workers that share one configuration, which is what makes ten identical machines one entry rather than ten.
Appears in: - NodeSet
Example:
name: string
workers:
- string
mgmtInterface: string
dataInterfaces:
- string
devices:
nvme:
- '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
block:
- '^/dev/[a-zA-Z0-9._/-]+$'
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name identifies the group within its node set, for a reader and for the events a validation failure emits. |
MaxLength: 253 Required: {} |
|
workers string array |
Workers are the Kubernetes worker hostnames in this group. | MaxItems: 200 MinItems: 1 Required: {} items:MaxLength: 253 |
|
mgmtInterface string |
MgmtInterface is the management network interface the storage nodes bind. | MaxLength: 63 Optional: {} |
|
dataInterfaces string array |
DataInterfaces are the data-plane network interfaces. | MaxItems: 32 items:MaxLength: 63 Optional: {} |
|
devices DeviceSelection |
Devices selects the storage devices every worker in the group uses. | Optional: {} |
|
failureDomain string |
FailureDomain is the label of the fault group every worker in this group belongs to ("rack-b"), which is usually the name of the rack, zone, or power feed they share. Discovery seeds it from topology.kubernetes.io/zone and leaves it unset where the Kubernetes API carries no topology, which holds provisioning with a clear reason rather than guessing. It expands into StorageNode.spec.config.failureDomain, whose shape it shares. |
MaxLength: 63 Pattern: ^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$ Optional: {} |
|
spdkSystemMemory string |
SpdkSystemMemory is the memory the control plane starts SPDK with on these nodes. |
MaxLength: 32 Pattern: ^[0-9]+(G\|GI\|GB\|GiB\|M\|MI\|MB\|MiB\|g\|gi\|gb\|gib\|m\|mi\|mb\|mib)?$ Optional: {} |
|
reservedSystemCPU string |
ReservedSystemCPU is the CPU set held back from SPDK for the system on these nodes, as a core list such as 0,1 or 0-3. It is a group's rather than the cluster's because it names core ids, and a group is what a document calls the workers that share their hardware: 0,1 on a sixteen-core worker and 0,1 on a ninety-six-core worker are different fractions of the machine. It expands into StorageNode.spec.config.reservedSystemCPU, whose shape it shares, and a group that states none leaves the cluster's fleet-wide value to decide. On OpenShift it reaches the kubelet through a KubeletConfig for the machine config pool, which is the cluster's, so groups that disagree there are writing over one another's pool configuration. |
MaxLength: 63 Pattern: ^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$ Optional: {} |
|
journalManager JournalManagerSpec |
JournalManager tunes the journal managers on these nodes. | Optional: {} |
NodeLatencyMetrics
NodeLatencyMetrics is the fio-measured 4K NVMe-oF write latency of one backend storage node, which is the denominator of the rebalancer's deviation signal.
It is a node's reading and it lives on the node. The retired StorageNodeSet collected one entry per node in a fleet-wide list, which made every node's measurement a write to one object shared by all of them.
Appears in: - StorageNodeStatus
Example:
nodeUUID: string
baselineP50NS: integer
baselineP99NS: integer
baselineMeasuredAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
nodeUUID string |
NodeUUID is the backend storage node the reading was taken against. It is carried beside the reading rather than inferred from status.uuid, because a baseline measured against one backend node stops describing the slot once a replacement fills it. |
Required: {} |
|
baselineP50NS integer |
BaselineP50NS is the p50 write latency, in nanoseconds, of the initial empty-cluster benchmark. |
Minimum: 0 Optional: {} |
|
baselineP99NS integer |
BaselineP99NS is the p99 write latency, in nanoseconds, of the same benchmark. |
Minimum: 0 Optional: {} |
|
baselineMeasuredAt Time ⧉ |
BaselineMeasuredAt is when the baseline was established. | Optional: {} |
NodeLoadMetrics
NodeLoadMetrics is one storage node's latency deviation, as the last evaluation cycle measured it.
Appears in: - RebalancingMetrics
Example:
nodeUUID: string
latencyDeviationPct: float
volumeCount: integer
lastUpdated: Time
| Field | Description | Default | Validation |
|---|---|---|---|
nodeUUID string |
|||
latencyDeviationPct float |
|||
volumeCount integer |
|||
lastUpdated Time ⧉ |
NodeSet
NodeSet is the organizational grouping of a deployment, usually a rack: the workers a document adds or grows together. It carries no sizing, because sizing is uniform across a cluster and is stated once in ClusterTemplate.
Appears in: - ClusterDeploymentConfigSpec - StorageSiteDraft
Example:
name: string
groups:
- name: string
workers:
- string
mgmtInterface: string
dataInterfaces:
- string
devices:
nvme:
- '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
block:
- '^/dev/[a-zA-Z0-9._/-]+$'
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name is the node set's name. It is copied to StorageNode.spec.nodeSet, so that a node can be traced back to the part of the document that produced it. |
MaxLength: 253 Required: {} |
|
groups NodeGroup array |
Groups are the sets of workers sharing one configuration. | MaxItems: 64 MinItems: 1 Required: {} |
OpenShiftSpec
OpenShiftSpec is what a deployment onto OpenShift states beyond what every distribution states.
Its presence is the statement. A cluster carrying the block runs on OpenShift and one without it does not, which is why there is no boolean beside it: a block naming a machine-config pool on a cluster that also said it was not OpenShift was expressible before and meant nothing.
Appears in: - ClusterTemplate - StorageNodesSpec
Example:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
| Field | Description | Default | Validation |
|---|---|---|---|
machineConfigPool string |
MachineConfigPool names a machine-config role the storage nodes' own pool inherits from, beyond the worker role it always inherits. It is not the pool the nodes end up in, which the description it carried before said and which cost a reader the reboot they were trying to avoid. Adding a node creates a pool of its own, storage- node into it; a node belongs to exactly one custom pool, so whatever machine configuration its previous pool carried is lost unless that pool's role is named here for the new one to select as well. The default is the role every pool already selects, which is what makes it a no-op for a fleet whose workers are ordinary workers. |
worker | MaxLength: 253 Pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ Optional: {} |
OperatorOps
OperatorOps is a single operation performed against the operator itself, which today means a discovery run that writes a ClusterDeploymentConfig. It runs to a terminal phase and stays afterward as the audit record.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: OperatorOps
metadata:
name: string
spec:
action: OperatorOpsAction
abort: boolean
discover:
configName: string
workers:
- string
nodeSelector:
string: string
tolerations:
- Toleration
enableControlPlaneNodes: boolean
enableLogicalBlockDevices: boolean
enablePartitionedDevices: boolean
forceJournalDevice: boolean
deviceFilter:
pcieAllowList:
- string
pcieDenyList:
- string
pcieModel: string
blockAllowList:
- '^/dev/[a-zA-Z0-9._/-]+$'
blockDenyList:
- '^/dev/[a-zA-Z0-9._/-]+$'
driveSizeRange: string
clusterRef: string
status:
phase: OperatorOpsPhase
step: KubeSnapshot
configRef: string
workers:
- string
environment: KubernetesEnvironment
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
OperatorOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec OperatorOpsSpec |
|||
status OperatorOpsStatus |
OperatorOpsAction
Underlying type: string
OperatorOpsAction is the operation an OperatorOps performs.
The field stays with one value in it. Every Ops kind in this group is dispatched on spec.action, so a kind that left its single action implicit would be the one kind whose shape has to change the day it gains a second.
Validation: - Enum: [Discover]
Appears in: - OperatorOpsSpec
| Field | Description |
|---|---|
Discover |
OperatorOpsPhase
Underlying type: string
OperatorOpsPhase is the operation's own progress.
Validation: - Enum: [Pending Running Succeeded Failed Aborted]
Appears in: - OperatorOpsStatus
| Field | Description |
|---|---|
Pending |
|
Running |
|
Succeeded |
|
Failed |
|
Aborted |
OperatorOpsSpec
OperatorOpsSpec is one operation to perform against the operator itself.
It carries no target reference. Every other Ops kind in this group names the entity it acts on; this one acts on the operator process, which is not a resource in this API group, and the absent field is the signal.
Appears in: - OperatorOps
Example:
action: OperatorOpsAction
abort: boolean
discover:
configName: string
workers:
- string
nodeSelector:
string: string
tolerations:
- Toleration
enableControlPlaneNodes: boolean
enableLogicalBlockDevices: boolean
enablePartitionedDevices: boolean
forceJournalDevice: boolean
deviceFilter:
pcieAllowList:
- string
pcieDenyList:
- string
pcieModel: string
blockAllowList:
- '^/dev/[a-zA-Z0-9._/-]+$'
blockDenyList:
- '^/dev/[a-zA-Z0-9._/-]+$'
driveSizeRange: string
clusterRef: string
| Field | Description | Default | Validation |
|---|---|---|---|
action OperatorOpsAction |
Action is the operation to perform. | Enum: [Discover] Required: {} |
|
abort boolean |
Abort asks a running operation to stop at its next step and unwind. Whether an abort is expressible from the current step is declared by that action's graph rather than checked here. Discovery changes nothing, so an aborted run leaves behind at most the config it had already written. |
Optional: {} |
|
discover DiscoverSpec |
Discover parameterizes action Discover. | Optional: {} |
OperatorOpsStatus
OperatorOpsStatus is the observed state of one operator operation.
Appears in: - OperatorOps
Example:
phase: OperatorOpsPhase
step: KubeSnapshot
configRef: string
workers:
- string
environment: KubernetesEnvironment
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase OperatorOpsPhase |
Phase is the operation's own progress. | Enum: [Pending Running Succeeded Failed Aborted] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the running action's state machine. | Optional: {} |
|
configRef string |
ConfigRef names the ClusterDeploymentConfig a Discover run wrote. | Optional: {} |
|
workers string array |
Workers are the workers this run is inspecting, decided once in Inspecting so that a node joining the cluster mid-run does not change what the run is about. |
Optional: {} |
|
environment KubernetesEnvironment |
Environment is the Kubernetes distribution Inspecting concluded, which Writing copies into the draft. It is recorded here as well so that a run that failed later still says what it found. |
Enum: [Vanilla OpenShift Rancher K3s Talos] Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the operation moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation started. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when it reached a terminal phase. | Optional: {} |
PersistentVolumeOps
PersistentVolumeOps is a single operation performed against one PersistentVolume. It is the one Ops kind in this group whose target is a core Kubernetes type rather than a kind this group defines, so it locks its target with an annotation rather than a status field, is cluster-scoped because its target is, cannot be owned by the namespaced operation that created it, and derives its cluster, pool, and volume from the volume's CSI handle rather than being told.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: PersistentVolumeOps
metadata:
name: string
spec:
persistentVolumeName: string
action: PersistentVolumeOpsAction
abort: boolean
migrate:
targetNodeRef:
namespace: string
name: string
creatorRef:
kind: string
namespace: string
name: string
uid: UID
status:
phase: PersistentVolumeOpsPhase
step: KubeSnapshot
migration:
migrationUUID: string
clusterUUID: string
poolUUID: string
volumeUUID: string
subsystemNQN: string
sourceNodeUUID: string
targetNodeUUID: string
continuedAt: Time
memberCount: integer
connections:
- nqn: string
address: string
port: integer
transport: string
nrIOQueues: integer
reconnectDelaySeconds: integer
ctrlLossTimeoutSeconds: integer
fastIOFailTimeoutSeconds: integer
keepAliveTimeoutSeconds: integer
validationJobs:
- namespace: string
name: string
node: string
succeeded: boolean
deferredSince: Time
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
PersistentVolumeOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec PersistentVolumeOpsSpec |
|||
status PersistentVolumeOpsStatus |
PersistentVolumeOpsAction
Underlying type: string
PersistentVolumeOpsAction is the operation a PersistentVolumeOps performs. The kind is named for its target rather than for the action so that carrying a second one later would not rename it. It carries one, and no second one is planned.
Validation: - Enum: [Migrate]
Appears in: - PersistentVolumeOpsSpec
| Field | Description |
|---|---|
Migrate |
PersistentVolumeOpsActionMigrate moves the volume's backing logical volume to a different storage node. |
PersistentVolumeOpsPhase
Underlying type: string
PersistentVolumeOpsPhase is the operation's own progress. Succeeded rather than the registered kind's Completed, so that every Ops kind in this group reports the same five phases and an alert on completion matches one value.
Validation: - Enum: [Pending Running Succeeded Failed Aborted]
Appears in: - PersistentVolumeOpsStatus
| Field | Description |
|---|---|
Pending |
PersistentVolumeOpsPhasePending is an operation waiting for the volume's lock, or holding nothing yet. |
Running |
PersistentVolumeOpsPhaseRunning is an operation holding the lock and working. |
Succeeded |
PersistentVolumeOpsPhaseSucceeded is a finished operation that did what it said. |
Failed |
PersistentVolumeOpsPhaseFailed is a finished operation that did not. |
Aborted |
PersistentVolumeOpsPhaseAborted is an operation stopped on request, or stopped because its volume went away, whose unwind has finished. |
PersistentVolumeOpsSpec
PersistentVolumeOpsSpec is one operation to perform against one PersistentVolume.
The action and its parameter block have to agree: Migrate requires spec.migrate, and no other action accepts it.
Appears in: - PersistentVolumeOps
Example:
persistentVolumeName: string
action: PersistentVolumeOpsAction
abort: boolean
migrate:
targetNodeRef:
namespace: string
name: string
creatorRef:
kind: string
namespace: string
name: string
uid: UID
| Field | Description | Default | Validation |
|---|---|---|---|
persistentVolumeName string |
PersistentVolumeName names the PersistentVolume this operation acts on. It is a name rather than a reference because a PersistentVolume is cluster-scoped, and it names the volume rather than the claim because a claim can be deleted while its volume is retained — under a Retain reclaim policy the logical volume still occupies capacity on a node that may be draining, and is still worth moving. |
Required: {} |
|
action PersistentVolumeOpsAction |
Action is the operation to perform. Immutable: an operation that changed what it was doing halfway through would have a status describing neither. |
Enum: [Migrate] Required: {} |
|
abort boolean |
Abort asks a running operation to stop at its next step and unwind. It is expressible from Validating and Migrating and not from Verifying, which the action's graph declares rather than this field: once the copy has finished, the volume has moved and there is nothing to undo. |
Optional: {} |
|
migrate MigrateVolumeSpec |
Migrate parameterizes action Migrate. | Optional: {} |
|
creatorRef CreatorReference |
CreatorRef names the object that created this one, and that object's finalizer is what aborts and deletes this one when it goes. Absent on an operation written by hand, which has no creator to cascade from. Immutable, because an operation changing whose fan-out it belongs to would change who cascades over it. |
Optional: {} |
PersistentVolumeOpsStatus
PersistentVolumeOpsStatus is the observed state of one volume operation.
Appears in: - PersistentVolumeOps
Example:
phase: PersistentVolumeOpsPhase
step: KubeSnapshot
migration:
migrationUUID: string
clusterUUID: string
poolUUID: string
volumeUUID: string
subsystemNQN: string
sourceNodeUUID: string
targetNodeUUID: string
continuedAt: Time
memberCount: integer
connections:
- nqn: string
address: string
port: integer
transport: string
nrIOQueues: integer
reconnectDelaySeconds: integer
ctrlLossTimeoutSeconds: integer
fastIOFailTimeoutSeconds: integer
keepAliveTimeoutSeconds: integer
validationJobs:
- namespace: string
name: string
node: string
succeeded: boolean
deferredSince: Time
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase PersistentVolumeOpsPhase |
Phase is the operation's own progress. | Enum: [Pending Running Succeeded Failed Aborted] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the running action's state machine. It is persisted before the side effect that step performs, so a step reported here is a step that started. |
Optional: {} |
|
migration MigrationStatus |
Migration is everything about the migration rather than about the operation. |
Optional: {} |
|
deferredSince Time ⧉ |
DeferredSince is when the operation was first held — behind another operation's lock, or behind a control plane that is not accepting migrations yet. It is what the auto-rebalancer reads to decide whether a migration has waited long enough to give up on, and it is in status rather than in memory because the operator may restart and an observer needs to see that the operation is waiting and since when. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the operation moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation began. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when it reached a terminal phase. | Optional: {} |
PoolLimits
PoolLimits are the ceilings the pool as a whole is held to. They are the pool's budget rather than a volume's: a volume's own defaults are in StoragePoolSpec.VolumeDefaults, and the two use the same units so that a reader can compare them.
Appears in: - StoragePoolSpec
Example:
capacity: string
maxVolumeSize: string
iops: integer
throughput:
read: integer
write: integer
readWrite: integer
| Field | Description | Default | Validation |
|---|---|---|---|
capacity string |
Capacity is the total capacity the pool may allocate, written the way an administrator writes one: 10T, 500G. Empty is unlimited. |
Optional: {} |
|
maxVolumeSize string |
MaxVolumeSize is the largest single logical volume the pool will create. Empty is unlimited. |
Optional: {} |
|
iops integer |
IOPS is the pool-wide ceiling on operations per second, both directions together. Zero is unlimited, which is the control plane's own convention for these values. |
Minimum: 0 Optional: {} |
|
throughput ThroughputLimits |
Throughput is the pool-wide throughput ceiling. | Optional: {} |
PoolLimitsStatus
PoolLimitsStatus is what the control plane reports the pool's ceilings actually are, which is not necessarily what Limits asked for.
Appears in: - StoragePoolStatus
Example:
host: string
iops: integer
throughput:
read: integer
write: integer
readWrite: integer
| Field | Description | Default | Validation |
|---|---|---|---|
host string |
Host is the backend host enforcing the pool's QoS. | Optional: {} |
|
iops integer |
IOPS is the combined ceiling the control plane is enforcing. | Optional: {} |
|
throughput ThroughputLimits |
Throughput is the throughput ceiling the control plane is enforcing. | Optional: {} |
ProvisioningSlot
StorageClusterStatus is the observed state of one backend cluster. ProvisioningSlot is one worker's hold on the cluster's node-add concurrency, held from the moment its add is posted until the node has a backend UUID or has given up.
Appears in: - StorageClusterStatus
Example:
worker: string
node: string
takenAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
worker string |
Worker is the Kubernetes node the add was posted for, and it is what the cap counts. |
||
node string |
Node is the StorageNode object that took the slot. A release removes only the entry naming its own object, which is what keeps a node from freeing somebody else's slot, and a slot whose object is gone is reaped. |
||
takenAt Time ⧉ |
TakenAt is when the slot was taken, so a hold that outlives its node's deadlines is visible in the object rather than only in the events. |
Optional: {} |
RebalancingMetrics
RebalancingMetrics is written by the auto-rebalancer each evaluation cycle.
Appears in: - StorageClusterStatus
Example:
avgDeviationPct: float
maxDeviationPct: float
hottestNodeUUID: string
coolestNodeUUID: string
imbalancePercent: float
lastEvaluatedAt: Time
lastMigrationAt: Time
nodeMetrics:
- nodeUUID: string
latencyDeviationPct: float
volumeCount: integer
lastUpdated: Time
| Field | Description | Default | Validation |
|---|---|---|---|
avgDeviationPct float |
AvgDeviationPct is the mean latency deviation across the cluster's nodes. | ||
maxDeviationPct float |
MaxDeviationPct is the highest per-node latency deviation, which is what ImbalancePercent reports. |
||
hottestNodeUUID string |
|||
coolestNodeUUID string |
|||
imbalancePercent float |
|||
lastEvaluatedAt Time ⧉ |
|||
lastMigrationAt Time ⧉ |
|||
nodeMetrics NodeLoadMetrics array |
RemovalStatus
RemovalStatus is the control plane's progress through a node removal it has accepted, as the operation last read it while waiting in AwaitingRemoval.
Any change in it counts as progress. Each change moves the step's deadline out by the full budget again, so a removal that keeps moving is waited on for as long as it moves, and one that stops is failed once a full budget passes with nothing changing.
Appears in: - StorageNodeOpsStatus
Example:
nodeStatus: string
devices:
string: string
lastProgressTime: Time
prepareAttempts: integer
lastPrepareTime: Time
| Field | Description | Default | Validation |
|---|---|---|---|
nodeStatus string |
NodeStatus is the node's status as the control plane last reported it, in the control plane's own spelling: pending_removal, migrating_devices, migrating_lvols, in_removal, removed, or removed_failed. |
Optional: {} |
|
devices object (keys:string, values:string) |
Devices is the control-plane status each of the node's StorageDevices last reported, keyed by the StorageDevice's name. A device moves from online through failed to failed_and_migrated, or to removed, as the removal rebuilds its data onto the peers. |
Optional: {} |
|
lastProgressTime Time ⧉ |
LastProgressTime is when NodeStatus or any of Devices last changed. | Optional: {} |
|
prepareAttempts integer |
PrepareAttempts is how many times the removal's first step was sent again for a node that stayed pending_removal, because the control plane reported the step failed or nothing moved for longer than a shutdown takes. The operation fails once the attempts run out. |
Minimum: 0 Optional: {} |
|
lastPrepareTime Time ⧉ |
LastPrepareTime is when the removal's first step was last sent again. | Optional: {} |
RemoveSpec
RemoveSpec parameterizes the Remove action and is ignored by the others.
Appears in: - StorageNodeOpsSpec
Example:
systemVolumeFilterRegex: string
| Field | Description | Default | Validation |
|---|---|---|---|
systemVolumeFilterRegex string |
SystemVolumeFilterRegex matches backend volume names that are system volumes: excluded from the drain's migration and deleted during verification rather than blocking it. |
^sb-fio-baseline-.* | Optional: {} |
RestartSpec
RestartSpec parameterizes the Restart action and is ignored by the others.
Appears in: - ControlPlaneOpsSpec
Example:
components:
- string
| Field | Description | Default | Validation |
|---|---|---|---|
components string array |
Components names the workloads to recycle. Empty recycles the whole control plane. Naming only components that status.components reports as non-essential skips the drain, because recycling them interrupts nothing. |
Optional: {} |
RestoreOpsSpec
RestoreOpsSpec carries the parameters of the Restore action.
Appears in: - VolumeGroupSnapshotOpsSpec
Example:
namePrefix: string
storageClassName: string
consistencyGroup: string
enablePartialRestore: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
namePrefix string |
NamePrefix prefixes every restored claim's name: snapshot's spec.source.persistentVolumeClaimName. Defaults to the operation's own name. |
Optional: {} |
|
storageClassName string |
StorageClassName is the class every restored claim requests. When empty, each claim inherits the class of its member snapshot's source claim, and the restore fails for a member whose source claim no longer exists. |
Optional: {} |
|
consistencyGroup string |
ConsistencyGroup labels every restored claim with storage.simplyblock.io/consistency-group: new group at provisioning under the mandatory placement rule. Empty leaves the clones as independent, mutually consistent volumes. |
Optional: {} |
|
enablePartialRestore boolean |
EnablePartialRestore restores the members an incomplete generation still has instead of failing the operation. Off by default: an incomplete generation fails, naming the missing members. |
Optional: {} |
RestoreSpec
RestoreSpec parameterizes the Restore action.
It carries no size, no access mode, and no volume mode, which the registered BackupRestore took as a whole PersistentVolumeClaim template. All three are facts about the backup rather than choices: a restored volume is the size of the copy, and asking for a different one is either a truncation or a lie. The controller reads the size off status.backup.size and mounts the filesystem status.source.fsType records.
Appears in: - StorageBackupOpsSpec
Example:
claimName: string
targetPool: string
claimLabels:
string: string
claimAnnotations:
string: string
| Field | Description | Default | Validation |
|---|---|---|---|
claimName string |
ClaimName is the PersistentVolumeClaim to create. It must not already exist: a restore that adopted an existing claim would replace a running workload's data with the backup's. |
Required: {} |
|
targetPool string |
TargetPool is the StoragePool to restore into. It is required rather than defaulted: a backup found in the store may have been written by another cluster, so status.source.poolName names a pool this cluster need not have, and which pool a volume lands in is a tenancy and QoS decision nobody should make by omission. |
Required: {} |
|
claimLabels object (keys:string, values:string) |
ClaimLabels and ClaimAnnotations are applied to the created claim, so that a restored volume can be selected by a policy or an application the same way its original was. Immutable with the rest of the block. The claim is written at the last step, so a value edited while the operation waited for its volume would produce a claim built from inputs the admitted and audited operation never carried, which is the audit record disagreeing with what happened. |
Optional: {} |
|
claimAnnotations object (keys:string, values:string) |
Optional: {} |
RestoredMemberStatus
RestoredMemberStatus records one member snapshot and the claim restored from it.
Appears in: - VolumeGroupSnapshotOpsStatus
Example:
volumeSnapshotName: string
persistentVolumeClaimName: string
bound: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
volumeSnapshotName string |
VolumeSnapshotName is the member VolumeSnapshot the claim restores from. | ||
persistentVolumeClaimName string |
PersistentVolumeClaimName is the restored claim. | ||
bound boolean |
Bound reports whether the restored claim has bound. | Optional: {} |
RollingRestartSpec
RollingRestartSpec parameterizes the RollingRestart action.
Appears in: - StorageClusterOpsSpec
Example:
refreshSNodeAPI: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
refreshSNodeAPI boolean |
RefreshSNodeAPI restarts each node's storage-node DaemonSet pod between its shutdown and its restart, so the latest image is running when the node returns. |
Optional: {} |
RollingRestartStatus
RollingRestartStatus is the walk's position over the cluster's nodes. Where the machine has got to within the node currently being restarted is status.step, and neither field is complete without the other.
Appears in: - StorageClusterOpsStatus
Example:
nodes:
- string
nodeIndex: integer
skipped:
- string
| Field | Description | Default | Validation |
|---|---|---|---|
nodes string array |
Nodes is the ordered list of storage node UUIDs this action covers, written once when the walk starts and not modified afterward, so a node added mid-walk is not restarted and one removed mid-walk is skipped when the walk reaches it. |
Optional: {} |
|
nodeIndex integer |
NodeIndex is the position in Nodes of the node being restarted. Advancing the walk increments it, and the walk is complete when it reaches len(Nodes). No omitempty: zero is a valid index, and a field that disappears at zero makes "the first node" and "unset" the same wire value. |
Minimum: 0 |
|
skipped string array |
Skipped are the nodes of Nodes the walk passed over without restarting them, in walk order: a node the control plane stopped listing, or one whose removal started after the walk was planned. A node leaving the cluster belongs to its removal, and the walk sends it nothing. |
Optional: {} |
SidecarImages
SidecarImages overrides the CSI sidecar images this deployment runs. An unset field takes the version this operator release ships, which is the combination it was tested against, and the fields exist so that a pin a Helm release made survives the adoption of that release's deployment.
Every field carries the registry pattern Image carries. The node plugin is privileged and mounts /dev, /sys, and the kubelet's plugin directory from the host, so a sidecar beside it runs with the same access.
Appears in: - SimplyblockDriverSpec
Example:
provisioner: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
attacher: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
resizer: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
snapshotter: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
healthMonitor: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
nodeDriverRegistrar: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
csiAddons: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block|quay\.io/csiaddons)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
| Field | Description | Default | Validation |
|---|---|---|---|
provisioner string |
Provisioner is csi-provisioner, on the controller plugin. | Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
|
attacher string |
Attacher is csi-attacher, on the controller plugin. | Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
|
resizer string |
Resizer is csi-resizer, on the controller plugin. | Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
|
snapshotter string |
Snapshotter is csi-snapshotter, on the controller plugin. It is this driver's sidecar and not the cluster's snapshot-controller, whose image is not overridable here because that component belongs to the cluster. |
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
|
healthMonitor string |
HealthMonitor is csi-external-health-monitor-controller, on the controller plugin. |
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
|
nodeDriverRegistrar string |
NodeDriverRegistrar is node-driver-registrar, on the node plugin. | Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
|
csiAddons string |
CSIAddons is the kubernetes-csi-addons sidecar, on the controller plugin. It connects to the plugin's socket, probes the csi-addons Identity service for capabilities, and publishes a CSIAddonsNode so the kubernetes-csi-addons controller-manager (design design-csi-addons-replication.md §4.1) can reach the Replication service this driver serves. Unlike the other sidecars above, this one's upstream home is the csi-addons project's own registry, not simplyblock's: the allowlist carries quay.io/csiaddons alongside the simplyblock registries so a deployment can run the stock kubernetes-csi-addons sidecar image directly, ahead of (or instead of) a quay.io/simplyblock-io mirror. |
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block\|quay\.io/csiaddons)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
SimplyblockDriver
SimplyblockDriver is the deployment of simplyblock's CSI driver: the node plugin, the controller plugin, their RBAC, and the core CSIDriver registration they produce. It is named for the brand rather than the interface because CSIDriver is already a kind in core storage.k8s.io/v1, and the two are not the same object: the core kind is the cluster's registration record, and this one is the deployment that produces it.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: SimplyblockDriver
metadata:
name: string
spec:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
driverName: string
controllerReplicas: integer
nodeSelector:
string: string
tolerations:
- Toleration
controllerNodeSelector:
string: string
controllerTolerations:
- Toleration
controllerResources: ResourceRequirements
nodeResources: ResourceRequirements
sidecarImages:
provisioner: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
attacher: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
resizer: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
snapshotter: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
healthMonitor: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
nodeDriverRegistrar: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
csiAddons: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block|quay\.io/csiaddons)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
enableServiceAccountAuth: boolean
enableVolumeSnapshots: boolean
tls:
enableTLS: boolean
enableMutualTLS: boolean
provider: DriverTLSProvider
pnfs:
enablePNFS: boolean
status:
phase: SimplyblockDriverPhase
snapshotSupport: SnapshotSupportOrigin
origin: SimplyblockDriverOrigin
version: string
nodesReady: integer
nodesTotal: integer
controllerReady: boolean
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
SimplyblockDriver |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec SimplyblockDriverSpec |
|||
status SimplyblockDriverStatus |
SimplyblockDriverOrigin
Underlying type: string
SimplyblockDriverOrigin is where the running deployment came from. Every cluster upgraded from a chart install reads Adopted, because the chart had applied the objects before this kind existed.
Validation: - Enum: [Created Adopted]
Appears in: - SimplyblockDriverStatus
| Field | Description |
|---|---|
Created |
SimplyblockDriverOriginCreated is a deployment whose objects the operator applied from nothing. |
Adopted |
SimplyblockDriverOriginAdopted is a deployment the operator took over in place, taking field ownership from Helm and removing the release's metadata once the handover was verified. |
SimplyblockDriverPhase
Underlying type: string
SimplyblockDriverPhase is where the operator has got to with the CSI driver. Installing covers the applies, and the three values after it are decided by what the node plugins and the controller plugin report.
Validation: - Enum: [Installing Ready Degraded Unavailable]
Appears in: - SimplyblockDriverStatus
| Field | Description |
|---|---|
Installing |
SimplyblockDriverPhaseInstalling is a deployment whose objects are not all applied yet. |
Ready |
SimplyblockDriverPhaseReady is every plugin pod serving. |
Degraded |
SimplyblockDriverPhaseDegraded is a node plugin restarting while the controller plugin still provisions, which strands one worker's volumes rather than the namespace's. |
Unavailable |
SimplyblockDriverPhaseUnavailable is a controller plugin that is not running, which is when provisioning stops. |
SimplyblockDriverSpec
SimplyblockDriverSpec is the CSI driver deployment: the node plugin, the controller plugin, their RBAC, and the CSIDriver registration they produce.
Appears in: - SimplyblockDriver
Example:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
driverName: string
controllerReplicas: integer
nodeSelector:
string: string
tolerations:
- Toleration
controllerNodeSelector:
string: string
controllerTolerations:
- Toleration
controllerResources: ResourceRequirements
nodeResources: ResourceRequirements
sidecarImages:
provisioner: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
attacher: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
resizer: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
snapshotter: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
healthMonitor: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
nodeDriverRegistrar: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
csiAddons: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block|quay\.io/csiaddons)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
enableServiceAccountAuth: boolean
enableVolumeSnapshots: boolean
tls:
enableTLS: boolean
enableMutualTLS: boolean
provider: DriverTLSProvider
pnfs:
enablePNFS: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
image string |
Image is the CSI driver image, used by both plugins. Unset takes the operator's own registry and tag with the CSI driver's repository, so a deployment that states nothing runs the driver belonging to the operator reconciling it, which is the pairing the release was tested as. |
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
|
imagePullPolicy PullPolicy ⧉ |
ImagePullPolicy controls when that image is pulled. It defaults to Always because the default Image is a moving tag: it follows the operator's own, and a development build's tag is rebuilt in place. IfNotPresent against a tag that moved leaves the workers that already pulled it running the old plugin and the workers that had not running the new one, which is version skew inside one deployment and invisible from the object. |
Always | Enum: [Always Never IfNotPresent] Optional: {} |
driverName string |
DriverName is the CSI driver name a StorageClass provisions with. Every PersistentVolume the driver created records it in spec.csi.driver and every VolumeAttachment records it too, so changing it orphans every volume in the namespace rather than renaming anything. |
csi.simplyblock.io | Optional: {} |
controllerReplicas integer |
ControllerReplicas is the number of controller-plugin instances. | 1 | Minimum: 1 Optional: {} |
nodeSelector object (keys:string, values:string) |
NodeSelector restricts which workers run the node plugin. Empty means every schedulable worker, which is the usual case: a node that cannot attach a volume cannot run a workload that needs one. |
Optional: {} |
|
tolerations Toleration ⧉ array |
Tolerations are applied to the node plugin, which usually needs to run where workloads run rather than where the operator does. |
Optional: {} |
|
controllerNodeSelector object (keys:string, values:string) |
ControllerNodeSelector and ControllerTolerations place the controller plugin. The unprefixed pair above is the node plugin's, because that placement decides which workers can attach a volume, and this pair is ordinary pod placement for the one workload that provisions them. |
Optional: {} |
|
controllerTolerations Toleration ⧉ array |
Optional: {} |
||
controllerResources ResourceRequirements ⧉ |
ControllerResources and NodeResources set requests and limits for the two plugins. Unset enforces no limits. |
Optional: {} |
|
nodeResources ResourceRequirements ⧉ |
Optional: {} |
||
sidecarImages SidecarImages |
SidecarImages overrides the seven CSI sidecars, one field each. Unset takes the version this operator release ships. |
Optional: {} |
|
enableServiceAccountAuth boolean |
EnableServiceAccountAuth makes both plugins authenticate to the management API with their pod's Kubernetes service-account token instead of the static cluster secret. The control plane has to list those accounts in SB_K8S_ADMIN_SERVICE_ACCOUNTS for it to work, which is why this is a deployment-wide switch rather than a per-plugin one. |
false | Optional: {} |
enableVolumeSnapshots boolean |
EnableVolumeSnapshots decides whether snapshot support is part of this deployment: the VolumeSnapshotClass for DriverName, and the CRDs and a controller where the cluster serves neither. False applies none of them. |
true | Optional: {} |
tls DriverTLS |
TLS configures whether both plugins reach the control plane over TLS. Unset is plaintext, the shape every deployment ran before this field existed. Adopting a deployment that already runs TLS requires this to agree with what its plugins are configured for, because it is not read off the running objects. |
Optional: {} |
|
pnfs DriverPNFS |
PNFS configures pNFS support on the node plugin. | Optional: {} |
SimplyblockDriverStatus
SimplyblockDriverStatus is the observed state of the CSI driver deployment.
Appears in: - SimplyblockDriver
Example:
phase: SimplyblockDriverPhase
snapshotSupport: SnapshotSupportOrigin
origin: SimplyblockDriverOrigin
version: string
nodesReady: integer
nodesTotal: integer
controllerReady: boolean
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
phase SimplyblockDriverPhase |
Phase is the operator's own view of the deployment. | Enum: [Installing Ready Degraded Unavailable] Optional: {} |
|
snapshotSupport SnapshotSupportOrigin |
SnapshotSupport is whether the cluster already had snapshot support or the operator installed it, which is what says whether other drivers depend on what this one applied. |
Enum: [Detected Installed] Optional: {} |
|
origin SimplyblockDriverOrigin |
Origin is whether the first reconcile created this deployment's objects or met ones it did not create. It is decided once and never revised, because what it records is where the running deployment came from rather than what the controller did most recently. |
Enum: [Created Adopted] Optional: {} |
|
version string |
Version is the version the deployed driver reports, published so that a skew against ControlPlane.status.version is visible on one screen. |
Optional: {} |
|
nodesReady integer |
NodesReady is how many workers run a ready node plugin, and NodesTotal how many are expected to. Neither takes omitempty: zero ready plugins is the condition worth seeing. |
Minimum: 0 |
|
nodesTotal integer |
Minimum: 0 |
||
controllerReady boolean |
ControllerReady is whether the controller plugin is serving. | Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the deployment moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
SnapshotSupportOrigin
Underlying type: string
SnapshotSupportOrigin is where the cluster's snapshot support came from.
Validation: - Enum: [Detected Installed]
Appears in: - SimplyblockDriverStatus
| Field | Description |
|---|---|
Detected |
SnapshotSupportOriginDetected is a cluster that already served snapshot.storage.k8s.io/v1, so the operator applied no CRDs and no controller. |
Installed |
SnapshotSupportOriginInstalled is a cluster where the operator applied them. They are cluster-scoped and shared, so they carry no controller reference and outlive this object. |
StorageBackup
StorageBackup is one point-in-time copy of one volume that exists in a cluster's store.
Objects are created by the operator from what the store holds and are never written by a user: creating one by hand would claim a backup exists that the store does not hold, and deleting one would hide a backup that is still there and still restorable. Deleting the object never deletes the copy.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackup
metadata:
name: string
spec:
clusterRef: string
backupID: string
status:
phase: StorageBackupPhase
apiStatus: string
clusterID: string
backup:
backupID: string
s3ID: integer
size: integer
previousBackupID: string
startedAt: Time
completedAt: Time
source:
claimName: string
claimNamespace: string
persistentVolumeName: string
poolName: string
poolUUID: string
lvolID: string
lvolName: string
fsType: string
snapshotID: string
snapshotName: string
nodeID: string
clusterUUID: string
activeOpsRef: string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageBackup |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
Optional: {} |
|
spec StorageBackupSpec |
spec identifies the backup this object reports on | Required: {} |
|
status StorageBackupStatus |
status defines the observed state of StorageBackup | Optional: {} |
StorageBackupOps
StorageBackupOps is a single operation performed against one StorageBackup. It runs to a terminal phase and stays afterward as the audit record of what was restored, into which pool, and how it ended.
The claim a restore produces is not owned by the operation and outlives it: nobody restores a backup in order to keep a StorageBackupOps, so deleting the audit record must not delete the recovered volume.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackupOps
metadata:
name: string
spec:
clusterRef: string
backupRef: string
action: StorageBackupOpsAction
abort: boolean
restore:
claimName: string
targetPool: string
claimLabels:
string: string
claimAnnotations:
string: string
status:
phase: StorageBackupOpsPhase
step: KubeSnapshot
clusterID: string
backupID: string
restoredLvolID: string
poolUUID: string
persistentVolumeName: string
claimName: string
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageBackupOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
Optional: {} |
|
spec StorageBackupOpsSpec |
spec defines the operation to perform | Required: {} |
|
status StorageBackupOpsStatus |
status defines the observed state of StorageBackupOps | Optional: {} |
StorageBackupOpsAction
Underlying type: string
StorageBackupOpsAction is the operation a StorageBackupOps performs. Restore acts on a StorageBackup, which is every backup in the cluster's store.
Validation: - Enum: [Restore]
Appears in: - StorageBackupOpsSpec
| Field | Description |
|---|---|
Restore |
StorageBackupOpsPhase
Underlying type: string
StorageBackupOpsPhase is the operation's own progress. It is the same small set every Ops kind in the group carries: Pending before the operation holds its target's lock, Running while it works, and three terminal values.
Validation: - Enum: [Pending Running Succeeded Failed Aborted]
Appears in: - StorageBackupOpsStatus
| Field | Description |
|---|---|
Pending |
|
Running |
|
Succeeded |
|
Failed |
|
Aborted |
StorageBackupOpsSpec
StorageBackupOpsSpec is one operation to perform against a backup.
Appears in: - StorageBackupOps
Example:
clusterRef: string
backupRef: string
action: StorageBackupOpsAction
abort: boolean
restore:
claimName: string
targetPool: string
claimLabels:
string: string
claimAnnotations:
string: string
| Field | Description | Default | Validation |
|---|---|---|---|
clusterRef string |
ClusterRef names the StorageCluster the operation runs against. Bounded at what a StorageCluster name may be, since a longer value names nothing that can exist. |
MaxLength: 63 Required: {} |
|
backupRef string |
BackupRef names the StorageBackup this operation acts on, in this namespace. Required, since Restore is the only action and every backup in the store has an object. |
Required: {} |
|
action StorageBackupOpsAction |
Action is the operation to perform. | Enum: [Restore] Required: {} |
|
abort boolean |
Abort asks a running operation to stop at its next step and unwind. It is the one field of this spec that may be edited after the object is created, because it is the one thing about an operation that can legitimately be decided after it started. A restore cannot be aborted once it has created a logical volume, and the action's graph declares that rather than this field: an abort arriving later is reported as an illegal transition while the operation runs on, rather than leaving the work half undone. |
Optional: {} |
|
restore RestoreSpec |
Restore parameterizes action Restore. | Optional: {} |
StorageBackupOpsStatus
StorageBackupOpsStatus is the observed state of one backup operation.
Appears in: - StorageBackupOps
Example:
phase: StorageBackupOpsPhase
step: KubeSnapshot
clusterID: string
backupID: string
restoredLvolID: string
poolUUID: string
persistentVolumeName: string
claimName: string
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageBackupOpsPhase |
Phase is the operation's own progress. | Enum: [Pending Running Succeeded Failed Aborted] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the running action's state machine. It is persisted before the side effect that step performs, so a step reported here is a step that started. |
Optional: {} |
|
clusterID string |
ClusterID is the backend cluster the operation ran against. | Optional: {} |
|
backupID string |
BackupID is the copy the restore read from, recorded so the operation says what it restored after the object list has moved on. |
Optional: {} |
|
restoredLvolID string |
RestoredLvolID is the logical volume the control plane created. It is written before the claim, so a restarted Binding step knows what it is binding. |
Optional: {} |
|
poolUUID string |
PoolUUID is the backend identifier of spec.restore.targetPool, resolved once at Validating. |
Optional: {} |
|
persistentVolumeName string |
PersistentVolumeName is the PV the operation wrote for the restored volume. |
Optional: {} |
|
claimName string |
ClaimName is the PersistentVolumeClaim a Restore produced. It is written before the claim is created rather than after, because the claim carries no owner reference back: a restarted Binding step recognizes its own work by this name together with the storage.simplyblock.io/restored-by label on the claim, and refuses a claim of the right name that carries neither. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the operation moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from. On this kind it advances at most twice, and the second advance is precisely the signal that spec.abort has been observed. |
Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation acquired its target's lock. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when it reached a terminal phase. | Optional: {} |
StorageBackupPhase
Underlying type: string
StorageBackupPhase is where the operator has got to with this backup. Available is the terminal success rather than Succeeded, because a backup is not an operation: what matters afterward is that the copy can be restored, not that the copying finished.
Validation: - Enum: [Pending Creating Available Failed]
Appears in: - StorageBackupStatus
| Field | Description |
|---|---|
Pending |
|
Creating |
|
Available |
|
Failed |
StorageBackupPolicy
StorageBackupPolicy schedules and retains the backups of the claims it selects. It is the only thing that decides a backup is taken; the copies themselves are the control plane's, are pruned by its retention, and reach Kubernetes as StorageBackup objects the operator discovers.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackupPolicy
metadata:
name: string
spec:
clusterRef: string
claimSelector: LabelSelector
schedule: '^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$'
maxVersions: integer
maxAge: '^[1-9]\d*[mhdw]$'
status:
phase: StorageBackupPolicyPhase
clusterID: string
policyID: string
attachedClaims:
- name: string
persistentVolumeName: string
lvolID: string
attachedAt: Time
lastBackupAt: Time
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageBackupPolicy |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
Optional: {} |
|
spec StorageBackupPolicySpec |
spec defines the desired state of StorageBackupPolicy | Required: {} |
|
status StorageBackupPolicyStatus |
status defines the observed state of StorageBackupPolicy | Optional: {} |
StorageBackupPolicyPhase
Underlying type: string
StorageBackupPolicyPhase is where the operator has got to with this policy.
Validation: - Enum: [Pending Active Failed]
Appears in: - StorageBackupPolicyStatus
| Field | Description |
|---|---|
Pending |
|
Active |
|
Failed |
StorageBackupPolicySpec
StorageBackupPolicySpec schedules and retains the backups of the claims it selects. The operator reconciles it into the control plane and reports what the control plane did; it does not run the schedule itself, because a backup schedule that stopped when the operator was down would be one nobody could rely on.
Appears in: - StorageBackupPolicy
Example:
clusterRef: string
claimSelector: LabelSelector
schedule: '^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$'
maxVersions: integer
maxAge: '^[1-9]\d*[mhdw]$'
| Field | Description | Default | Validation |
|---|---|---|---|
clusterRef string |
ClusterRef names the StorageCluster whose backup target this policy writes to. Bounded at what a StorageCluster name may be, since a longer value names nothing that can exist. |
MaxLength: 63 Required: {} |
|
claimSelector LabelSelector ⧉ |
ClaimSelector selects the PersistentVolumeClaims this policy backs up. An absent selector selects nothing, which is deliberate: the cost of backing up too much is silent and recurring, and the cost of backing up too little is an error somebody sees. The controller reports an inert policy with a SelectorEmpty event rather than refusing it. |
Optional: {} |
|
schedule string |
Schedule is the tiered schedule the control plane runs the policy on, as a space-separated list of interval,keep_count pairs ("15m,4 60m,11 24h,7"). Intervals must be strictly increasing, and the supported units are m, h, d, and w. Immutable, and that is a property of the control plane rather than a choice. The control plane offers no call that applies a changed schedule: it creates, deletes, attaches, and detaches a policy and nothing else. A mutable field the operator cannot reconcile would leave the declaration and the backups actually being taken permanently disagreeing, with the object still reporting Active, so the schedule is fixed at creation until that call exists. Changing one means replacing the policy. |
Pattern: ^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$ Optional: {} |
|
maxVersions integer |
MaxVersions is how many backups of one claim to keep. Zero means no limit by count. Immutable, for the reason Schedule is. |
Minimum: 0 Optional: {} |
|
maxAge string |
MaxAge is how long to keep a backup ("30d," "720h"). Empty means no limit by age. Retention is enforced by the control plane, not here. Immutable, for the reason Schedule is. |
Pattern: ^[1-9]\d*[mhdw]$ Optional: {} |
StorageBackupPolicyStatus
StorageBackupPolicyStatus is the observed state of the policy.
Appears in: - StorageBackupPolicy
Example:
phase: StorageBackupPolicyPhase
clusterID: string
policyID: string
attachedClaims:
- name: string
persistentVolumeName: string
lvolID: string
attachedAt: Time
lastBackupAt: Time
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageBackupPolicyPhase |
Phase is the operator's own view of this policy. | Enum: [Pending Active Failed] Optional: {} |
|
clusterID string |
ClusterID is the backend cluster the policy was created in. | Optional: {} |
|
policyID string |
PolicyID is the control plane's identifier for the policy. | Optional: {} |
|
attachedClaims AttachedClaim array |
AttachedClaims are the claims the policy currently covers. Detaching one stops new backups being taken and deletes none of the existing ones. |
Optional: {} |
|
lastBackupAt Time ⧉ |
LastBackupAt is when the control plane last completed a backup under this policy, and it is what an age alert is computed from. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the policy moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
StorageBackupSpec
StorageBackupSpec is the identity of one backup the operator found in a cluster's store, and nothing else. The object is created by the operator and by nobody else, so there is no request here to carry.
Appears in: - StorageBackup
Example:
clusterRef: string
backupID: string
| Field | Description | Default | Validation |
|---|---|---|---|
clusterRef string |
ClusterRef names the StorageCluster whose store this backup was found in. With BackupID it is the whole of this object's identity. Bounded at what a StorageCluster name may be, since a longer value names nothing that can exist. |
MaxLength: 63 Required: {} |
|
backupID string |
BackupID is the identifier the store holds the backup under, and what a restore addresses. It is the store's identifier rather than a name this operator assigns, so the same backup is the same object however many clusters have the location configured. |
Required: {} |
StorageBackupStatus
StorageBackupStatus is the observed state of one backup, in three groups: the copy, what the volume was, and the operator's own view.
Appears in: - StorageBackup
Example:
phase: StorageBackupPhase
apiStatus: string
clusterID: string
backup:
backupID: string
s3ID: integer
size: integer
previousBackupID: string
startedAt: Time
completedAt: Time
source:
claimName: string
claimNamespace: string
persistentVolumeName: string
poolName: string
poolUUID: string
lvolID: string
lvolName: string
fsType: string
snapshotID: string
snapshotName: string
nodeID: string
clusterUUID: string
activeOpsRef: string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageBackupPhase |
Phase is the operator's own view of this backup. | Enum: [Pending Creating Available Failed] Optional: {} |
|
apiStatus string |
APIStatus is the control plane's own lifecycle string, in the control plane's spelling, which is why it carries no Enum here. It is what keeps the states the four-value phase folds together — merging, deleting — legible. |
Optional: {} |
|
clusterID string |
ClusterID is the backend cluster whose stream this backup was last observed on. An object outliving its backup has to say which scope's silence is authoritative before it may be deleted, and spec.clusterRef names a Kubernetes object rather than a backend one. StorageDevice carries the same field for the same reason. |
Optional: {} |
|
backup BackupCopy |
Backup is the copy itself. | Optional: {} |
|
source BackupSource |
Source is what the volume was when the copy was taken. | Optional: {} |
|
activeOpsRef string |
ActiveOpsRef names the StorageBackupOps currently allowed to act on this backup. Empty when none is running. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the backup moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. On this kind it moves at most once, since every spec field is fixed when the object is created. |
Optional: {} |
StorageCluster
StorageCluster is one simplyblock backend cluster. It owns the storage nodes beneath it, the pools carved out of it, and the Kubernetes workload its nodes run as.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageCluster
metadata:
name: string
spec:
maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
stripe:
dataChunks: integer
parityChunks: integer
fabricType: string
clientDataIfname: string
nvmfBasePort: integer
rpcBasePort: integer
snodeApiPort: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
enableChecksumValidation: boolean
enableAtomicity4K: boolean
deviceClass: StorageClusterDeviceClass
kms:
vault:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
warningThreshold:
capacity: integer
provisionedCapacity: integer
criticalThreshold:
capacity: integer
provisionedCapacity: integer
maxConcurrentWorkerRestarts: integer
storageNodes:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
mgmtInterface: string
dataInterfaces:
- string
socketsToUse:
- string
nodesPerSocket: integer
nodeProvisioningBudget: integer
enableJournalDevice: boolean
enableFormat4K: boolean
enableBlockFormat: boolean
enableCpuTopology: boolean
reservedSystemCPU: string
enableKubeletConfiguration: boolean
ubuntuHost: boolean
openshift:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
tolerations:
- Toleration
containerResources: ResourceRequirements
initContainerResources: ResourceRequirements
backup:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
disableDataRealignment: boolean
enableVolumeAutoPlacement: boolean
volumeMigrationSettings:
rebalancerImage: string
dataRealignment:
interval: Duration
minMoves: integer
volumeAutoPlacement:
disableMigration: boolean
evaluationInterval: Duration
imbalanceThreshold: integer
minHotColdDifferencePct: integer
defaultCoolDownSeconds: integer
maxVolumeMigrationsPerCycle: integer
storageNodeCandidateCount: integer
metricsBackend: MetricsBackend
prometheusURL: string
enableLatencyBenchmark: boolean
latencyBenchmarkInterval: Duration
baselineStrategy: BaselineStrategy
baselineWindow: Duration
baselineColdStart: BaselineColdStartPolicy
baselineMinSamples: integer
baselineOutlierK: float
iopsWeight: float
throughputWeight: float
status:
phase: StorageClusterPhase
step: KubeSnapshot
uuid: string
clusterName: string
nqn: string
erasureCodingScheme: string
status: string
configured: boolean
rebalancing: boolean
maxFaultTolerance: integer
maxConcurrentWorkerRestarts: integer
volumeMoveGeneration: integer
realignedGeneration: integer
lastDataRealignmentAt: Time
tasks:
- id: string
type: string
status: string
retry: integer
provisioningSlots:
- worker: string
node: string
takenAt: Time
failureDomains:
- name: string
index: integer
activeOpsRef: string
rebalancingMetrics:
avgDeviationPct: float
maxDeviationPct: float
hottestNodeUUID: string
coolestNodeUUID: string
imbalancePercent: float
lastEvaluatedAt: Time
lastMigrationAt: Time
nodeMetrics:
- nodeUUID: string
latencyDeviationPct: float
volumeCount: integer
lastUpdated: Time
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageCluster |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec StorageClusterSpec |
|||
status StorageClusterStatus |
StorageClusterDeviceClass
Underlying type: string
StorageClusterDeviceClass is the class of backend storage a cluster is built out of. The values are the two classes simplyblock accepts, spelled as the standards that name them are.
Validation: - Enum: [NVMe LogicalBlock]
Appears in: - StorageClusterSpec
| Field | Description |
|---|---|
NVMe |
StorageClusterDeviceClassNVMe is a cluster whose nodes hand over NVMe devices, named by PCI address. |
LogicalBlock |
StorageClusterDeviceClassLogicalBlock is a cluster whose nodes hand over logical block devices, named by path. The backend accepts them from 26.4. |
StorageClusterOps
StorageClusterOps is a single operation performed against one StorageCluster. It runs to a terminal phase and stays afterward as the audit record of what was done, to which cluster, with which parameters, and how it ended. Only one operation acts on a cluster at a time; a second is admitted, waits at Pending, and runs when the lock frees.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageClusterOps
metadata:
name: string
spec:
clusterRef: string
action: StorageClusterOpsAction
abort: boolean
cancelTask:
taskID: string
rollingRestart:
refreshSNodeAPI: boolean
status:
phase: StorageClusterOpsPhase
step: KubeSnapshot
message: string
rollingRestart:
nodes:
- string
nodeIndex: integer
skipped:
- string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageClusterOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec StorageClusterOpsSpec |
|||
status StorageClusterOpsStatus |
StorageClusterOpsAction
Underlying type: string
StorageClusterOpsAction is the operation a StorageClusterOps performs. The values are PascalCase, as every enum in the group is; v1alpha1 spelled them lowercase and hyphenated, and the conversion maps between the two.
Validation: - Enum: [Activate Expand Shutdown Start Restart RollingRestart CancelTask]
Appears in: - StorageClusterOpsSpec
| Field | Description |
|---|---|
Activate |
|
Expand |
|
Shutdown |
|
Start |
|
Restart |
|
RollingRestart |
|
CancelTask |
StorageClusterOpsActionCancelTask is the one action whose target is inside the cluster: it names a task of the cluster's status.tasks by its control-plane ID. |
StorageClusterOpsPhase
Underlying type: string
StorageClusterOpsPhase is the operation's own progress. Aborted is terminal and distinct from Failed, because a canceled operation did not go wrong.
Validation: - Enum: [Pending Running Succeeded Failed Aborted]
Appears in: - StorageClusterOpsStatus
| Field | Description |
|---|---|
Pending |
StorageClusterOpsPhasePending: the operation holds no lock and has issued nothing. It is both where an operation starts and where it waits behind another one's lock. |
Running |
StorageClusterOpsPhaseRunning: the operation holds the lock and its first side effect may have been issued. |
Succeeded |
|
Failed |
|
Aborted |
StorageClusterOpsPhaseAborted: the operation was called off. A rolling restart holding on a degraded cluster is the one operation an administrator has a reason to stop, and stopping it is not a failure. |
StorageClusterOpsSpec
StorageClusterOpsSpec is one operation to perform against one StorageCluster.
Appears in: - StorageClusterOps
Example:
clusterRef: string
action: StorageClusterOpsAction
abort: boolean
cancelTask:
taskID: string
rollingRestart:
refreshSNodeAPI: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
clusterRef string |
ClusterRef names the StorageCluster this operation acts on. The operation never owns its target, because deleting the record of an operation must not delete the cluster it operated on. Bounded at what a StorageCluster name may be, since a longer value names nothing that can exist. |
MaxLength: 63 Required: {} |
|
action StorageClusterOpsAction |
Action is the operation to perform. | Enum: [Activate Expand Shutdown Start Restart RollingRestart CancelTask] Required: {} |
|
abort boolean |
Abort asks a running operation to stop at its next step and unwind. Whether an abort is expressible from the current step is declared by that action's graph rather than checked here. |
Optional: {} |
|
cancelTask CancelTaskSpec |
CancelTask parameterizes action CancelTask and is ignored by the others. | Optional: {} |
|
rollingRestart RollingRestartSpec |
RollingRestart parameterizes action RollingRestart and is ignored by the others. |
Optional: {} |
StorageClusterOpsStatus
StorageClusterOpsStatus is the observed state of one cluster operation.
Appears in: - StorageClusterOps
Example:
phase: StorageClusterOpsPhase
step: KubeSnapshot
message: string
rollingRestart:
nodes:
- string
nodeIndex: integer
skipped:
- string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageClusterOpsPhase |
Phase is the operation's own progress. | Enum: [Pending Running Succeeded Failed Aborted] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the running action's state machine. The value is one of the steps the running action declares. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the operation moves, and never a log. |
Optional: {} |
|
rollingRestart RollingRestartStatus |
RollingRestart is the walk's position, set only for action RollingRestart. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation acquired its target's lock. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when it reached a terminal phase. | Optional: {} |
StorageClusterPhase
Underlying type: string
StorageClusterPhase is where the operator has got to with this cluster. The first two values are the operator's own creation path; the rest are its reading of the lifecycle status.status carries in the control plane's own spelling.
Validation: - Enum: [Pending Creating Provisioning Activating Online Shrinking Rebalancing Degraded Unavailable Suspended]
Appears in: - StorageClusterStatus
| Field | Description |
|---|---|
Pending |
StorageClusterPhasePending: the object exists and nothing has been claimed for it yet. |
Creating |
StorageClusterPhaseCreating: the creation machine is running. |
Provisioning |
StorageClusterPhaseProvisioning: the cluster exists in the control plane and is being built up — its first nodes are joining, or an expansion is adding more. It is not serving and there is nothing wrong with it, which is the distinction Unavailable cannot carry. |
Activating |
StorageClusterPhaseActivating: the control plane is activating the cluster. It is a phase of its own rather than part of Provisioning because it is not only the last step of a deployment: an expansion ends in one, and so does recovering from a suspension, long after anything was being built. |
Online |
StorageClusterPhaseOnline: the control plane reports the cluster active and serving. |
Shrinking |
StorageClusterPhaseShrinking: serving, and removing at least one of its nodes. It replaces Rebalancing, Online, and Degraded for as long as the control plane reports a removal in progress, because a removal moves the departing node's data onto its peers and degrades the cluster while it does, and the removal is the cause of both. It never replaces a phase that is not serving. |
Rebalancing |
StorageClusterPhaseRebalancing: serving, and moving data between its nodes or devices. It replaces Online and Degraded for as long as the control plane reports a rebalance running, because a rebalance is what makes most operations on the cluster unavailable and the phase is the one column a watch shows. It never replaces a phase that is not serving. status.status keeps the control plane's own word beside it, which is how a rebalance on a degraded cluster is told from one on an active cluster. |
Degraded |
StorageClusterPhaseDegraded: serving, with less than the redundancy it was built for. |
Unavailable |
StorageClusterPhaseUnavailable: not serving, and not because anybody asked. It is what is left once the statuses that mean something has been asked for are read as themselves, which is what keeps it worth reporting: a cluster in this phase is one whose status this operator has no reading for, rather than every cluster that is not currently serving. |
Suspended |
StorageClusterPhaseSuspended: shut down deliberately, which is where a Shutdown operation leaves it. |
StorageClusterSpec
StorageClusterSpec is the desired state of one simplyblock backend cluster.
Appears in: - StorageCluster
Example:
maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
stripe:
dataChunks: integer
parityChunks: integer
fabricType: string
clientDataIfname: string
nvmfBasePort: integer
rpcBasePort: integer
snodeApiPort: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
enableChecksumValidation: boolean
enableAtomicity4K: boolean
deviceClass: StorageClusterDeviceClass
kms:
vault:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
warningThreshold:
capacity: integer
provisionedCapacity: integer
criticalThreshold:
capacity: integer
provisionedCapacity: integer
maxConcurrentWorkerRestarts: integer
storageNodes:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
mgmtInterface: string
dataInterfaces:
- string
socketsToUse:
- string
nodesPerSocket: integer
nodeProvisioningBudget: integer
enableJournalDevice: boolean
enableFormat4K: boolean
enableBlockFormat: boolean
enableCpuTopology: boolean
reservedSystemCPU: string
enableKubeletConfiguration: boolean
ubuntuHost: boolean
openshift:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
tolerations:
- Toleration
containerResources: ResourceRequirements
initContainerResources: ResourceRequirements
backup:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
disableDataRealignment: boolean
enableVolumeAutoPlacement: boolean
volumeMigrationSettings:
rebalancerImage: string
dataRealignment:
interval: Duration
minMoves: integer
volumeAutoPlacement:
disableMigration: boolean
evaluationInterval: Duration
imbalanceThreshold: integer
minHotColdDifferencePct: integer
defaultCoolDownSeconds: integer
maxVolumeMigrationsPerCycle: integer
storageNodeCandidateCount: integer
metricsBackend: MetricsBackend
prometheusURL: string
enableLatencyBenchmark: boolean
latencyBenchmarkInterval: Duration
baselineStrategy: BaselineStrategy
baselineWindow: Duration
baselineColdStart: BaselineColdStartPolicy
baselineMinSamples: integer
baselineOutlierK: float
iopsWeight: float
throughputWeight: float
| Field | Description | Default | Validation |
|---|---|---|---|
maxSubsystemCount integer |
MaxSubsystemCount is the maximum number of NVMe-oF subsystems per storage node. It is the cluster's and no node carries a copy: every node's configuration is generated from this field, so changing it reaches the nodes that already exist as each of them next restarts. Required: it sizes huge pages, and a node that receives no value fails config generation outright rather than falling back to a default. |
Maximum: 75 Minimum: 10 Required: {} |
|
vcpuCount integer |
VCPUCount is the number of vCPUs allocated to SPDK on each storage node, as an explicit core count rather than a percentage. Unlike MaxSubsystemCount it is stamped onto a node when the node is created, because it describes the host that node runs on. Required: the core layout it produces must match across the cluster in steady state, so it is stated rather than left to a per-node heuristic. The floor is 4 rather than a hardware limit: a node must carry one core beyond this budget for the system, and the control plane's core layout assigns no NVMe-oF poller core at all for a 2-vCPU budget. |
Minimum: 4 Required: {} |
|
minHugePagesSize string |
MinHugePagesSize is the smallest huge-page allocation each storage node makes, as a size string such as 100G or 1T, where a bare number is gigabytes. It is a floor and not a limit: the effective allocation is the larger of this value and the minimum the node's device and subsystem count requires, so simplyblock takes more when it needs more. Omitted, the computed minimum is used. |
Optional: {} |
|
stripe StripeSpec |
Stripe is the erasure-coding layout every volume in the cluster is written with. It describes on-disk layout, so it cannot change under a live cluster. |
Optional: {} |
|
fabricType string |
FabricType is the storage fabric the cluster serves volumes over. It describes on-wire layout, so it cannot change under a live cluster. |
Optional: {} |
|
clientDataIfname string |
ClientDataIfname is the network interface clients reach the data plane on. It is mutable: the control plane reads it on every connect rather than once at cluster-add, so an edit moves the next attach onto the named interface. |
Optional: {} |
|
nvmfBasePort integer |
NvmfBasePort is the base of the NVMe-oF port range every node binds. The default is the control plane's own, which is what it applies to a cluster that sends none, so the field states the number the cluster actually runs with rather than leaving a reader to know the backend. |
4420 | Maximum: 65535 Minimum: 1024 Optional: {} |
rpcBasePort integer |
RpcBasePort is the base of the RPC port range every node binds. Its default is the control plane's, as NvmfBasePort's is. |
8080 | Maximum: 65535 Minimum: 1024 Optional: {} |
snodeApiPort integer |
SnodeApiPort is the port each node's storage-node API listens on. Its default is the control plane's, as NvmfBasePort's is. |
50001 | Maximum: 65535 Minimum: 1024 Optional: {} |
enableFailureDomains boolean |
EnableFailureDomains opts the cluster into failure-domain mode, where every node must label the fault group it belongs to so the control plane can spread erasure-coding chunks across independent ones. |
Optional: {} |
|
enableNodeAffinity boolean |
EnableNodeAffinity has the data plane serve an erasure-coded volume's I/O from the local node's own devices where it can, before crossing the network. It is not Kubernetes affinity, and the name is the one place this API invites that reading: nothing about it schedules a pod, labels a worker, or places a volume's primary node. The control plane carries it into the cluster map it pushes to each node, where it sets the local node's index, and what changes is which copy of a chunk is read. Co-locating a workload with the primary node of its volume is a separate mechanism and is not configured here. |
Optional: {} |
|
enableChecksumValidation boolean |
EnableChecksumValidation turns on inline CRC validation of every I/O, for silent-data-error protection. The backend bakes the checksum method into each device at cluster-create time and never re-applies it, so it cannot change under a live cluster. |
false | Optional: {} |
enableAtomicity4K boolean |
EnableAtomicity4K enforces 4K write atomicity on devices that report a smaller logical block size, which lets checksum fallback mode run on them despite the data plane's usual 4K minimum. It is an enforcement rather than a reading, and that is what it is for. A device may complete a 4K write whole across a power failure and have no way to say so: a SATA drive presenting 512-byte logical blocks over a 4K physical sector reports 512 and nothing else, and a kernel older than 6.11 publishes no atomic write attributes at all, so the fleet it runs on cannot be asked. Where the hardware can answer, the storage node's report carries what it said; where it cannot, this is how an administrator states what they know and the cluster proceeds on it. Which is why it is the setting that loses data when it is wrong. An enforced guarantee the hardware does not keep is a torn write under a checksum that disagrees with it, so it is approved against the devices' own report where one exists. It means nothing unless EnableChecksumValidation is set, and it cannot change under a live cluster. |
false | Optional: {} |
deviceClass StorageClusterDeviceClass |
DeviceClass is the class of backend storage every node in this cluster hands over: NVMe devices named by PCI address, or logical block devices named by path. A cluster is built out of one of them, because an erasure-coding stripe placed across both is written and rebuilt at the slower class's rate. It describes on-disk layout, so it cannot change under a live cluster, and it defaults to NVMe because that is the only class the backend accepted before 26.4. |
NVMe | Enum: [NVMe LogicalBlock] |
kms KMSSpec |
KMS selects where the cluster stores volume encryption keys. Switching providers on a live cluster is at least as unsupportable as changing one provider's endpoint, which is why the whole block is immutable rather than its members. |
Optional: {} |
|
warningThreshold CapacityThresholdSpec |
WarningThreshold is the capacity level at which the cluster warns. | Optional: {} |
|
criticalThreshold CapacityThresholdSpec |
CriticalThreshold is the capacity level at which the cluster alarms. | Optional: {} |
|
maxConcurrentWorkerRestarts integer |
MaxConcurrentWorkerRestarts caps how many Kubernetes workers the operator may drain and restart at once. The effective value is the smaller of this and status.maxFaultTolerance, published as status.maxConcurrentWorkerRestarts so tooling reads one authoritative number rather than recomputing it. |
1 | Minimum: 1 Optional: {} |
storageNodes StorageNodesSpec |
StorageNodes is the Kubernetes workload the cluster's storage nodes run as, and the cluster owns every object in it by controller reference: a cluster deleted takes its DaemonSet, Services, certificate, and per-node ConfigMap with it. One workload serves the whole cluster, because growth is nodes rather than sets and what differs between hardware generations is per node already. |
Optional: {} |
|
backup BackupStoreSpec |
Backup is the S3 location this cluster's backups live in, and it is both the target copies are written to and the inventory the operator walks to produce StorageBackup objects. Mutable: a cluster that has never had a store gains one by acquiring the field, and every backup already in that bucket becomes visible. |
Optional: {} |
|
disableDataRealignment boolean |
DisableDataRealignment turns off the post-migration data realignment, which runs by default. It is spelled as a disable because the behavior it governs is on: realignment restores the fault-tolerance and node-affinity guarantees every volume move invalidates, so a cluster that says nothing gets them back rather than silently accumulating unaligned structures. Turning it off is for a cluster migrating continuously, where a run that blocks migrations for tens of minutes costs more than the delay in realigning; volumeMigrationSettings.dataRealignment.minMoves is the gentler answer to the same problem. It is a field of the spec rather than of the block it governs, because volumeMigrationSettings.dataRealignment.disableDataRealignment says the same word twice. There is no DisableVolumeMigration beside it: migration cannot be turned off, since a drain, a rebalance, and a device replacement are all performed by moving volumes. |
Optional: {} |
|
enableVolumeAutoPlacement boolean |
EnableVolumeAutoPlacement turns on automatic, latency-driven rebalancing. | Optional: {} |
|
volumeMigrationSettings VolumeMigrationSettings |
VolumeMigrationSettings controls how volume migration and the post-migration realignment behave, not whether they happen. It is separate from volumeAutoPlacement because realignment applies to every volume move, whatever asked for it. |
Optional: {} |
|
volumeAutoPlacement VolumeAutoPlacementSettings |
VolumeAutoPlacement configures automatic, latency-driven rebalancing. | Optional: {} |
StorageClusterStatus
Appears in: - StorageCluster
Example:
phase: StorageClusterPhase
step: KubeSnapshot
uuid: string
clusterName: string
nqn: string
erasureCodingScheme: string
status: string
configured: boolean
rebalancing: boolean
maxFaultTolerance: integer
maxConcurrentWorkerRestarts: integer
volumeMoveGeneration: integer
realignedGeneration: integer
lastDataRealignmentAt: Time
tasks:
- id: string
type: string
status: string
retry: integer
provisioningSlots:
- worker: string
node: string
takenAt: Time
failureDomains:
- name: string
index: integer
activeOpsRef: string
rebalancingMetrics:
avgDeviationPct: float
maxDeviationPct: float
hottestNodeUUID: string
coolestNodeUUID: string
imbalancePercent: float
lastEvaluatedAt: Time
lastMigrationAt: Time
nodeMetrics:
- nodeUUID: string
latencyDeviationPct: float
volumeCount: integer
lastUpdated: Time
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageClusterPhase |
Phase is the operator's own view of this cluster. | Enum: [Pending Creating Provisioning Activating Online Shrinking Rebalancing Degraded Unavailable Suspended] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the creation machine. The value is one of the steps that machine declares. |
Optional: {} |
|
uuid string |
UUID is the backend cluster UUID. Empty means the cluster has not been created or adopted, and non-empty means steady state. |
Optional: {} |
|
clusterName string |
ClusterName is the resolved backend name. | Optional: {} |
|
nqn string |
NQN is the cluster subsystem qualified name. | Optional: {} |
|
erasureCodingScheme string |
ErasureCodingScheme is the active layout, rendered as ndcs, an x, and npcs: a two-plus-one cluster reads 2x1. |
Optional: {} |
|
status string |
Status is the lifecycle the control plane reports, and its values are the control plane's, which is why they are neither PascalCase nor constrained by an Enum here. |
Optional: {} |
|
configured boolean |
Configured records that initial setup completed. | Optional: {} |
|
rebalancing boolean |
Rebalancing is the control plane's report that a rebalance is in progress, which is one of the two conditions that hold a node operation. |
Optional: {} |
|
maxFaultTolerance integer |
MaxFaultTolerance is how many nodes may be simultaneously offline without violating redundancy, as the control plane reports it. |
Optional: {} |
|
maxConcurrentWorkerRestarts integer |
MaxConcurrentWorkerRestarts is the effective limit, the smaller of spec.maxConcurrentWorkerRestarts and maxFaultTolerance. |
Optional: {} |
|
volumeMoveGeneration integer |
VolumeMoveGeneration is incremented by every migration reaching its successful terminal phase and by nothing else, so it only grows. |
Optional: {} |
|
realignedGeneration integer |
RealignedGeneration is the generation the last successfully requested realignment covers. A realignment is outstanding while volumeMoveGeneration exceeds it. The value recorded is the one read before the request was sent, which is what that realignment can actually account for: a migration completing while the request is in flight raises volumeMoveGeneration past it and correctly leaves another realignment outstanding. |
Optional: {} |
|
lastDataRealignmentAt Time ⧉ |
LastDataRealignmentAt is when a realignment was last requested, and it is what the configured interval spaces requests against. |
Optional: {} |
|
tasks ClusterTask array |
Tasks are the control plane's running and pending jobs, capped at twenty and in the order the control plane reports them, which is not newest first. Completed and canceled tasks are not here: they leave the list and become events, so the length tracks concurrency rather than history. |
MaxItems: 20 Optional: {} |
|
provisioningSlots ProvisioningSlot array |
ProvisioningSlots are the workers whose node add is outstanding. The list is the metadata of the Provisioning phase, and it is also the mutex that caps concurrent adds at spec.storageNodes.nodeProvisioningBudget. It is one list on one object because that is what makes taking a slot atomic. A node takes one with an optimistic-locked patch of this field, so exactly one node wins a given resourceVersion and every other is told to count again. A slot recorded per node could not do that: six objects carry six resourceVersions, and two nodes reading a cold cache would both see the same one free and both take it. A worker rather than an object is what holds a slot, because one POST adds every socket of a worker and a two-socket host must consume one slot. |
MaxItems: 64 Optional: {} |
|
failureDomains FailureDomainIndex array |
FailureDomains maps each failure-domain label the cluster's nodes declare (StorageNode.spec.config.failureDomain) to the integer the control plane identifies that domain by. A deployment adds an entry for every label it introduces, both when it creates the cluster and when it grows one, and never changes or removes an entry: the control plane has already placed data by that index. A label that is a number keeps that number as its index when the number is free. A cluster holds at most 256 failure domains, and a deployment that would exceed that is refused before it is approved. |
MaxItems: 256 Optional: {} |
|
activeOpsRef string |
ActiveOpsRef names the StorageClusterOps currently allowed to operate on this cluster. Empty when none is running. |
Optional: {} |
|
rebalancingMetrics RebalancingMetrics |
RebalancingMetrics is written by the auto-rebalancer each evaluation cycle. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the cluster moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
StorageDevice
StorageDevice is one storage backend device belonging to one storage node. It is the bottom of the ownership spine and the narrowest thing an operation can target.
Objects are created by the operator from what the control plane reports and are never written by a user: the device's existence follows from the node having it, and which devices a node uses is decided in the node's own spec.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageDevice
metadata:
name: string
spec:
nodeRef: string
deviceID: string
status:
phase: StorageDevicePhase
deviceStatus: string
role: StorageDeviceRole
capacity:
totalBytes: integer
hardware:
pciAddress: string
serialNumber: string
model: string
nvmeController: string
clusterID: string
nodeID: string
activeOpsRef: string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageDevice |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
Optional: {} |
|
spec StorageDeviceSpec |
spec identifies the mirrored control-plane device | Required: {} |
|
status StorageDeviceStatus |
status defines the observed state of the device | Optional: {} |
StorageDeviceOps
StorageDeviceOps is a single operation performed against one StorageDevice.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageDeviceOps
metadata:
name: string
spec:
deviceRef: string
action: StorageDeviceOpsAction
abort: boolean
status:
phase: StorageDeviceOpsPhase
step: KubeSnapshot
deviceStatusBefore: string
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageDeviceOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec StorageDeviceOpsSpec |
|||
status StorageDeviceOpsStatus |
StorageDeviceOpsAction
Underlying type: string
StorageDeviceOpsAction is the operation a StorageDeviceOps performs.
There is no Add: a device that appears is discovered. There is no bare Remove either: a removal is a step of Replace and of Migrate, and taking a device out of the data path without replacing it is Fail.
Validation: - Enum: [Restart Fail]
Appears in: - StorageDeviceOpsSpec
| Field | Description |
|---|---|
Restart |
StorageDeviceOpsActionRestart is the action the kind exists for: recycling one device rather than its node. |
Fail |
StorageDeviceOpsActionFail declares a device untrustworthy and takes it out of the data path for good, so the cluster rebuilds the redundancy it held elsewhere and stops reading from it. It is two calls rather than one: the control plane refuses to fail a device that is still serving, so the device is removed and then failed. The removal alone is reversible and the failure is not, which is why the graph splits them and declares the abort edge on the first. |
SelfTest |
These three actions are declared but not accepted: the control plane has no verb for them yet, so an object naming one is refused at admission rather than created and failed. |
Replace |
|
Migrate |
StorageDeviceOpsPhase
Underlying type: string
StorageDeviceOpsPhase is the operation's own progress.
Validation: - Enum: [Pending Running Succeeded Failed Aborted]
Appears in: - StorageDeviceOpsStatus
| Field | Description |
|---|---|
Pending |
|
Running |
|
Succeeded |
|
Failed |
|
Aborted |
StorageDeviceOpsSpec
StorageDeviceOpsSpec is one operation to perform against one StorageDevice.
Appears in: - StorageDeviceOps
Example:
deviceRef: string
action: StorageDeviceOpsAction
abort: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
deviceRef string |
DeviceRef names the StorageDevice this operation acts on, in this operation's own namespace. The operation never owns its target, because deleting the record of an operation must not delete the device record it operated on. |
MaxLength: 253 Required: {} |
|
action StorageDeviceOpsAction |
Action is the operation to perform. | Enum: [Restart Fail] Required: {} |
|
abort boolean |
Abort asks a running operation to stop at its next step and unwind. Each action can be aborted before it has issued anything and not after: a restart the control plane has accepted is one nothing can recall, and a device a failure has already removed is one this operator has no call to put back. The graph declares where the edge exists rather than this field promising one. |
Optional: {} |
StorageDeviceOpsStatus
StorageDeviceOpsStatus is the observed state of one device operation.
Appears in: - StorageDeviceOps
Example:
phase: StorageDeviceOpsPhase
step: KubeSnapshot
deviceStatusBefore: string
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageDeviceOpsPhase |
Phase is the operation's own progress. | Enum: [Pending Running Succeeded Failed Aborted] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the running action's state machine. It is persisted before the side effect that step performs. |
Optional: {} |
|
deviceStatusBefore string |
DeviceStatusBefore is what the control plane reported the device's status to be when the operation took its lock, so a wait can tell the device coming back from its never having gone. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the operation moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation acquired its target's lock. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when it reached a terminal phase. | Optional: {} |
StorageDevicePhase
Underlying type: string
StorageDevicePhase is the operator's own view of a device. Degraded and Failed are deliberately distinct: a degraded device is serving and should not be, while a failed one is not serving and the cluster is running with less redundancy than it thinks until it is replaced.
Validation: - Enum: [Online Degraded Unknown Removed Failed Migrated]
Appears in: - StorageDeviceStatus
| Field | Description |
|---|---|
Online |
|
Degraded |
|
Unknown |
StorageDevicePhaseUnknown is a device whose node cannot be reached, so its state is not observable rather than bad. A terminal phase is not overwritten by it. |
Removed |
|
Failed |
|
Migrated |
StorageDevicePhaseMigrated is a device taken out of service whose data the control plane has rebuilt onto the node's peers, so the cluster carries no less redundancy for it. It is where the devices of a removed node end up, and like Failed and Removed it is terminal. |
StorageDeviceRole
Underlying type: string
StorageDeviceRole is what the device carries. It is decided when the node is created, by spec.storageNodes.enableJournalDevice, and it is what makes one device's failure worse than another's.
Validation: - Enum: [Storage Journal]
Appears in: - StorageDeviceStatus
| Field | Description |
|---|---|
Storage |
|
Journal |
StorageDeviceSpec
StorageDeviceSpec identifies the device this object reports on, and carries nothing else. Everything a user could change about a device is on the node, so a spec field here would be a second place to set the same thing.
Appears in: - StorageDevice
Example:
nodeRef: string
deviceID: string
| Field | Description | Default | Validation |
|---|---|---|---|
nodeRef string |
NodeRef names the StorageNode this device belongs to. The node owns this object by controller reference, so deleting the node deletes its devices. |
Required: {} |
|
deviceID string |
DeviceID is the control plane's identifier for the device. With NodeRef it is the whole of this object's identity. |
Required: {} |
StorageDeviceStatus
StorageDeviceStatus is everything observed about the device.
Appears in: - StorageDevice
Example:
phase: StorageDevicePhase
deviceStatus: string
role: StorageDeviceRole
capacity:
totalBytes: integer
hardware:
pciAddress: string
serialNumber: string
model: string
nvmeController: string
clusterID: string
nodeID: string
activeOpsRef: string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageDevicePhase |
Phase is the operator's own view of the device. | Enum: [Online Degraded Unknown Removed Failed Migrated] Optional: {} |
|
deviceStatus string |
DeviceStatus is the control plane's own string, in the control plane's spelling, which is why it carries no Enum here. |
Optional: {} |
|
role StorageDeviceRole |
Role is what the device carries. | Enum: [Storage Journal] Optional: {} |
|
capacity DeviceCapacity |
Capacity is how big the device is. What it holds is served as StorageDeviceMetrics instead. |
Optional: {} |
|
hardware DeviceHardware |
Hardware identifies the part. | Optional: {} |
|
clusterID string |
ClusterID and NodeID are the backend ids of the stream this device was last observed on. They are recorded because an object outliving its device has to say which scope's absence would be authoritative before the mirror may delete it; the object's own spec names Kubernetes objects rather than backend ones, so it cannot answer that on its own. |
Optional: {} |
|
nodeID string |
Optional: {} |
||
activeOpsRef string |
ActiveOpsRef names the StorageDeviceOps currently allowed to act on this device, and is empty when none is. It is the operation lock every entity of this group carries, and until StorageDeviceOps exists nothing takes it, so the field is present and always empty. Declaring it now is what lets a reader tell "no operation is running" from "this kind cannot say," and it keeps the lock out of the change that introduces the operations. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the device moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from. On this kind it moves at most once, since every spec field is fixed when the object is created. |
Optional: {} |
StorageNode
StorageNode is one backend storage node: one SPDK process bound to one NUMA socket of one Kubernetes worker. One object exists per (workerNode, slot) pair, owned by the StorageCluster it belongs to.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageNode
metadata:
name: string
spec:
clusterRef: string
nodeSet: string
workerNode: string
socketId: string
nodeIndex: integer
slot: integer
config:
sizing:
vcpuCount: integer
minHugePagesSize: string
spdkImage: string
spdkImagePullPolicy: PullPolicy
spdkProxyImage: string
spdkProxyImagePullPolicy: PullPolicy
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
deviceNames:
- '^([0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]|/dev/[a-zA-Z0-9._/-]+|[a-zA-Z0-9._-]+)$'
pcieAllowList:
- string
pcieDenyList:
- string
pcieModel: string
driveSizeRange: string
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
expand: boolean
status:
phase: StorageNodePhase
step: KubeSnapshot
uuid: string
nodeAddTaskID: string
status: string
health: boolean
hostname: string
uptime: string
resources:
cpu: integer
memory: string
volumes: integer
devices:
online: integer
total: integer
capacity:
totalBytes: integer
usedBytes: integer
sampledAt: Time
ports:
management: string
nvmeof: integer
lvol: integer
rpc: integer
failureDomain: string
activeOpsRef: string
latencyMetrics:
nodeUUID: string
baselineP50NS: integer
baselineP99NS: integer
baselineMeasuredAt: Time
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageNode |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec StorageNodeSpec |
|||
status StorageNodeStatus |
StorageNodeCapacity
StorageNodeCapacity is a node's storage occupancy, as the control plane last measured it.
It carries the same two numbers as a device's capacity, because a node's is the sum of its devices' and a reader comparing the two should not have to reconcile different shapes. It is written only when the reading has moved materially: a sample that changed by a few blocks is not worth an etcd write, and writing every sample would make the reconciler retrigger itself on its own status update.
Appears in: - StorageNodeResources
Example:
totalBytes: integer
usedBytes: integer
sampledAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
totalBytes integer |
TotalBytes is the storage the node's devices provide. | Minimum: 0 Optional: {} |
|
usedBytes integer |
UsedBytes is what they currently hold. | Minimum: 0 Optional: {} |
|
sampledAt Time ⧉ |
SampledAt is when the control plane took the reading. It is not when the object was written, and it may be considerably older if metrics collection has stopped. |
Optional: {} |
StorageNodeConfig
StorageNodeConfig is a storage node's complete configuration, copied from the ClusterDeploymentConfig entry that produced the node. It is a copy rather than a projection, because that document is ephemeral and nothing reads it once the node exists.
Most of it is immutable: by marker where a field has no legitimate writer, and by the validating webhook where it has exactly one.
Appears in: - StorageNodeSpec
Example:
sizing:
vcpuCount: integer
minHugePagesSize: string
spdkImage: string
spdkImagePullPolicy: PullPolicy
spdkProxyImage: string
spdkProxyImagePullPolicy: PullPolicy
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
deviceNames:
- '^([0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]|/dev/[a-zA-Z0-9._/-]+|[a-zA-Z0-9._-]+)$'
pcieAllowList:
- string
pcieDenyList:
- string
pcieModel: string
driveSizeRange: string
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
expand: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
sizing StorageNodeSizing |
Sizing is what this node's huge pages and core layout were sized from. Writable by the operator alone. |
Required: {} |
|
spdkImage string |
SpdkImage overrides the SPDK image the control plane starts for this node, which is what makes a phased image rollout expressible per node. |
Optional: {} |
|
spdkImagePullPolicy PullPolicy ⧉ |
SpdkImagePullPolicy controls when that image is pulled, and defaults to Always because the images this product ships are moving tags. The control plane starts the SPDK pod, not the operator, and its spdk_process_start takes no pull policy: the pod template it renders writes Always itself. So a node states the policy here and the node-add call does not yet carry it, which is a gap the control plane closes rather than this kind. Stating anything but Always is therefore recorded and not yet obeyed. |
Always | Enum: [Always Never IfNotPresent] Optional: {} |
spdkProxyImage string |
SpdkProxyImage overrides the SPDK proxy image for this node. | Optional: {} |
|
spdkProxyImagePullPolicy PullPolicy ⧉ |
SpdkProxyImagePullPolicy controls when that image is pulled. It is stated apart from SpdkImagePullPolicy because a node may pin the proxy and follow the SPDK image, and it carries the same not-yet-spent caveat: the template the control plane renders writes Always for both containers. |
Always | Enum: [Always Never IfNotPresent] Optional: {} |
spdkSystemMemory string |
SpdkSystemMemory is the memory the control plane starts this node's SPDK with, as a size string such as 4G or 512M. It can be raised, because a node whose device count grew legitimately needs more, and who may raise it is decided at admission. |
Pattern: ^[0-9]+(G\|GI\|GB\|GiB\|M\|MI\|MB\|MiB\|g\|gi\|gb\|gib\|m\|mi\|mb\|mib)?$ Optional: {} |
|
reservedSystemCPU string |
ReservedSystemCPU is the CPU set held back from SPDK for the system, as a core list such as 0,1 or 0-3. It is per node and not per cluster because it names core ids: 0,1 on a sixteen-core worker and 0,1 on a ninety-six-core worker are different fractions of the machine, and a fleet whose groups differ in core count has no one list that is right for all of them. StorageCluster.spec.storageNodes.reservedSystemCPU is the fleet's value, which the pod carries as an environment variable, and this overrides it for the node that states it. On OpenShift the value reaches the kubelet through a KubeletConfig for the machine config pool rather than through the node alone, so nodes sharing a pool that disagree are writing over one another's pool configuration. It carries no immutability marker, because the CPUs a machine holds back are a tuning decision rather than a layout one, and the webhook that guards spec.config is what decides who may retune them. |
MaxLength: 63 Pattern: ^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$ Optional: {} |
|
journalManager JournalManagerSpec |
JournalManager tunes the journal manager count and per-device capacity share for this node. Immutable: both are on-disk layout, fixed when the devices were partitioned. |
Optional: {} |
|
deviceNames string array |
DeviceNames names the devices to use. An entry is a PCI address ("0000:5e:00.0") or a device path ("/dev/sdb,") which are the two classes simplyblock accepts as backend storage, and a bare name ("nvme0n1") is read as a path under /dev. One list carries both spellings, and every entry is of the class its cluster declares in StorageCluster.spec.deviceClass: a list mixing the two, or naming the class the cluster is not, is rejected by the StorageNode validating webhook. Set explicitly, it overrides every filter below. Immutable: it selects which physical devices the node owns. |
items:Pattern: ^([0-9a-fA-F]\{4\}:[0-9a-fA-F]\{2\}:[0-9a-fA-F]\{2\}\.[0-9a-fA-F]\|/dev/[a-zA-Z0-9._/-]+\|[a-zA-Z0-9._-]+)$ Optional: {} |
|
pcieAllowList string array |
PcieAllowList selects devices by PCI address. It is the one device field a migration writes, merging spec.migrate.newSsdPcie into it so devices added on the target host survive a later rebuild, so it is guarded by the StorageNode validating webhook rather than by a marker. This and the two PCI filters below belong to an NVMe cluster: the webhook rejects them on a cluster whose deviceClass is LogicalBlock, because a logical block device has no PCI address to match. |
Optional: {} |
|
pcieDenyList string array |
PcieDenyList excludes devices by PCI address. | Optional: {} |
|
pcieModel string |
PcieModel filters devices by PCI model string. | Optional: {} |
|
driveSizeRange string |
DriveSizeRange filters devices by size. | Optional: {} |
|
failureDomain string |
FailureDomain is the label of the fault group this node belongs to such as rack-b, naming the physical grouping it shares with its peers rather than indexing it. Required when the cluster has enableFailureDomains set, and provisioning is held with a FailureDomainMissing event until it is present. Immutable once set, which is what makes it fillable later and then frozen: chunk placement was computed from it. The value takes the shape of a Kubernetes label value, because that is what it is seeded from where a cluster carries topology labels at all. |
MaxLength: 63 Pattern: ^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$ Optional: {} |
|
expand boolean |
Expand marks this node as an addition to an already-active cluster, which the control plane reads as a request to rebalance onto it rather than to treat it as part of an initial layout. Immutable once set: it describes how the node joined rather than what it is. |
Optional: {} |
StorageNodeDevices
StorageNodeDevices counts the NVMe devices on a node and how many of them are online. It is a summary rather than an inventory: per-device capacity, health, and conditions belong to StorageDevice.
Neither field takes omitempty. Zero online devices is the condition worth seeing, and a field that disappears at zero would report it as nothing at all. A node the control plane has not reported on is the absent parent instead.
Appears in: - StorageNodeResources
Example:
online: integer
total: integer
| Field | Description | Default | Validation |
|---|---|---|---|
online integer |
Online is how many of the node's devices the control plane reports as usable. |
Minimum: 0 |
|
total integer |
Total is how many devices the node has. | Minimum: 0 |
StorageNodeOps
StorageNodeOps is a single operation performed against one StorageNode. It runs to a terminal phase and stays afterward as the audit record of what was done, to which node, with which parameters, and how it ended.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageNodeOps
metadata:
name: string
spec:
nodeRef: string
action: StorageNodeOpsAction
abort: boolean
force: boolean
reattachVolume: boolean
migrate:
targetWorkerNode: string
newSsdPcie:
- string
remove:
systemVolumeFilterRegex: string
status:
phase: StorageNodeOpsPhase
removal:
nodeStatus: string
devices:
string: string
lastProgressTime: Time
prepareAttempts: integer
lastPrepareTime: Time
step: KubeSnapshot
message: string
drain:
volumesTotal: integer
volumesMigrated: integer
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageNodeOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec StorageNodeOpsSpec |
|||
status StorageNodeOpsStatus |
StorageNodeOpsAction
Underlying type: string
StorageNodeOpsAction is the operation a StorageNodeOps performs. Values are PascalCase, which is the casing every enum this API group defines carries; v1alpha1 spelled them lowercase, and the conversion maps between the two.
Validation: - Enum: [Shutdown Restart Suspend Resume Remove Migrate HostMaintenance]
Appears in: - StorageNodeOpsSpec
| Field | Description |
|---|---|
Shutdown |
|
Restart |
|
Suspend |
|
Resume |
|
Remove |
|
Migrate |
|
HostMaintenance |
StorageNodeOpsActionHostMaintenance takes a node down deliberately so that its Kubernetes worker can be drained and rebooted, then brings it back. The operator raises it when it sees the worker cordoned, and a user creating one by hand behaves identically. |
StorageNodeOpsPhase
Underlying type: string
StorageNodeOpsPhase is the operation's own progress. Aborted is terminal and distinct from Failed, because a canceled operation did not go wrong.
Validation: - Enum: [Pending Running Succeeded Failed Aborted]
Appears in: - StorageNodeOpsStatus
| Field | Description |
|---|---|
Pending |
StorageNodeOpsPhasePending: the operation holds no lock and has issued nothing. |
Running |
StorageNodeOpsPhaseRunning: it holds its node's lock and its first side effect may have been issued. |
Succeeded |
|
Failed |
|
Aborted |
StorageNodeOpsPhaseAborted: called off rather than gone wrong. A drain an administrator stops after an hour reported as Failed would sit in the same bucket as one the control plane rejected. |
StorageNodeOpsSpec
StorageNodeOpsSpec is one operation to perform against one StorageNode.
Appears in: - StorageNodeOps
Example:
nodeRef: string
action: StorageNodeOpsAction
abort: boolean
force: boolean
reattachVolume: boolean
migrate:
targetWorkerNode: string
newSsdPcie:
- string
remove:
systemVolumeFilterRegex: string
| Field | Description | Default | Validation |
|---|---|---|---|
nodeRef string |
NodeRef names the StorageNode this operation acts on. The operation never owns its target, because deleting the record of an operation must not delete the node it operated on. |
Required: {} |
|
action StorageNodeOpsAction |
Action is the operation to perform. | Enum: [Shutdown Restart Suspend Resume Remove Migrate HostMaintenance] Required: {} |
|
abort boolean |
Abort asks a running operation to stop at its next step and unwind. It is the only mutable field on this spec, because it is the only thing about an operation that can legitimately be decided after it started. Whether an abort is expressible from the current step is declared by that action's graph rather than checked here. |
Optional: {} |
|
force boolean |
Force passes the control plane's force flag where the action supports it. Migrate defaults it to true, because the control plane rejects a non-forced restart of a node that is not already offline. |
Optional: {} |
|
reattachVolume boolean |
ReattachVolume asks the control plane to reattach this node's volumes as part of a restart. Applies to Restart, Migrate, and HostMaintenance. |
Optional: {} |
|
migrate MigrateSpec |
Migrate parameterizes action Migrate and is ignored by the others. | Optional: {} |
|
remove RemoveSpec |
Remove parameterizes action Remove and is ignored by the others. | Optional: {} |
StorageNodeOpsStatus
StorageNodeOpsStatus is the observed state of one node operation.
Appears in: - StorageNodeOps
Example:
phase: StorageNodeOpsPhase
removal:
nodeStatus: string
devices:
string: string
lastProgressTime: Time
prepareAttempts: integer
lastPrepareTime: Time
step: KubeSnapshot
message: string
drain:
volumesTotal: integer
volumesMigrated: integer
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageNodeOpsPhase |
Phase is the operation's own progress. | Enum: [Pending Running Succeeded Failed Aborted] Optional: {} |
|
removal RemovalStatus |
Removal is the control plane's progress through the removal, written by a Remove while it waits in AwaitingRemoval. |
Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the running action's state machine. The value is one of the steps the running action declares. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the operation moves, and never a log. |
Optional: {} |
|
drain DrainStatus |
Drain is the drain's progress over the node's volumes, set only for action Remove. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation acquired its target's lock. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when it reached a terminal phase. | Optional: {} |
StorageNodePhase
Underlying type: string
StorageNodePhase is where the operator has got to with this node. The first two values are the operator's own provisioning path; the rest are its reading of the lifecycle status.status carries in the control plane's own spelling.
Validation: - Enum: [Pending Provisioning Online Removing Removed Offline Degraded Failed]
Appears in: - StorageNodeStatus
| Field | Description |
|---|---|
Pending |
StorageNodePhasePending: the object exists and no slot has been claimed for it yet. |
Provisioning |
StorageNodePhaseProvisioning: the provisioning machine is running. |
Online |
StorageNodePhaseOnline: the control plane reports the node online and carrying its share. |
Removing |
StorageNodePhaseRemoving: the control plane is removing the node, from the removal being accepted until its devices and volumes have moved off. |
Removed |
StorageNodePhaseRemoved: the control plane has removed the node. It is the last phase a node reaches, and nothing brings it back into service. |
Offline |
StorageNodePhaseOffline: out of service and reachable, which is where Shutdown, Suspend, and a host maintenance window leave it. |
Degraded |
StorageNodePhaseDegraded: serving with less than its devices, which is the node-level half of what StorageDevice reports per device. |
Failed |
StorageNodePhaseFailed: unreachable, timed out, or provisioning that will not complete. |
StorageNodePorts
StorageNodePorts groups the addresses and ports a node listens on.
Appears in: - StorageNodeStatus
Example:
management: string
nvmeof: integer
lvol: integer
rpc: integer
| Field | Description | Default | Validation |
|---|---|---|---|
management string |
Management is the management IP address of the node. | Optional: {} |
|
nvmeof integer |
The NVMe-oF fabric port. | Optional: {} |
|
lvol integer |
Lvol is the logical-volume subsystem port. | Optional: {} |
|
rpc integer |
Rpc is the RPC and management API port. | Optional: {} |
StorageNodeReference
StorageNodeReference locates a StorageNode from a cluster-scoped object.
Every other reference in this API group is a bare string, which works because both objects are namespaced and a name means the same namespace. A cluster-scoped object has no namespace for a bare name to mean, and two clusters in two namespaces may each hold a node called worker-3, so this one carries both — for the same reason pv.spec.claimRef does in core Kubernetes.
Appears in: - MigrateVolumeSpec
Example:
namespace: string
name: string
| Field | Description | Default | Validation |
|---|---|---|---|
namespace string |
Namespace is where the StorageNode lives, which is the namespace of the StorageCluster that owns it. |
Required: {} |
|
name string |
Name is the StorageNode object's name. | Required: {} |
StorageNodeResources
StorageNodeResources groups the compute and storage figures the control plane reports for a node.
Appears in: - StorageNodeStatus
Example:
cpu: integer
memory: string
volumes: integer
devices:
online: integer
total: integer
capacity:
totalBytes: integer
usedBytes: integer
sampledAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
cpu integer |
CPU is the number of SPDK cores allocated to this node. | Optional: {} |
|
memory string |
Memory is the SPDK memory allocation the control plane reports. | Optional: {} |
|
volumes integer |
Volumes is the current number of logical volumes on this node. | Optional: {} |
|
devices StorageNodeDevices |
Devices summarizes the node's NVMe devices. Absent until the control plane has reported, which is what tells a node that has not reported from one that genuinely has no devices. |
Optional: {} |
|
capacity StorageNodeCapacity |
Capacity is how much of the node's storage is in use, summed over its devices. It is a measurement rather than a declaration, so it is absent until something has measured it, and it lags reality by the interval at which the control plane's metrics are scraped. |
Optional: {} |
StorageNodeSizing
StorageNodeSizing is what this node's SPDK core layout and huge-page floor were sized from. It is stamped from StorageCluster.spec when the node is created and is equal across the fleet in steady state; a rolling hardware upgrade is what makes two nodes differ, and only for as long as the roll takes. The StorageNode validating webhook admits a change from the operator and rejects it from everyone else, because unmanaged divergence is what stops the control plane placing erasure-coding chunks evenly.
The cluster's maxSubsystemCount is not copied in here. It bounds how many volumes a node can serve rather than describing the host the node runs on, so it is the same for every node of a cluster and is read from the cluster when the node's configuration is generated.
Appears in: - StorageNodeConfig
Example:
vcpuCount: integer
minHugePagesSize: string
| Field | Description | Default | Validation |
|---|---|---|---|
vcpuCount integer |
VCPUCount is the number of vCPUs allocated to SPDK on this node, as an explicit core count rather than a percentage. |
Minimum: 4 Required: {} |
|
minHugePagesSize string |
MinHugePagesSize is the smallest huge-page allocation this node makes, as a size string such as 100G or 1T, where a bare number is gigabytes. It is a floor rather than a limit: the effective allocation is the larger of this value and the minimum the node's device and subsystem count requires. |
Optional: {} |
StorageNodeSpec
StorageNodeSpec is the desired state of one backend storage node, meaning one SPDK process bound to one NUMA socket of one Kubernetes worker.
Appears in: - StorageNode
Example:
clusterRef: string
nodeSet: string
workerNode: string
socketId: string
nodeIndex: integer
slot: integer
config:
sizing:
vcpuCount: integer
minHugePagesSize: string
spdkImage: string
spdkImagePullPolicy: PullPolicy
spdkProxyImage: string
spdkProxyImagePullPolicy: PullPolicy
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
deviceNames:
- '^([0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]|/dev/[a-zA-Z0-9._/-]+|[a-zA-Z0-9._-]+)$'
pcieAllowList:
- string
pcieDenyList:
- string
pcieModel: string
driveSizeRange: string
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
expand: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
clusterRef string |
ClusterRef names the StorageCluster this node belongs to. The cluster also owns this object by controller reference, so deleting the cluster deletes its nodes. Bounded at what a StorageCluster name may be, since a longer value names nothing that can exist. |
MaxLength: 63 Required: {} |
|
nodeSet string |
NodeSet is the name of the group in ClusterDeploymentConfig.nodeSets[] this node was declared under. It is a label rather than a reference: nothing is fetched by it, and it exists so that a node can be traced back to the document that produced it. |
Optional: {} |
|
workerNode string |
WorkerNode is the Kubernetes worker hostname this node runs on. It is not immutable, because a migration re-points it, but a change made by anything outside the operator's namespace is rejected. |
Required: {} |
|
socketId string |
SocketID is the NUMA socket this node is bound to, as declared in the node set's socket list, so 0 or 1. With NodeIndex it decomposes Slot into the pair a person reads; nothing but a print column consumes either. It is not marked immutable, because where a node sits is a fact about the host it runs on and a relocation moves it: the target worker's free socket is not necessarily the source's. The StorageNode validating webhook rejects a change made by an identity outside the operator's namespace, which is the same treatment WorkerNode takes and for the same reason. |
Optional: {} |
|
nodeIndex integer |
NodeIndex is the position among the nodes sharing this socket, in 0..nodesPerSocket-1. See SocketID, whose guard it shares. |
Minimum: 0 Optional: {} |
|
slot integer |
Slot is which storage-node slot on this worker the object occupies, counted from zero. A worker runs one node per socket per nodesPerSocket, and the slot is the position among them. It is the identity the operator keys on: the topology label the CSI driver reads is storage.simplyblock.io/storage-node-uuid. outlives the node filling it, because only the UUID behind it changes when a node is replaced or relocated. |
Minimum: 0 Optional: {} |
|
config StorageNodeConfig |
Config is this node's complete configuration, copied from the ClusterDeploymentConfig entry that produced it. It is a copy rather than a projection, because that document is ephemeral: nothing reads it once the node exists, deleting it changes nothing, and editing it reaches only nodes created afterward. |
Required: {} |
StorageNodeStatus
StorageNodeStatus is the observed state of one storage node.
Appears in: - StorageNode
Example:
phase: StorageNodePhase
step: KubeSnapshot
uuid: string
nodeAddTaskID: string
status: string
health: boolean
hostname: string
uptime: string
resources:
cpu: integer
memory: string
volumes: integer
devices:
online: integer
total: integer
capacity:
totalBytes: integer
usedBytes: integer
sampledAt: Time
ports:
management: string
nvmeof: integer
lvol: integer
rpc: integer
failureDomain: string
activeOpsRef: string
latencyMetrics:
nodeUUID: string
baselineP50NS: integer
baselineP99NS: integer
baselineMeasuredAt: Time
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageNodePhase |
Phase is the operator's own view of this node, and the field its provisioning branches on. |
Enum: [Pending Provisioning Online Removing Removed Offline Degraded Failed] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the provisioning machine. The value is one of the steps that machine declares. |
Optional: {} |
|
uuid string |
UUID is the backend node UUID. Empty means the node has neither been provisioned nor adopted, and non-empty means steady state. |
Optional: {} |
|
nodeAddTaskID string |
NodeAddTaskID is the control-plane task the last add of this node created. It is how a node that is still being added reads why its own add is failing. |
Optional: {} |
|
status string |
Status is the lifecycle the control plane reports: online, suspended, offline, in_creation, in_restart, in_shutdown, unreachable, or timeout. The values are the control plane's, which is why they are neither PascalCase nor constrained by an Enum here. |
Optional: {} |
|
health boolean |
Health is the health flag the control plane reports. | Optional: {} |
|
hostname string |
Hostname is the node hostname as the control plane reports it. | Optional: {} |
|
uptime string |
Uptime is the node uptime as the control plane reports it. | Optional: {} |
|
resources StorageNodeResources |
Resources groups the reported compute and storage figures. | Optional: {} |
|
ports StorageNodePorts |
Ports groups the reported addresses and ports. | Optional: {} |
|
failureDomain string |
FailureDomain is the failure-domain label the control plane actually assigned, which is not necessarily the one spec.config.failureDomain requested. |
Optional: {} |
|
activeOpsRef string |
ActiveOpsRef names the StorageNodeOps currently allowed to touch this node. Empty when none is running. |
Optional: {} |
|
latencyMetrics NodeLatencyMetrics |
LatencyMetrics holds the fio-measured NVMe-oF baseline the volume rebalancer reads. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the node moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
StorageNodesSpec
StorageNodesSpec is the Kubernetes workload every storage node in the cluster runs as: a DaemonSet, a headless Service and its EndpointSlices, a serving certificate, a ServiceAccount, and the ConfigMap the init container reads its per-node configuration out of.
Every field here is cluster-uniform by construction, because a DaemonSet is one object for every node it schedules and its pod template cannot differ per node. What can differ is in StorageNode.spec.config: the two images, the SPDK system memory, and the sizing block, which are per node precisely so that an image rollout and a hardware re-size can walk the fleet one machine at a time.
Appears in: - StorageClusterSpec
Example:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
imagePullPolicy: PullPolicy
mgmtInterface: string
dataInterfaces:
- string
socketsToUse:
- string
nodesPerSocket: integer
nodeProvisioningBudget: integer
enableJournalDevice: boolean
enableFormat4K: boolean
enableBlockFormat: boolean
enableCpuTopology: boolean
reservedSystemCPU: string
enableKubeletConfiguration: boolean
ubuntuHost: boolean
openshift:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
tolerations:
- Toleration
containerResources: ResourceRequirements
initContainerResources: ResourceRequirements
| Field | Description | Default | Validation |
|---|---|---|---|
image string |
Image is the storage-node container image. Defaults to the ControlPlane singleton's spec.image when unset, so a deployment states the version once. |
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Optional: {} |
|
imagePullPolicy PullPolicy ⧉ |
ImagePullPolicy controls when that image is pulled. It defaults to Always for the reason SimplyblockDriver's does: the images this product ships are moving tags, so a node brought up after a release that kept IfNotPresent would run whatever its kubelet already held. The workload builder has always meant this and falls back to Always for an unset policy, a fallback the schema default means it never reaches. |
Always | Enum: [Always Never IfNotPresent] Optional: {} |
mgmtInterface string |
MgmtInterface is the management network interface storage nodes bind. | Optional: {} |
|
dataInterfaces string array |
DataInterfaces are the data-plane network interfaces. | Optional: {} |
|
socketsToUse string array |
SocketsToUse restricts deployment to selected NUMA sockets. Empty means socket 0 alone. |
Optional: {} |
|
nodesPerSocket integer |
NodesPerSocket is how many storage nodes run per NUMA socket. | 1 | Minimum: 1 Optional: {} |
nodeProvisioningBudget integer |
NodeProvisioningBudget limits how many workers may be in the node-add process at once, counted by distinct worker rather than by object so that a two-socket host consumes one slot. Workers hosting a FoundationDB pod are always sequential regardless of this value, because a node add reboots the host and two simultaneous FoundationDB reboots reduce the control plane's own fault tolerance. |
1 | Minimum: 1 Optional: {} |
enableJournalDevice boolean |
EnableJournalDevice dedicates the smallest NVMe device on each node to the journal manager, instead of carving a journal partition out of every device. |
Optional: {} |
|
enableFormat4K boolean |
EnableFormat4K formats NVMe devices to a 4K block size where the device supports it. |
Optional: {} |
|
enableBlockFormat boolean |
EnableBlockFormat wipes the partition tables and filesystem signatures from this cluster's logical block devices, so that a disk carrying something already becomes one a storage node can take. It is the block class's half of the document's enableDriveFormat, and a separate field because it is a separate operation on a separate channel: EnableFormat4K is a reformat the control plane performs at node-add, and this is a wipefs node_configure.py performs on the worker before the node is added at all. A block device's block size is fixed by the drive, so there is no reformat to ask for, and an NVMe controller is handed to SPDK whole, so there are no signatures to wipe. Neither operation is available in the other's class. Destructive, and immutable for the reason the other is: it describes what was done to the disks a fleet was built on. |
Optional: {} |
|
enableCpuTopology boolean |
EnableCpuTopology turns on topology-aware CPU assignment. | Optional: {} |
|
reservedSystemCPU string |
ReservedSystemCPU is the CPU set held back from SPDK for system workloads. | Optional: {} |
|
enableKubeletConfiguration boolean |
EnableKubeletConfiguration lets the storage node apply the kubelet configuration changes it needs. Off by default, which is the behavior the retired skipKubeletConfiguration expressed by being set. |
Optional: {} |
|
ubuntuHost boolean |
UbuntuHost states that the worker's host OS is Ubuntu, which changes how the node configures huge pages and the kernel modules it loads. |
Optional: {} |
|
openshift OpenShiftSpec |
OpenShift is what this deployment states because it runs on OpenShift, and its presence is that statement. See OpenShiftSpec. |
Optional: {} |
|
tolerations Toleration ⧉ array |
Tolerations are applied to the storage-node pods. | Optional: {} |
|
containerResources ResourceRequirements ⧉ |
ContainerResources sets requests and limits for the storage-node container. Unset enforces no limits. |
Optional: {} |
|
initContainerResources ResourceRequirements ⧉ |
InitContainerResources does the same for the init container. | Optional: {} |
StoragePool
StoragePool carves a StorageCluster into a unit with its own capacity limit and QoS ceilings, and it is what a StorageClass is assigned to. It is therefore the join between the storage administrator's world and the application developer's, a join held together by three labels, an opaque parameter map, and a finalizer rather than by the API.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StoragePool
metadata:
name: string
spec:
clusterRef: string
allowedNodes:
- string
limits:
capacity: string
maxVolumeSize: string
iops: integer
throughput:
read: integer
write: integer
readWrite: integer
volumeDefaults:
iops: integer
throughput:
read: integer
write: integer
readWrite: integer
filesystem: string
enableCompression: boolean
enableClientCompression: boolean
enableClientDeduplication: boolean
enableReplication: boolean
enableDHCHAP: boolean
priorityClass: string
fabric: string
maxNamespacesPerSubsystem: integer
tune2fsReservedBlocks: string
status:
phase: StoragePoolPhase
uuid: string
status: string
storageClassNames:
- string
defaultStorageClassName: string
limits:
host: string
iops: integer
throughput:
read: integer
write: integer
readWrite: integer
allowedNodes:
- string
activeOpsRef: string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StoragePool |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec StoragePoolSpec |
|||
status StoragePoolStatus |
StoragePoolOps
StoragePoolOps is a single operation performed against one StoragePool. Analogous to a Kubernetes Job: it drives an action to completion and records the result, and only one StoragePoolOps may be active per StoragePool at a time, which the pool's status.activeOpsRef enforces.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StoragePoolOps
metadata:
name: string
spec:
poolRef: string
action: StoragePoolOpsAction
abort: boolean
status:
phase: StoragePoolOpsPhase
step: KubeSnapshot
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StoragePoolOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec StoragePoolOpsSpec |
|||
status StoragePoolOpsStatus |
StoragePoolOpsAction
Underlying type: string
StoragePoolOpsAction is the operation a StoragePoolOps performs. The kind holds one action and that action is provisional: nothing implements it and nothing depends on it. It is declared because the motivation is real and cheaper to keep than to rediscover, not because it is work in progress.
Validation: - Enum: [Rebalance]
Appears in: - StoragePoolOpsSpec
| Field | Description |
|---|---|
Rebalance |
StoragePoolOpsActionRebalance moves the pool's volumes off nodes spec.allowedNodes no longer lists. It is a fan-out of PersistentVolumeOps rather than a backend call: the control plane has no pool-granularity rebalance and does not need one, because moving a volume is already an operation this group has. The decision is the pool's and only the execution is per-volume, which is why the action lives on this kind. |
StoragePoolOpsPhase
Underlying type: string
StoragePoolOpsPhase is the operation's own progress.
Validation: - Enum: [Pending Running Succeeded Failed Aborted]
Appears in: - StoragePoolOpsStatus
| Field | Description |
|---|---|
Pending |
StoragePoolOpsPhasePending is an operation waiting for its target's lock. |
Running |
StoragePoolOpsPhaseRunning is an operation holding the lock and working. |
Succeeded |
StoragePoolOpsPhaseSucceeded is a finished operation that did what it said. |
Failed |
StoragePoolOpsPhaseFailed is a finished operation that did not. |
Aborted |
StoragePoolOpsPhaseAborted is an operation stopped on request, whose unwind has finished. |
StoragePoolOpsSpec
StoragePoolOpsSpec is one operation to perform against one StoragePool.
Appears in: - StoragePoolOps
Example:
poolRef: string
action: StoragePoolOpsAction
abort: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
poolRef string |
PoolRef names the StoragePool this operation acts on, in this object's own namespace. The operation never owns its target, because deleting the record of an operation must not delete the pool it operated on. |
Required: {} |
|
action StoragePoolOpsAction |
Action is the operation to perform. Immutable: an operation that changed what it was doing halfway through would have a status describing neither. |
Enum: [Rebalance] Required: {} |
|
abort boolean |
Abort asks a running operation to stop at its next step and unwind. | Optional: {} |
StoragePoolOpsStatus
StoragePoolOpsStatus is the observed state of one pool operation.
Appears in: - StoragePoolOps
Example:
phase: StoragePoolOpsPhase
step: KubeSnapshot
message: string
observedGeneration: integer
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase StoragePoolOpsPhase |
Phase is the operation's own progress. | Enum: [Pending Running Succeeded Failed Aborted] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the running action's state machine. It is persisted before the side effect that step performs, so a step reported here is a step that started. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the operation moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation acquired its target's lock. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when it reached a terminal phase. | Optional: {} |
StoragePoolPhase
Underlying type: string
StoragePoolPhase is where the operator has got to with this pool.
Validation: - Enum: [Pending Ready Deleting]
Appears in: - StoragePoolStatus
| Field | Description |
|---|---|
Pending |
StoragePoolPhasePending is a pool that has no backend UUID yet, whether because its cluster is not finished or because the create has not landed. |
Ready |
StoragePoolPhaseReady is a pool the control plane has created and that volumes can be provisioned from. |
Deleting |
StoragePoolPhaseDeleting is a pool being torn down, which may be held for a long time behind an assigned class or a bound volume. |
StoragePoolSpec
StoragePoolSpec is the desired state of one tenancy unit within a cluster.
Appears in: - StoragePool
Example:
clusterRef: string
allowedNodes:
- string
limits:
capacity: string
maxVolumeSize: string
iops: integer
throughput:
read: integer
write: integer
readWrite: integer
volumeDefaults:
iops: integer
throughput:
read: integer
write: integer
readWrite: integer
filesystem: string
enableCompression: boolean
enableClientCompression: boolean
enableClientDeduplication: boolean
enableReplication: boolean
enableDHCHAP: boolean
priorityClass: string
fabric: string
maxNamespacesPerSubsystem: integer
tune2fsReservedBlocks: string
| Field | Description | Default | Validation |
|---|---|---|---|
clusterRef string |
ClusterRef names the StorageCluster this pool is carved out of, in this pool's own namespace. The cluster owns this object by controller reference, so deleting the cluster deletes its pools, held behind each pool's own finalizer while classes are assigned or volumes are bound. Immutable from creation: which cluster a pool is in is its identity. The maximum is what a StorageCluster name may be rather than what a reference may be: a longer value names nothing that can exist, and the reference is immutable, so admitting one creates a pool whose only remedy is deletion. |
MaxLength: 63 Required: {} |
|
allowedNodes string array |
AllowedNodes restricts which hosts may carry this pool's volumes, by Kubernetes Node name. Empty means every node in the cluster. Narrowing it stops new volumes landing on the removed nodes and leaves the existing ones where they are. Requires volumeDefaults.enableDHCHAP to be true. The list is left exactly as authored: a name that no longer resolves is dropped from Status.AllowedNodes rather than pruned from here, so a node removed for maintenance and added back under the same name returns to the pools that named it without anybody re-authoring them. |
Optional: {} |
|
limits PoolLimits |
Limits are the ceilings the pool as a whole is held to. Mutable: raising a pool's capacity is an ordinary operation the control plane supports, and it does not touch any StorageClass. |
Optional: {} |
|
volumeDefaults VolumeDefaults |
VolumeDefaults are what every volume in the pool is created with. Immutable once set, because StorageClass.parameters is immutable in the Kubernetes API: a pool whose defaults changed would have a class the operator cannot update. Changing them means creating a new pool. |
Optional: {} |
StoragePoolStatus
StoragePoolStatus is the observed state of one pool.
Appears in: - StoragePool
Example:
phase: StoragePoolPhase
uuid: string
status: string
storageClassNames:
- string
defaultStorageClassName: string
limits:
host: string
iops: integer
throughput:
read: integer
write: integer
readWrite: integer
allowedNodes:
- string
activeOpsRef: string
message: string
observedGeneration: integer
| Field | Description | Default | Validation |
|---|---|---|---|
phase StoragePoolPhase |
Phase is the operator's own view of this pool. | Enum: [Pending Ready Deleting] Optional: {} |
|
uuid string |
UUID is the backend pool UUID. Empty means the pool has not been created, and it is the field the controller branches on. |
Optional: {} |
|
status string |
Status is the lifecycle the control plane reports, in the control plane's own spelling, which is why it carries no Enum here. |
Optional: {} |
|
storageClassNames string array |
StorageClassNames are the classes assigned to this pool, found by the three storage.simplyblock.io labels a class carries. Empty means no class draws from this pool yet, which is a valid state and what a freshly created cluster's default pool has before anybody writes one. Publishing it is what makes the assignment readable from the pool, and what a deletion is held on. |
Optional: {} |
|
defaultStorageClassName string |
DefaultStorageClassName is the class the operator wrote for the default pool, set on that pool only. It records that the class was created, so a class missing while this is set was deleted deliberately and is not written again. |
Optional: {} |
|
limits PoolLimitsStatus |
Limits is what the control plane reports the ceilings to be. | Optional: {} |
|
allowedNodes string array |
AllowedNodes is Spec.AllowedNodes resolved against the Node objects that exist, which is what the control plane's host list and the per-pool node labels are derived from. An empty list here is not the same as an absent Spec.AllowedNodes: absent means every node, and empty after resolution means the pool can place nothing. |
Optional: {} |
|
activeOpsRef string |
ActiveOpsRef names the StoragePoolOps currently allowed to act on this pool. Empty when none is running. |
Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the pool moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
StorageSiteCluster
StorageSiteCluster is the StorageCluster the approved draft produced.
Appears in: - StorageSiteDeploymentStatus
Example:
name: string
uuid: string
phase: string
pool: string
nodes:
- name: string
phase: string
hostname: string
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name is the StorageCluster object on the site. | ||
uuid string |
UUID is the storage cluster's id in the control plane, which a StorageClass names in cluster_id. |
Optional: {} |
|
phase string |
Phase is the StorageCluster's phase on the site. | Optional: {} |
|
pool string |
Pool is the pool the cluster was created with, which a StorageClass names in pool_name. |
Optional: {} |
|
nodes StorageSiteNode array |
Nodes are the cluster's storage nodes. | Optional: {} |
StorageSiteDeployment
StorageSiteDeployment requests a managed site's storage cluster from the hub: a discovery on the site, the sizing of the draft it writes, and the approval that expands the draft into a StorageCluster. The hub carries the request through OCM and projects the site's draft and cluster into the status. Deleting the request leaves the storage cluster alone.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageSiteDeployment
metadata:
name: string
spec:
cluster: string
siteNamespace: string
draftName: string
discover:
enableControlPlaneNodes: boolean
workers:
- string
nodeSelector:
string: string
sizing:
name: string
vcpuCount: integer
minHugePagesSize: string
maxSubsystemCount: integer
enableDriveFormat: boolean
enableJournalDevice: boolean
stripe:
dataChunks: integer
parityChunks: integer
approved: boolean
status:
phase: StorageSiteDeploymentPhase
message: string
observedGeneration: integer
workName: string
draft:
name: string
phase: string
message: string
approved: boolean
cluster:
name: string
maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
enableDriveFormat: boolean
enableJournalDevice: boolean
socketsToUse:
- string
nodesPerSocket: integer
initContainerResources: ResourceRequirements
tolerations:
- Toleration
containerResources: ResourceRequirements
nodeProvisioningBudget: integer
enableChecksumValidation: boolean
enableAtomicity4K: boolean
stripe:
dataChunks: integer
parityChunks: integer
fabricType: string
openshift:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
ports:
nvmf: integer
rpc: integer
nodeAgent: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
backup:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
kms:
vault:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
nodeSets:
- name: string
groups:
- name: string
workers:
- string
mgmtInterface: string
dataInterfaces:
- string
devices:
nvme:
- '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
block:
- '^/dev/[a-zA-Z0-9._/-]+$'
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
nodeRefs:
- string
storageCluster:
name: string
uuid: string
phase: string
pool: string
nodes:
- name: string
phase: string
hostname: string
conditions:
- Condition
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
StorageSiteDeployment |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec StorageSiteDeploymentSpec |
|||
status StorageSiteDeploymentStatus |
StorageSiteDeploymentPhase
Underlying type: string
StorageSiteDeploymentPhase is the request's own progress.
Validation: - Enum: [Pending Discovering Drafted Deploying Online Failed]
Appears in: - StorageSiteDeploymentStatus
| Field | Description |
|---|---|
Pending |
StorageSiteDeploymentPhasePending is the request before the hub delivered anything to the site. |
Discovering |
StorageSiteDeploymentPhaseDiscovering is the discovery running on the site: the draft is not written yet, or names no node yet. |
Drafted |
StorageSiteDeploymentPhaseDrafted is a draft with nodes on the site, sized as the request says, awaiting approval. |
Deploying |
StorageSiteDeploymentPhaseDeploying is an approved draft expanding into a StorageCluster that is not Online yet. |
Online |
StorageSiteDeploymentPhaseOnline is the StorageCluster Online on the site. |
Failed |
StorageSiteDeploymentPhaseFailed is the site's own failure: the draft or the StorageCluster failed, or the hub could not deliver the request. |
StorageSiteDeploymentSpec
StorageSiteDeploymentSpec is the request for one site's storage cluster.
Appears in: - StorageSiteDeployment
Example:
cluster: string
siteNamespace: string
draftName: string
discover:
enableControlPlaneNodes: boolean
workers:
- string
nodeSelector:
string: string
sizing:
name: string
vcpuCount: integer
minHugePagesSize: string
maxSubsystemCount: integer
enableDriveFormat: boolean
enableJournalDevice: boolean
stripe:
dataChunks: integer
parityChunks: integer
approved: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
cluster string |
Cluster is the OCM ManagedCluster the storage is deployed on. The request's ManifestWork and views live in its namespace on the hub. Immutable. |
MaxLength: 63 MinLength: 1 Required: {} |
|
siteNamespace string |
SiteNamespace is the simplyblock operator's namespace on the site, where the discovery and the draft live. |
simplyblock | MaxLength: 63 Optional: {} |
draftName string |
DraftName is the ClusterDeploymentConfig the discovery writes on the site and the request sizes and approves. Immutable. |
site-draft | MaxLength: 63 Optional: {} |
discover StorageSiteDiscovery |
Discover is the discovery the site runs first. Changing it runs another discovery, which rewrites the draft. |
Optional: {} |
|
sizing StorageSiteSizing |
Sizing is written onto the draft's cluster template once the draft exists, so the reviewer sees the sized draft before approving it. |
Optional: {} |
|
approved boolean |
Approved is the review gate, delivered to the draft on the site. One-way, as the draft's own gate is. |
false | Optional: {} |
StorageSiteDeploymentStatus
StorageSiteDeploymentStatus is what the site reports back, projected.
Appears in: - StorageSiteDeployment
Example:
phase: StorageSiteDeploymentPhase
message: string
observedGeneration: integer
workName: string
draft:
name: string
phase: string
message: string
approved: boolean
cluster:
name: string
maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
enableDriveFormat: boolean
enableJournalDevice: boolean
socketsToUse:
- string
nodesPerSocket: integer
initContainerResources: ResourceRequirements
tolerations:
- Toleration
containerResources: ResourceRequirements
nodeProvisioningBudget: integer
enableChecksumValidation: boolean
enableAtomicity4K: boolean
stripe:
dataChunks: integer
parityChunks: integer
fabricType: string
openshift:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
ports:
nvmf: integer
rpc: integer
nodeAgent: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
backup:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
kms:
vault:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
nodeSets:
- name: string
groups:
- name: string
workers:
- string
mgmtInterface: string
dataInterfaces:
- string
devices:
nvme:
- '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
block:
- '^/dev/[a-zA-Z0-9._/-]+$'
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
nodeRefs:
- string
storageCluster:
name: string
uuid: string
phase: string
pool: string
nodes:
- name: string
phase: string
hostname: string
conditions:
- Condition
| Field | Description | Default | Validation |
|---|---|---|---|
phase StorageSiteDeploymentPhase |
Phase is the request's own progress. | Enum: [Pending Discovering Drafted Deploying Online Failed] Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the request moves, and never a log. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from. |
Optional: {} |
|
workName string |
WorkName is the ManifestWork carrying the request to the site. | Optional: {} |
|
draft StorageSiteDraft |
Draft is the draft as the site reports it. | Optional: {} |
|
storageCluster StorageSiteCluster |
StorageCluster is the cluster the approved draft produced. | Optional: {} |
|
conditions Condition ⧉ array |
Conditions: Delivered (the work is applied on the site), Discovered (the draft names nodes), Approved (the site's draft is approved), Ready (the StorageCluster is Online). |
Optional: {} |
StorageSiteDiscovery
StorageSiteDiscovery is the discovery the site runs: which nodes are inspected. It is the hub-side form of OperatorOps.spec.discover.
Appears in: - StorageSiteDeploymentSpec
Example:
enableControlPlaneNodes: boolean
workers:
- string
nodeSelector:
string: string
| Field | Description | Default | Validation |
|---|---|---|---|
enableControlPlaneNodes boolean |
EnableControlPlaneNodes lets the discovery consider the nodes that run the API server. Every server of a small distribution is one, so a three-node site has no storage without it. |
Optional: {} |
|
workers string array |
Workers limits the discovery to these nodes. Empty is every worker. | Optional: {} |
|
nodeSelector object (keys:string, values:string) |
NodeSelector limits the discovery to the nodes carrying these labels. | Optional: {} |
StorageSiteDraft
StorageSiteDraft is the draft as the site reports it.
Appears in: - StorageSiteDeploymentStatus
Example:
name: string
phase: string
message: string
approved: boolean
cluster:
name: string
maxSubsystemCount: integer
vcpuCount: integer
minHugePagesSize: string
enableDriveFormat: boolean
enableJournalDevice: boolean
socketsToUse:
- string
nodesPerSocket: integer
initContainerResources: ResourceRequirements
tolerations:
- Toleration
containerResources: ResourceRequirements
nodeProvisioningBudget: integer
enableChecksumValidation: boolean
enableAtomicity4K: boolean
stripe:
dataChunks: integer
parityChunks: integer
fabricType: string
openshift:
machineConfigPool: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
ports:
nvmf: integer
rpc: integer
nodeAgent: integer
enableFailureDomains: boolean
enableNodeAffinity: boolean
backup:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
bucket: string
region: string
credentialsSecretRef: LocalObjectReference
kms:
vault:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
nodeSets:
- name: string
groups:
- name: string
workers:
- string
mgmtInterface: string
dataInterfaces:
- string
devices:
nvme:
- '^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$'
block:
- '^/dev/[a-zA-Z0-9._/-]+$'
failureDomain: '^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$'
spdkSystemMemory: '^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$'
reservedSystemCPU: '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'
journalManager:
count: integer
percentPerDevice: integer
nodeRefs:
- string
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name is the ClusterDeploymentConfig on the site. | ||
phase string |
Phase is the draft's own phase on the site (Draft, Expanding, Expanded, Failed). |
Optional: {} |
|
message string |
Message is what the site says about the draft: validation findings while it is a draft, the expansion's step afterwards. |
Optional: {} |
|
approved boolean |
Approved is whether the draft is approved on the site. | Optional: {} |
|
cluster ClusterTemplate |
Cluster is the draft's cluster template, with the sizing applied. | Optional: {} |
|
nodeSets NodeSet array |
NodeSets are the nodes and devices the discovery found, for review. | Optional: {} |
|
nodeRefs string array |
NodeRefs are the StorageNode objects the expansion created. | Optional: {} |
StorageSiteNode
StorageSiteNode is one storage node of the deployed cluster, as the site reports it.
Appears in: - StorageSiteCluster
Example:
name: string
phase: string
hostname: string
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name is the StorageNode object on the site. | ||
phase string |
Phase is the node's phase on the site. | Optional: {} |
|
hostname string |
Hostname is the Kubernetes node it runs on. | Optional: {} |
StorageSiteSizing
StorageSiteSizing is the cluster template written onto the draft before it is approved: the fields of ClusterDeploymentConfig.spec.cluster a reviewer decides. Absent fields keep what the discovery wrote.
Appears in: - StorageSiteDeploymentSpec
Example:
name: string
vcpuCount: integer
minHugePagesSize: string
maxSubsystemCount: integer
enableDriveFormat: boolean
enableJournalDevice: boolean
stripe:
dataChunks: integer
parityChunks: integer
| Field | Description | Default | Validation |
|---|---|---|---|
name string |
Name is the StorageCluster's name on the site. | MaxLength: 63 Optional: {} |
|
vcpuCount integer |
VCPUCount is the number of vCPUs each storage node takes. | Minimum: 1 Optional: {} |
|
minHugePagesSize string |
MinHugePagesSize is the hugepage memory each storage node takes, as a quantity ("8G"). |
Optional: {} |
|
maxSubsystemCount integer |
MaxSubsystemCount is the number of NVMe-oF subsystems each node serves. | Minimum: 1 Optional: {} |
|
enableDriveFormat boolean |
EnableDriveFormat lets the deployment format the devices it takes. | Optional: {} |
|
enableJournalDevice boolean |
EnableJournalDevice dedicates one device per node to the journal. | Optional: {} |
|
stripe StripeSpec |
Stripe is the erasure-coding layout. | Optional: {} |
StripeSpec
StripeSpec is the erasure-coding layout: how many data chunks a stripe carries and how many parity chunks protect them.
The pair is one of the seven schemes simplyblock supports, and the rule below is the same set the control plane holds in SUPPORTED_ERASURE_CODING_SCHEMES. It is stated here as well because the control plane's refusal arrives at the cluster create, which is several steps and — for a cluster a deployment config produced — one irreversible approval after the apply that stated the scheme.
Each scheme also has a storage-node count below which it must not be used, which is ndcs+npcs nodes to place a stripe across plus one spare per tolerated failure to rebuild onto: 1 for 1+0, 3 for 1+1, 4 for 2+1, 6 for 4+1, 5 for 1+2, 6 for 2+2, and 8 for 4+2. That is not expressible here, because the nodes are objects of their own and a cluster is created before any of them exists. It is answered by the deployment config's validation, by its approval webhook, and by the cluster's activation gate.
Appears in: - ClusterTemplate - StorageClusterSpec - StorageSiteSizing
Example:
dataChunks: integer
parityChunks: integer
| Field | Description | Default | Validation |
|---|---|---|---|
dataChunks integer |
DataChunks is the number of data chunks per stripe (ndcs). | Minimum: 1 Optional: {} |
|
parityChunks integer |
ParityChunks is the number of parity chunks per stripe (npcs), and therefore how many chunk losses a stripe survives. |
Minimum: 0 Optional: {} |
TestFailover
TestFailover is a one-way, non-disruptive test-failover drill. It recovers a source volume, or a consistency group, from a snapshot into an isolated namespace on a chosen cluster as bound PVCs, without touching the source. The hub reads the source on its cluster and places the bubble on the recovery cluster through OCM. It runs to a terminal phase, or holds Ready until it is deleted, and deletion reclaims the clones and any snapshots the drill took.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: TestFailover
metadata:
name: string
spec:
scope: TestFailoverScope
sourceCluster: string
sourceNamespace: string
sourceRef: string
bubbleCluster: string
bubbleNamespace: string
ttlSeconds: integer
status:
phase: TestFailoverPhase
step: KubeSnapshot
message: string
triggered: boolean
observedGeneration: integer
clones:
- sourceRef: string
sourceHandle: string
snapshotID: string
cloneID: string
pvcName: string
sizeBytes: integer
sourceVolumeContext:
string: string
sourceFSType: string
sourceVolumeMode: string
report:
bubbleCluster: string
recoveryPoint: string
recoveryPointTime: Time
recoveryPointAgeSeconds: integer
invariantsHeld: boolean
startedAt: Time
readyAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
TestFailover |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec TestFailoverSpec |
|||
status TestFailoverStatus |
TestFailoverClone
TestFailoverClone is one recovered volume: the source it came from, the snapshot and clone the drill built, and the PVC placed on the bubble cluster.
Appears in: - TestFailoverStatus
Example:
sourceRef: string
sourceHandle: string
snapshotID: string
cloneID: string
pvcName: string
sizeBytes: integer
sourceVolumeContext:
string: string
sourceFSType: string
sourceVolumeMode: string
| Field | Description | Default | Validation |
|---|---|---|---|
sourceRef string |
SourceRef is the source volume, or group member, the recovered volume maps to. |
||
sourceHandle string |
SourceHandle is the source volume's backend handle, read from its PV. | Optional: {} |
|
snapshotID string |
SnapshotID is the recovery-point snapshot: the replicated snapshot already on the bubble cluster's backend that the clone is built from. |
Optional: {} |
|
cloneID string |
CloneID is the backend id of the writable clone. | Optional: {} |
|
pvcName string |
PVCName is the bound PVC in the bubble namespace on the bubble cluster. | Optional: {} |
|
sizeBytes integer |
SizeBytes is the recovered volume's size. | Optional: {} |
|
sourceVolumeContext object (keys:string, values:string) |
SourceVolumeContext is the source PV's CSI volumeAttributes, minus the identity and provisioner keys, carried onto the bubble PV so the node plugin receives a non-nil VolumeContext when it stages the clone. The clone's own identity (NQN, connections, nsId, and so on) is re-resolved from the clone handle at stage time, so only the class-level parameters are carried; the identity keys are dropped so a failed clone lookup can never point the mount back at the source. |
Optional: {} |
|
sourceFSType string |
SourceFSType is the source PV's CSI fsType, carried onto the bubble PV so the node plugin stages the clone with the filesystem it actually carries. The clone is a block copy of the source, so its filesystem is the source's; an empty fsType makes the node plugin default to ext4 and refuse to mount an XFS volume. |
Optional: {} |
|
sourceVolumeMode string |
SourceVolumeMode is the source PV's volumeMode (Filesystem or Block), carried onto the bubble PV and PVC. A VM's disk is a Block claim; a bubble claim that omitted the mode defaulted to Filesystem and the kubelet asked the node plugin to mount a raw guest disk (2026-10-03). |
Optional: {} |
TestFailoverPhase
Underlying type: string
TestFailoverPhase is the drill's own progress.
Validation: - Enum: [Pending Provisioning Ready Failed TearingDown]
Appears in: - TestFailoverStatus
| Field | Description |
|---|---|
Pending |
|
Provisioning |
|
Ready |
|
Failed |
|
TearingDown |
TestFailoverReport
TestFailoverReport is the evidence a drill produces.
Appears in: - TestFailoverStatus
Example:
bubbleCluster: string
recoveryPoint: string
recoveryPointTime: Time
recoveryPointAgeSeconds: integer
invariantsHeld: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
bubbleCluster string |
BubbleCluster is the cluster the drill recovered onto. | Optional: {} |
|
recoveryPoint string |
RecoveryPoint is the snapshot or group generation the drill recovered. | Optional: {} |
|
recoveryPointTime Time ⧉ |
RecoveryPointTime is when that point was taken. | Optional: {} |
|
recoveryPointAgeSeconds integer |
RecoveryPointAgeSeconds is the drill time minus the recovery-point time. | Optional: {} |
|
invariantsHeld boolean |
InvariantsHeld is true only when the source fingerprint taken before the drill matches the one taken at Ready. A Ready drill with this false is a defect. |
Optional: {} |
TestFailoverScope
Underlying type: string
TestFailoverScope selects what a drill recovers.
Validation: - Enum: [Volume Group]
Appears in: - TestFailoverSpec
| Field | Description |
|---|---|
Volume |
TestFailoverScopeVolume recovers a single source volume, named by a PVC. |
Group |
TestFailoverScopeGroup recovers a consistency group from one group-consistent point. |
TestFailoverSpec
TestFailoverSpec is the request for one non-disruptive test-failover drill.
The source is named by where it runs and what it is, so the hub can find it without anyone extracting a backend handle by hand. SourceNamespace is required for a Volume drill, where the source is a PVC, and unused for a Group drill, where SourceRef names a consistency group.
Appears in: - TestFailover
Example:
scope: TestFailoverScope
sourceCluster: string
sourceNamespace: string
sourceRef: string
bubbleCluster: string
bubbleNamespace: string
ttlSeconds: integer
| Field | Description | Default | Validation |
|---|---|---|---|
scope TestFailoverScope |
Scope selects what the drill recovers. Immutable. | Enum: [Volume Group] Required: {} |
|
sourceCluster string |
SourceCluster is the OCM ManagedCluster the source runs on. The hub reads the source there through a ManagedClusterView. Immutable. |
Required: {} |
|
sourceNamespace string |
SourceNamespace is the namespace of the source PVC on SourceCluster. Required for scope=Volume. Immutable. |
Optional: {} |
|
sourceRef string |
SourceRef names the source on SourceCluster: a PersistentVolumeClaim in SourceNamespace (scope=Volume), or a consistency group (scope=Group). Immutable. |
Required: {} |
|
bubbleCluster string |
BubbleCluster is the OCM ManagedCluster to recover onto: a DR target holding the replicated point, or another cluster. It must differ from SourceCluster; test-failover recovers onto a different cluster, never in place. Immutable. |
Required: {} |
|
bubbleNamespace string |
BubbleNamespace is the namespace on the bubble cluster where the recovered PVCs are created. Immutable. |
bubble | Optional: {} |
ttlSeconds integer |
TTLSeconds is an optional maximum lifetime: the drill is torn down after it even without a delete, so a forgotten drill cannot hold a clone forever. |
Minimum: 0 Optional: {} |
TestFailoverStatus
TestFailoverStatus is the observed state of one drill.
Appears in: - TestFailover
Example:
phase: TestFailoverPhase
step: KubeSnapshot
message: string
triggered: boolean
observedGeneration: integer
clones:
- sourceRef: string
sourceHandle: string
snapshotID: string
cloneID: string
pvcName: string
sizeBytes: integer
sourceVolumeContext:
string: string
sourceFSType: string
sourceVolumeMode: string
report:
bubbleCluster: string
recoveryPoint: string
recoveryPointTime: Time
recoveryPointAgeSeconds: integer
invariantsHeld: boolean
startedAt: Time
readyAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase TestFailoverPhase |
Phase is the drill's own progress. | Enum: [Pending Provisioning Ready Failed TearingDown] Optional: {} |
|
step KubeSnapshot ⧉ |
Step is the position of the running drill's state machine. | Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the drill moves, and never a log. |
Optional: {} |
|
triggered boolean |
Triggered records that the current step's side effect was issued, so a restart does not repeat it. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the generation the rest of this status was computed from, so a stale status can be told from a current one. |
Optional: {} |
|
clones TestFailoverClone array |
Clones is one entry per recovered volume. | Optional: {} |
|
report TestFailoverReport |
Report is the drill's evidence, populated as it reaches Ready. | Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the drill started. | Optional: {} |
|
readyAt Time ⧉ |
ReadyAt is when every recovered PVC became bound. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when the drill reached a terminal phase. | Optional: {} |
ThroughputLimits
ThroughputLimits are throughput ceilings in megabytes per second. The unit is the field's rather than the value's, which is why the class keys these reach spell it out: a parameter map has no type to carry it.
Appears in: - PoolLimits - PoolLimitsStatus - VolumeDefaults
Example:
read: integer
write: integer
readWrite: integer
| Field | Description | Default | Validation |
|---|---|---|---|
read integer |
Read is the read-only ceiling, written as max_read_mbytes_per_sec. | Minimum: 0 Optional: {} |
|
write integer |
Write is the write-only ceiling, written as max_write_mbytes_per_sec. | Minimum: 0 Optional: {} |
|
readWrite integer |
ReadWrite is the ceiling on both directions together, written as max_mbytes_per_sec. It is not an access mode, which is the confusion the older class key qos_rw_mbytes invited. |
Minimum: 0 Optional: {} |
UpgradeSpec
UpgradeSpec parameterizes the Upgrade action and is ignored by the others.
Appears in: - ControlPlaneOpsSpec
Example:
image: '^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$'
| Field | Description | Default | Validation |
|---|---|---|---|
image string |
Image is the version to move to. It replaces ControlPlane.spec.source.managed.image when the operation succeeds, so the entity keeps describing what is running. |
Pattern: ^($\|(quay\.io/simplyblock-io\|docker\.io/simplyblock\|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]\{64\})?)$ Required: {} |
ValidationJob
ValidationJob is one Job started to check a path is reachable from one node.
It is tracked so that Verifying can delete every Job the operation started, including after a restart. The namespace is recorded for the same reason spec.migrate.targetNodeRef carries one: a Job is namespaced and this object is not, so a name alone would not locate it.
Appears in: - MigrationStatus
Example:
namespace: string
name: string
node: string
succeeded: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
namespace string |
Required: {} |
||
name string |
Required: {} |
||
node string |
Node is the worker the Job is pinned to, which is a node consuming one of the migrated subsystem's volumes. |
Optional: {} |
|
succeeded boolean |
Succeeded records a node whose paths were checked and found ready, so a restart does not run the check again on a node that already passed and whose Job its own TTL may already have reaped. |
Optional: {} |
VaultKMS
VaultKMS configures the HashiCorp Vault key store.
Appears in: - KMSSpec
Example:
endpoint: '^https?://[a-zA-Z0-9.-]+(:[0-9]{1,5})?(/.*)?$'
| Field | Description | Default | Validation |
|---|---|---|---|
endpoint string |
Endpoint is the Vault endpoint, for example, https://vault.example.com:8200. Rejected unless it resolves to an external address. |
Pattern: ^https?://[a-zA-Z0-9.-]+(:[0-9]\{1,5\})?(/.*)?$ Required: {} |
VolumeAutoPlacementSettings
VolumeAutoPlacementSettings configures automatic, latency-driven volume rebalancing. Whether it runs at all is StorageClusterSpec.EnableVolumeAutoPlacement.
Appears in: - StorageClusterSpec
Example:
disableMigration: boolean
evaluationInterval: Duration
imbalanceThreshold: integer
minHotColdDifferencePct: integer
defaultCoolDownSeconds: integer
maxVolumeMigrationsPerCycle: integer
storageNodeCandidateCount: integer
metricsBackend: MetricsBackend
prometheusURL: string
enableLatencyBenchmark: boolean
latencyBenchmarkInterval: Duration
baselineStrategy: BaselineStrategy
baselineWindow: Duration
baselineColdStart: BaselineColdStartPolicy
baselineMinSamples: integer
baselineOutlierK: float
iopsWeight: float
throughputWeight: float
| Field | Description | Default | Validation |
|---|---|---|---|
disableMigration boolean |
DisableMigration stops the rebalancer creating PersistentVolume migrations while leaving every other part of a cycle running: load is evaluated, deviations are computed, candidates are selected, and metrics are emitted, and the migrations are discarded rather than created. It is the dry run, and it is spelled as a disable because the behavior it governs is on by default. |
Optional: {} |
|
evaluationInterval Duration ⧉ |
EvaluationInterval is how often the rebalancer evaluates load. Defaults to 60s. |
Optional: {} |
|
imbalanceThreshold integer |
ImbalanceThreshold is the latency deviation from baseline, in percent, a node must exceed before it is considered a rebalancing source. Defaults to 80. |
Optional: {} |
|
minHotColdDifferencePct integer |
MinHotColdDifferencePct is how far below the hot source, in percentage points of deviation, a candidate target must be before a volume is moved. It is what stops a migration between two near-equally loaded nodes. Defaults to 20. |
Optional: {} |
|
defaultCoolDownSeconds integer |
DefaultCoolDownSeconds is how long a volume is left alone after it has been migrated. Defaults to 600. |
Optional: {} |
|
maxVolumeMigrationsPerCycle integer |
MaxVolumeMigrationsPerCycle caps how many volumes one cycle moves. Defaults to 10. |
Optional: {} |
|
storageNodeCandidateCount integer |
StorageNodeCandidateCount is how many of the most loaded nodes are evaluated each cycle to find the best source. Defaults to 3. |
Optional: {} |
|
metricsBackend MetricsBackend |
MetricsBackend selects the source of I/O metrics. Defaults to Prometheus. | Enum: [ControlPlane Prometheus Uniform] Optional: {} |
|
prometheusURL string |
PrometheusURL is required when MetricsBackend is Prometheus. | Optional: {} |
|
enableLatencyBenchmark boolean |
EnableLatencyBenchmark turns on fio-based NVMe-oF latency measurement through Kubernetes Jobs. It is off unless a RebalancerImage is configured. |
Optional: {} |
|
latencyBenchmarkInterval Duration ⧉ |
LatencyBenchmarkInterval is how often those Jobs run against each storage node. It also sets the step of the rolling-window baseline query, which is the cadence at which the probe sidecar publishes samples. Defaults to 5m. |
Optional: {} |
|
baselineStrategy BaselineStrategy |
BaselineStrategy selects how the per-node baseline is derived. Defaults to RollingWindow. |
Enum: [Benchmark RollingWindow] Optional: {} |
|
baselineWindow Duration ⧉ |
BaselineWindow is the look-back the RollingWindow strategy reduces. Defaults to 6h. |
Optional: {} |
|
baselineColdStart BaselineColdStartPolicy |
BaselineColdStart selects what happens to an under-sampled node. Defaults to PartialWindow. |
Enum: [Defer PartialWindow] Optional: {} |
|
baselineMinSamples integer |
BaselineMinSamples is the sample count below which a node counts as under-sampled. Defaults to 6. |
Optional: {} |
|
baselineOutlierK float |
BaselineOutlierK is the Hampel-identifier threshold: a sample is rejected when it lies more than k·1.4826·MAD from the window median, so a lower value rejects more. Defaults to 3.0. |
Optional: {} |
|
iopsWeight float |
IOPSWeight weights per-volume IOPS in the volume I/O score. Defaults to 1.0. |
Optional: {} |
|
throughputWeight float |
ThroughputWeight weights per-volume throughput, in MB/s, in the volume I/O score. Defaults to 0.1. |
Optional: {} |
VolumeDefaults
VolumeDefaults are the defaults every volume in the pool is created with. They are what a StorageClass assigned to this pool is expected to carry in its parameters, which is how they reach the CSI driver, and the whole block is immutable once set for that reason: StorageClass.parameters is immutable in the Kubernetes API, so a pool whose defaults changed would have a class the operator cannot update and a spec that no longer describes it.
Appears in: - StoragePoolSpec
Example:
iops: integer
throughput:
read: integer
write: integer
readWrite: integer
filesystem: string
enableCompression: boolean
enableClientCompression: boolean
enableClientDeduplication: boolean
enableReplication: boolean
enableDHCHAP: boolean
priorityClass: string
fabric: string
maxNamespacesPerSubsystem: integer
tune2fsReservedBlocks: string
| Field | Description | Default | Validation |
|---|---|---|---|
iops integer |
IOPS is each volume's ceiling on operations per second, both directions together. Zero is unlimited. It is written into the class as max_iops. |
Minimum: 0 Optional: {} |
|
throughput ThroughputLimits |
Throughput is each volume's throughput ceiling, in megabytes per second. | Optional: {} |
|
filesystem string |
Filesystem is what a volume is formatted with. The values are the kernel's names for filesystems, which is why they are not recased: this group did not invent the words. |
xfs | Enum: [ext4 xfs] Optional: {} |
enableCompression boolean |
EnableCompression compresses logical volumes. | Optional: {} |
|
enableClientCompression boolean |
EnableClientCompression compresses each volume on the node that consumes it (VDO), before a write ever reaches the wire, rather than on the storage node. Distinct from EnableCompression, and independent of EnableClientDeduplication: either, both, or neither may be set. A volume requesting this is pinned to a node whose kernel can run dm-vdo. |
Optional: {} |
|
enableClientDeduplication boolean |
EnableClientDeduplication deduplicates each volume on the node that consumes it (VDO), independent of EnableClientCompression. It carries a significant fixed RAM cost per volume for VDO's index, so it is meant for the pools where duplicate data is actually expected (VM images, container layers, backup targets) rather than enabled by default. |
Optional: {} |
|
enableReplication boolean |
EnableReplication replicates logical volumes. | Optional: {} |
|
enableDHCHAP boolean |
EnableDHCHAP authenticates NVMe-oF connections to this pool's volumes. Authentication is only enforced when AllowedNodes is non-empty, because the generated class gets its node selector from that list and a class's parameters cannot be edited afterward. |
Optional: {} |
|
priorityClass string |
PriorityClass is the logical-volume priority class the control plane places with. |
Optional: {} |
|
fabric string |
Fabric is the storage fabric a volume is served over, defaulting to the cluster's. |
Optional: {} |
|
maxNamespacesPerSubsystem integer |
MaxNamespacesPerSubsystem caps how many namespaces share one NVMe-oF subsystem. |
Minimum: 1 Optional: {} |
|
tune2fsReservedBlocks string |
Tune2fsReservedBlocks is the reserved-block percentage passed to tune2fs on an ext4 volume. Empty means the filesystem's own default, which is not the same as 0: the node plugin skips the call entirely when this isempty, and runs tune2fs -m 0 when it is 0. |
Optional: {} |
VolumeGroupSnapshotOps
VolumeGroupSnapshotOps is a one-shot operation on a VolumeGroupSnapshot, analogous to a Kubernetes Job: it drives its action to completion, records the result, and is then inert. The Restore action creates one PersistentVolumeClaim per member snapshot of the target's generation and waits for every claim to bind.
Example:
apiVersion: storage.simplyblock.io/v1alpha2
kind: VolumeGroupSnapshotOps
metadata:
name: string
spec:
volumeGroupSnapshotRef: string
action: VolumeGroupSnapshotOpsAction
restore:
namePrefix: string
storageClassName: string
consistencyGroup: string
enablePartialRestore: boolean
status:
phase: VolumeGroupSnapshotOpsPhase
step:
state: VolumeGroupSnapshotOpsStep
deadline: Time
message: string
observedGeneration: integer
membersExpected: integer
membersBound: integer
members:
- volumeSnapshotName: string
persistentVolumeClaimName: string
bound: boolean
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
apiVersion string |
storage.simplyblock.io/v1alpha2 |
||
kind string |
VolumeGroupSnapshotOps |
||
metadata ObjectMeta ⧉ |
Refer to Kubernetes API documentation for fields of metadata. |
||
spec VolumeGroupSnapshotOpsSpec |
|||
status VolumeGroupSnapshotOpsStatus |
VolumeGroupSnapshotOpsAction
Underlying type: string
VolumeGroupSnapshotOpsAction is the operation to perform on the target.
Validation: - Enum: [Restore]
Appears in: - VolumeGroupSnapshotOpsSpec
| Field | Description |
|---|---|
Restore |
VolumeGroupSnapshotOpsActionRestore restores every member of the target's generation into a new PersistentVolumeClaim. |
VolumeGroupSnapshotOpsPhase
Underlying type: string
VolumeGroupSnapshotOpsPhase is the lifecycle phase of the operation.
Validation: - Enum: [Pending Running Succeeded Failed]
Appears in: - VolumeGroupSnapshotOpsStatus
| Field | Description |
|---|---|
Pending |
|
Running |
|
Succeeded |
|
Failed |
VolumeGroupSnapshotOpsSpec
VolumeGroupSnapshotOpsSpec defines the requested operation. The whole spec is immutable: the object is a request.
Appears in: - VolumeGroupSnapshotOps
Example:
volumeGroupSnapshotRef: string
action: VolumeGroupSnapshotOpsAction
restore:
namePrefix: string
storageClassName: string
consistencyGroup: string
enablePartialRestore: boolean
| Field | Description | Default | Validation |
|---|---|---|---|
volumeGroupSnapshotRef string |
VolumeGroupSnapshotRef names the VolumeGroupSnapshot, in this namespace, the operation acts on. Resolved at admission: a create naming a VolumeGroupSnapshot that does not exist is rejected. |
Required: {} |
|
action VolumeGroupSnapshotOpsAction |
Action is the operation to perform. | Enum: [Restore] Required: {} |
|
restore RestoreOpsSpec |
Restore carries the parameters of the Restore action. Ignored for any other action. |
Optional: {} |
VolumeGroupSnapshotOpsStatus
VolumeGroupSnapshotOpsStatus holds the observed state of the operation.
Appears in: - VolumeGroupSnapshotOps
Example:
phase: VolumeGroupSnapshotOpsPhase
step:
state: VolumeGroupSnapshotOpsStep
deadline: Time
message: string
observedGeneration: integer
membersExpected: integer
membersBound: integer
members:
- volumeSnapshotName: string
persistentVolumeClaimName: string
bound: boolean
startedAt: Time
completedAt: Time
| Field | Description | Default | Validation |
|---|---|---|---|
phase VolumeGroupSnapshotOpsPhase |
Phase is the high-level lifecycle phase. | Enum: [Pending Running Succeeded Failed] Optional: {} |
|
step VolumeGroupSnapshotOpsStepSnapshot |
Step is the durable position of the action's state machine. | Optional: {} |
|
message string |
Message is the reason the phase is what it is: one sentence, replaced as the phase moves. |
Optional: {} |
|
observedGeneration integer |
ObservedGeneration is the spec generation this status was computed from. | Optional: {} |
|
membersExpected integer |
MembersExpected is the member count of the target generation. | Optional: {} |
|
membersBound integer |
MembersBound is how many restored claims have bound. | Optional: {} |
|
members RestoredMemberStatus array |
Members records each member snapshot and its restored claim. | Optional: {} |
|
startedAt Time ⧉ |
StartedAt is when the operation began. | Optional: {} |
|
completedAt Time ⧉ |
CompletedAt is when the operation finished, successfully or not. | Optional: {} |
VolumeGroupSnapshotOpsStep
Underlying type: string
VolumeGroupSnapshotOpsStep is one step of a running operation. The enum is the union of every action's steps. Which steps belong to which action is declared by the action's state-machine graph rather than by this type.
Validation: - Enum: [Validating CreatingClaims WaitingForBind]
Appears in: - VolumeGroupSnapshotOpsStepSnapshot
| Field | Description |
|---|---|
Validating |
|
CreatingClaims |
|
WaitingForBind |
VolumeGroupSnapshotOpsStepSnapshot
VolumeGroupSnapshotOpsStepSnapshot is the durable position of the action's state machine: the step and the deadline it expires at.
Appears in: - VolumeGroupSnapshotOpsStatus
Example:
state: VolumeGroupSnapshotOpsStep
deadline: Time
| Field | Description | Default | Validation |
|---|---|---|---|
state VolumeGroupSnapshotOpsStep |
Enum: [Validating CreatingClaims WaitingForBind] Optional: {} |
||
deadline Time ⧉ |
Optional: {} |
VolumeMigrationSettings
VolumeMigrationSettings controls how volume migration and the post-migration realignment behave, not whether they happen. It is separate from VolumeAutoPlacementSettings because realignment applies to every volume move, whether it came from the auto-rebalancer, a manual migration, or a drain, so it cannot sit under the rebalancing policy that is only one of its three sources.
There is no switch here. Migration cannot be turned off: a drain, a rebalance, and a device replacement are all performed by moving volumes.
Appears in: - StorageClusterSpec
Example:
rebalancerImage: string
dataRealignment:
interval: Duration
minMoves: integer
| Field | Description | Default | Validation |
|---|---|---|---|
rebalancerImage string |
RebalancerImage is the container image the migration path-validation Job and the rebalancer's latency and baseline Jobs run. It must carry nvme-cli, and for rebalancing also fio and jq. |
Optional: {} |
|
dataRealignment DataRealignmentSettings |
DataRealignment tunes the post-migration realignment. | Optional: {} |