Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/kubernetes/installation/k8s-storage-plane.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ for the full parameter mapping.
A StorageClass's parameters cannot be changed after creation, so `spec.storageClassParameters` is immutable
once the storage pool is created. A new storage pool is required to provision volumes with different defaults.

!!! warning "Pool limits are read once"
`capacityLimit`, `logicalVolumeMaxSize`, and `qos` are sent to the control plane when the pool is created and are
not reconciled afterward. Patching one of them on an existing `StoragePool` is accepted by the API server and has
no effect on the pool, so a different capacity limit or a different set of QoS limits requires a new storage pool.
`allowedNodes` is the exception and is reconciled, see
[Host Authentication and Encryption](../operations/security/authentication-encryption.md#configuring-dhchap-via-the-storagepool-crd).

The StorageClass is automatically removed when the storage pool is deleted. Full details and customization
options are available at [Simplyblock Operator: Storage Pool](../../reference/operator/reference.md#storagepool).

Expand Down
176 changes: 176 additions & 0 deletions docs/kubernetes/operations/cluster-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
title: "Storage Cluster Actions"
description: "Trigger cluster-wide lifecycle operations on a simplyblock storage cluster through the action field of the StorageCluster resource and track their outcome."
weight: 10700
---

Cluster-wide lifecycle operations are requested declaratively on Kubernetes. Setting `spec.action` on a
`StorageCluster` resource makes the Simplyblock Operator call the corresponding backend API, poll until the cluster
reaches the expected state, and record the outcome in `status.actionStatus`. The CLI is not involved.

Only one action can be requested at a time, since `spec.action` holds a single value.

## Requesting an Action

An action is requested by patching the field. The example below shuts the cluster down.

```bash title="Requesting a cluster action"
kubectl patch storagecluster simplyblock-cluster -n simplyblock \
--type=merge -p '{"spec": {"action": "shutdown"}}'
```

| Action | Effect | Expected cluster status |
|----------------|------------------------------------------------------------------------|-------------------------|
| `activate` | Activates a cluster whose nodes have joined but which is not yet live. | `active` |
| `expand` | Finalizes a cluster expansion after new storage nodes came online. | `active` |
| `shutdown` | Shuts the whole cluster down. | `suspended` |
| `start` | Starts a previously shut down cluster. | `active` |
| `restart` | Runs a shutdown followed by a start. | `active` |
| `node-recycle` | Restarts every storage node of the cluster, one after another. | `active` |

Any other value is rejected by the CRD schema. The `node-recycle` action has its own page, see
[Rolling Restart](rolling-restart.md).

## How an Action Is Executed

Every action follows the same pattern. The operator records the action in `status.actionStatus` with the state
`running`, sends the backend request once, and then polls the cluster until the expected status is reached. The first
poll follows five seconds after the request, and further polls follow every ten seconds.

The `status.actionStatus.triggered` flag marks that the request has already been sent, so a requeue or an operator
restart never sends it twice. A failed request moves the state to `failed` and writes the reason into
`status.actionStatus.message`. A failed action is not retried automatically.

| Field | Description |
|----------------------|--------------------------------------------------------------------------|
| `action` | The action this status belongs to. |
| `state` | `running` while the action is in progress, then `success` or `failed`. |
| `message` | The result, the failure reason, or the current sub-phase of a `restart`. |
| `triggered` | Whether the backend request has already been sent. |
| `observedGeneration` | The `metadata.generation` of the spec this action was started for. |
| `updatedAt` | The time of the last status transition. |

## Re-Running and Clearing an Action

An action counts as complete when its state is `success` **and** its `observedGeneration` matches the current
`metadata.generation` of the resource. Patching `spec.action` with the value it already holds does not change the
generation, so nothing happens. Re-running the same action therefore takes two patches: the field is cleared first
and set again afterward.

```bash title="Re-running the same action"
kubectl patch storagecluster simplyblock-cluster -n simplyblock \
--type=merge -p '{"spec": {"action": ""}}'
kubectl patch storagecluster simplyblock-cluster -n simplyblock \
--type=merge -p '{"spec": {"action": "restart"}}'
```

!!! important
While `spec.action` holds a value, the reconciler serves the action instead of its periodic status sync. Once the
action has succeeded, nothing further happens to the resource, and the remaining `status` fields are no longer
refreshed from the backend. Clearing `spec.action` after a completed action returns the cluster to normal status
reconciliation.

## Shutdown

A shutdown suspends the entire cluster. The operator calls the backend shutdown API and polls until the cluster
reports `suspended`.

```bash title="Shutting down the storage cluster"
kubectl patch storagecluster simplyblock-cluster -n simplyblock \
--type=merge -p '{"spec": {"action": "shutdown"}}'
```

!!! warning
A cluster shutdown takes every volume of the cluster offline. Workloads consuming those volumes lose their storage
for the duration of the shutdown. To take a single storage node out of service instead, see
[Storage Node Actions](storage-node-actions.md).

## Start

A start brings a suspended cluster back. The operator calls the backend start API and polls until the cluster reports
`active`. The rebalancing flag reported by the backend is recorded in `status.rebalancing` once the cluster is up.

```bash title="Starting a suspended storage cluster"
kubectl patch storagecluster simplyblock-cluster -n simplyblock \
--type=merge -p '{"spec": {"action": "start"}}'
```

## Restart

A restart sequences a shutdown and a start. Both legs are driven by the same action, and the leg currently running is
held in `status.actionStatus.message` as either `shutdown` or `start`. The action succeeds once the cluster is `active`
again.

```bash title="Restarting the storage cluster"
kubectl patch storagecluster simplyblock-cluster -n simplyblock \
--type=merge -p '{"spec": {"action": "restart"}}'
```

```bash title="Following the leg of a running restart"
kubectl get storagecluster simplyblock-cluster -n simplyblock \
-o jsonpath='{.status.actionStatus.message}{"\n"}'
```

## Activate

Activation is normally automatic. The operator activates a cluster on its own once every storage node declared in its
`StorageNodeSet` is online and healthy, and the number of those nodes is at least the sum of the data chunks, the
parity chunks, and one. See
[Create a Storage Cluster](../installation/k8s-storage-plane.md#when-does-the-cluster-become-active).

The `activate` action exists for the case where that did not happen, for example, because nodes came online after the
automatic check had already passed.

```bash title="Activating a cluster manually"
kubectl patch storagecluster simplyblock-cluster -n simplyblock \
--type=merge -p '{"spec": {"action": "activate"}}'
```

!!! warning
A `StorageCluster` that is deleted while `spec.action` is `activate` has its finalizer removed without the backend
cluster being deleted. The cluster is then left behind on the control plane and has to be removed there. Clear
`spec.action` before deleting the resource.

## Expand

An expansion is finalized with the `expand` action, after the new storage nodes have been added and are online. The
operator calls the backend expand API and polls until the cluster returns to `active`.

```bash title="Finalizing a cluster expansion"
kubectl patch storagecluster simplyblock-cluster -n simplyblock \
--type=merge -p '{"spec": {"action": "expand"}}'
```

Adding the storage nodes themselves is the step before this one, described in
[Expanding a Storage Cluster](scaling/expanding-storage-cluster.md).

## Monitoring an Action

The action state is exposed in the resource status.

```bash title="Reading the current action status"
kubectl get storagecluster simplyblock-cluster -n simplyblock \
-o jsonpath='{.status.actionStatus}' | jq .
```

```plain title="Example output of a running action"
{
"action": "restart",
"state": "running",
"message": "start",
"observedGeneration": 7,
"triggered": true,
"updatedAt": "2026-08-22T09:14:03Z"
}
```

The backend lifecycle status of the cluster is tracked separately from the action.

```bash title="Reading the backend cluster status"
kubectl get storagecluster simplyblock-cluster -n simplyblock \
-o jsonpath='{.status.status}{"\n"}'
```

```bash title="Streaming live cluster status changes"
kubectl get storagecluster simplyblock-cluster -n simplyblock -w
```
165 changes: 165 additions & 0 deletions docs/kubernetes/operations/cluster-upgrade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
---
title: "Upgrading a Cluster"
description: "Upgrade the simplyblock operator, control plane, and CSI driver with Helm, then roll the new storage-node image across the storage plane one node at a time."
weight: 10610
---

A simplyblock deployment on Kubernetes upgrades in two parts. The control plane, the operator, and the CSI driver come
from the Helm chart and move together with a chart upgrade. The storage plane runs from container images referenced by
the operator resources, and it is rolled node by node afterward.

The two parts can be upgraded independently, but a control plane that is newer than its storage planes is the only
combination that is supported during the transition. The control plane is therefore upgraded first, and a control
plane that manages several storage clusters is upgraded before any of them.

## Upgrade Order

1. Upgrade the Helm release, which covers the operator, the control plane, and the CSI driver.
2. Wait for the control plane to report itself ready again.
3. Roll the storage-node image across each storage cluster.

## Upgrading the Control Plane

The control plane, the operator, and the CSI driver are all rendered by the same chart, so one upgrade moves them.

```bash title="Upgrading the Helm release"
helm repo update
helm upgrade --install simplyblock -n simplyblock simplyblock/simplyblock-operator \
--reuse-values
```

`--reuse-values` keeps the values the release was installed with. Without it, every value that was set at install time
falls back to the chart default, which silently reverts settings such as the TLS configuration.

!!! warning
A chart upgrade re-renders every object the chart owns, which discards manual edits to them. A patch that has to
survive an upgrade is reapplied afterward, for example, the credentials mount described in
[FoundationDB Backup and Restore](foundationdb-backup.md).

### Confirming the Control Plane Is Ready

The `ControlPlane` resource is a singleton named `simplyblock`, created by the chart. Its phase is driven by the
readiness endpoint of the management API, which the operator polls every 30 seconds.

```bash title="Checking the control plane phase"
kubectl get controlplane simplyblock -n simplyblock
```

```plain title="Example output of the control plane status"
NAME PHASE MESSAGE AGE
simplyblock Ready 14d
```

A phase of `Initializing` means the health check is still failing, and the `MESSAGE` column carries the reason. The
storage plane is not touched until the phase is `Ready`.

```bash title="Waiting for the control plane to become ready"
kubectl wait --for=jsonpath='{.status.phase}'=Ready \
controlplane/simplyblock -n simplyblock --timeout=10m
```

## Upgrading the Storage Plane

Which image a storage node runs is decided by three fields. All of them accept only the trusted simplyblock
registries, and pinning by digest is recommended.

| Field | Applies to | Default |
|-----------------------|-------------------------------------------------------|---------------------------|
| `spec.clusterImage` | The storage-node pod of the `StorageNodeSet`. | `ControlPlane.spec.image` |
| `spec.spdkImage` | The SPDK image, sent with the node-add request. | The control plane default |
| `spec.spdkProxyImage` | The SPDK proxy image, sent with the node-add request. | The control plane default |

A `StorageNodeSet` that leaves `spec.clusterImage` empty inherits the image from the `ControlPlane` resource, which the
chart keeps up to date. On such a set the chart upgrade already changed the image, and the DaemonSet rolls its pods as
a consequence.

A `StorageNodeSet` that pins `spec.clusterImage` does not follow the chart. Its image is raised explicitly.

```bash title="Pinning a new storage-node image on a StorageNodeSet"
kubectl patch storagenodeset simplyblock-node -n simplyblock --type=merge \
-p '{"spec": {"clusterImage": "quay.io/simplyblock-io/simplyblock:26.3.0"}}'
```

`spec.spdkImage` and `spec.spdkProxyImage` are read when a storage node is added, so a change to them governs nodes
added from that point on.

```bash title="Reading the images a StorageNodeSet is configured with"
kubectl get storagenodeset simplyblock-node -n simplyblock \
-o jsonpath='{.spec.clusterImage}{"\n"}{.spec.spdkImage}{"\n"}{.spec.spdkProxyImage}{"\n"}'
```

### Rolling the Change Across the Nodes

A new image does not reach a running storage node on its own. The node has to be restarted, and the storage-node pod
has to be replaced so that it picks the image up rather than keeping the one it started with.

Both happen in a [Rolling Restart](rolling-restart.md) with the pod refresh enabled. One node at a time is shut down,
its pod is replaced, the node is restarted, and the cluster rebalances before the next node follows.

```bash title="Rolling the new image across the storage nodes"
kubectl patch storagecluster simplyblock-cluster -n simplyblock --type=merge \
-p '{"spec": {"action": "node-recycle", "nodeRecycle": {"refreshSNodeAPI": true}}}'
```

```bash title="Following the rollout"
kubectl get storagecluster simplyblock-cluster -n simplyblock \
-o jsonpath='{.status.nodeRecycleStatus}' | jq .
```

The rollout is complete when `status.actionStatus.state` is `success`. The action field is then cleared, so that the
cluster returns to normal status reconciliation, as described in
[Storage Cluster Actions](cluster-actions.md#re-running-and-clearing-an-action).

```bash title="Clearing the action after the rollout"
kubectl patch storagecluster simplyblock-cluster -n simplyblock \
--type=merge -p '{"spec": {"action": ""}}'
```

### Upgrading a Subset of Nodes First

A new image can be tried on a few nodes before the whole fleet follows. The per-node configuration of a
`StorageNodeSet` overrides the fleet image for the workers named in it.

```yaml title="Example of a phased rollout to two workers"
spec:
nodeConfigs:
worker-1.example.com:
spdkImage: quay.io/simplyblock-io/spdk:26.3.0
worker-2.example.com:
spdkImage: quay.io/simplyblock-io/spdk:26.3.0
```

The overrides are propagated to the `StorageNode` resources of those workers on the next reconcile. Once the sample
has proven itself, the fleet field is raised and the overrides are removed again.

## Verifying the Result

The storage nodes are online and healthy after a rollout, and the cluster is no longer rebalancing.

```bash title="Checking the storage nodes after an upgrade"
kubectl get storagenodes -n simplyblock
```

```bash title="Checking that the cluster settled"
kubectl get storagecluster simplyblock-cluster -n simplyblock \
-o jsonpath='{.status.status}{" rebalancing="}{.status.rebalancing}{"\n"}'
```

```bash title="Checking the running storage-node pods"
kubectl get pods -n simplyblock -l app=storage-node \
-o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,IMAGE:.spec.containers[0].image
```

## Rolling Back

A storage-plane image is rolled back the way it was rolled forward: the field is set to the previous reference and the
nodes are recycled again. A Helm release is rolled back with `helm rollback`, which restores the previous chart
version together with the values it was rendered from.

```bash title="Rolling the Helm release back to the previous revision"
helm rollback simplyblock -n simplyblock
```

!!! important
A rollback of the control plane below the version of a storage plane leaves the deployment in the one combination
that is not supported. The storage planes are rolled back first, and the control plane after them.
Loading
Loading