Expanding a Storage Cluster
A storage cluster is expanded while it serves I/O, so no maintenance window is required. New workers are described in
a growth ClusterDeploymentConfig, a deployment document that names the existing cluster in spec.clusterRef instead
of describing a new one. Once the document is approved, the Simplyblock Operator creates one StorageNode per worker
and slot, and every new node is integrated by the control plane on its own. The integration is followed by a
rebalancing that moves data onto the new devices. The rebalancing runs at low priority, but it is still work on the
data path, so an expansion is best started while the cluster is not fully utilized.
How an Expansion Runs
Every storage node a growth document creates carries spec.config.expand: true. The flag reaches the control plane
with the node addition and selects the expansion path, in which the cluster keeps the status active and one
expansion task is queued for the new node.
That task re-wires the role rotation of the cluster. The secondary logical volume store moves to the direct successor
of the new node, and the tertiary one to the second node in line. Both donors have to be torn down and rebuilt for it,
which is why the cluster reports in_expansion, and the StorageCluster the phase Provisioning, while the rotation
is rebuilt. It returns to active afterward. Only then does the expansion migration start and move data onto the
devices of the new node.
One expansion runs per cluster at a time. Several workers may be listed in the same document: they are integrated one after another, since the control plane refuses the addition of the next worker while an expansion is open, and the operator retries it.
Preconditions
The role rotation is only safe on a quiescent and fully redundant cluster, so the control plane refuses an expansion unless
- the cluster is
activeand every storage node isonline, - no data migration, logical volume migration, node restart, or backup task is open anywhere in the cluster, where open means anything that has not finished, and
- no deletion is in flight on the two donor nodes.
The conditions are checked twice: cluster-wide when the node is added, and again with the donors of the planned role moves right before the rotation is executed.
Drafting the Growth Document
A growth document is either drafted by a discovery run or written by hand.
With a Discovery Run
An OperatorOps with the action Discover probes the workers and writes a draft document. With
spec.discover.clusterRef, the draft grows the named cluster instead of describing a new one. A run that names no
workers inspects every schedulable worker that does not already run a storage node.
apiVersion: storage.simplyblock.io/v1alpha2
kind: OperatorOps
metadata:
name: discover-rack-c
namespace: simplyblock
spec:
action: Discover
discover:
configName: grow-rack-c
clusterRef: simplyblock-cluster
workers:
- worker-7.example.com
- worker-8.example.com
kubectl apply -f discover-growth.yaml
kubectl get operatorops discover-rack-c -n simplyblock -w
kubectl get operatorops discover-rack-c -n simplyblock -o jsonpath='{.status.configRef}{"\n"}'
The run walks the steps Inspecting, Probing, and Writing, and it names the document it wrote in
status.configRef. Without configName, the draft is named after the run. The device filters of a discovery run are
described in Create a Storage Cluster.
By Hand
apiVersion: storage.simplyblock.io/v1alpha2
kind: ClusterDeploymentConfig
metadata:
name: grow-rack-c
namespace: simplyblock
spec:
approved: false
clusterRef: simplyblock-cluster
nodeSets:
- name: rack-c
groups:
- name: default
workers:
- worker-7.example.com
- worker-8.example.com
devices:
nvme: ["0000:01:00.0", "0000:02:00.0"]
A growth document carries no spec.cluster block. The cluster-wide settings, such as the sizing, the erasure coding
scheme, and the storage-node workload, are those of the existing StorageCluster. Every group of the document uses the
same device class as the cluster.
spec.cluster and spec.clusterRef decide between them what the expansion does, and a mismatch is refused rather
than reconciled:
spec.cluster.name resolves to |
spec.clusterRef |
Expansion does |
|---|---|---|
No existing StorageCluster |
absent | Creates the cluster and all its nodes |
An existing StorageCluster |
absent | Refuses: ClusterExists, phase Failed |
An existing StorageCluster |
set to it | Adds only the nodes that do not exist yet |
No existing StorageCluster |
set | Refuses: ClusterNotFound, phase Failed |
A document never removes anything. A node set left out of a later document does not drain a node, and a device
removed from a group does not shrink one. Removal belongs to StorageNodeOps with action: Remove, where it is
deliberate, audited, and drains first.
On a cluster with enableFailureDomains: true, every group also sets failureDomain, otherwise the new nodes are held
with a FailureDomainMissing event. See Managing Failure Domains for the assignment
and the balance rules.
Approving the Expansion
A draft is inert until it is approved. The validation findings of the document, for example, a device that was not
found on a worker, are reported in status.message, and they are reviewed before the approval.
kubectl get clusterdeploymentconfig grow-rack-c -n simplyblock -o yaml
kubectl patch clusterdeploymentconfig grow-rack-c -n simplyblock \
--type=merge -p '{"spec": {"approved": true}}'
The approving edit is validated against the live cluster, and the document is immutable once it is approved. The approval cannot be withdrawn.
The document then walks the steps below and reports the phase Expanding, then Expanded.
| Step | Description |
|---|---|
Validating |
The document is checked against the cluster and the workers. |
CreatingCluster |
Nothing is created for a growth document. The existing cluster is recorded in status.clusterRef. |
AwaitingCluster |
The existing cluster is confirmed. |
CreatingNodes |
One StorageNode per worker and slot is created, with spec.config.expand: true, and recorded in status.nodeRefs. |
Activating |
The step waits until every node the document created is Online. |
Is an Expand Operation Needed?
No, not for nodes added by a growth document. The operator does not raise an Expand operation after a growth
document, and none is needed: the nodes carry the expansion flag, so the control plane integrates each of them on its
own. In the Activating step, the document raises an Activate operation named <cluster>-activate, or reuses the
one of the initial deployment if it still exists. An activation of a cluster that is already active succeeds without
a call to the control plane.
A StorageClusterOps with the action Expand is needed only for nodes that were added without the flag, for example,
a hand-written StorageNode without spec.config.expand. Such a node follows the older path: the cluster is set to
in_expansion when the node is registered, and the expansion is finalized with the operation once all new nodes are
online.
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageClusterOps
metadata:
name: expand-simplyblock-cluster
namespace: simplyblock
spec:
clusterRef: simplyblock-cluster
action: Expand
This path integrates all pending nodes at once, so it needs at least two new nodes, and at least three on a cluster
with dual fault tolerance (FTT 2), to build the failover paths of every one of them. The operation runs the steps
Requesting and Awaiting, and it succeeds once the cluster is active. The other actions are described in
Storage Cluster Actions.
Following the Expansion
kubectl get clusterdeploymentconfig grow-rack-c -n simplyblock -w
kubectl get storagenodes -n simplyblock -l storage.simplyblock.io/cluster=simplyblock-cluster -w
A new node walks its own provisioning steps, CheckingHost, CheckingConfig, AwaitingSlot, Posting, and
Resolving, before it reaches the phase Online. How many workers are added at the same time is described in
Parallel Storage Node Addition.
The cluster reports the rotation and the rebalancing that follow it. The status returns to active once the rotation
is rebuilt, and rebalancing turns to false once the data has been moved.
kubectl get storagecluster simplyblock-cluster -n simplyblock \
-o jsonpath='{.status.phase}{"\t"}{.status.status}{"\t"}{.status.rebalancing}{"\n"}' -w
Once the expansion is complete, the growth document can be deleted. The storage nodes it created belong to the
StorageCluster and are kept.