Backup and Recovery
Simplyblock backs up volumes to Amazon S3 or S3-compatible object storage as chains of incremental snapshots. On
Kubernetes, the StorageCluster names the S3 location, a StorageBackupPolicy selects the claims to back up and sets
the schedule and the retention, and the control plane takes the copies. A single backup of one claim can also be
requested at any time by creating a StorageBackup, the way a VolumeSnapshot is created. Every copy found in the
store appears as a StorageBackup object, and a copy is restored into a new claim with a StorageBackupOps
operation.
| Kind | Short name | Written by | Purpose |
|---|---|---|---|
StorageCluster |
stc |
Administrator | spec.backup names the S3 location and its credentials. |
StorageBackupPolicy |
sbp |
Administrator | Schedule and retention for the claims its selector matches. |
StorageBackup |
sb |
Operator, or a user | A record of one backup found in the store, or a user's request for one backup. |
StorageBackupOps |
sbops |
Administrator or user | Restores one backup into a new claim. |
All four use the API version storage.simplyblock.io/v1alpha2. The policy and the backup records live in the namespace
of the StorageCluster. A backup request lives in the namespace of its claim, and so does a restore.
Backups and disaster recovery
The backup bucket is part of the storage cluster and is configured when the cluster is deployed. Simplyblock Disaster Recovery does not use it: the backup method types of a protection plan back volumes up with the same mechanism into the DR metadata store of the site. See Backup and Restore.
Prerequisites
S3-Compatible Object Storage
Backups require an S3-compatible object storage endpoint and a bucket. For local testing, a MinIO instance can be deployed:
kubectl create ns minio
kubectl -n minio create deployment minio \
--image=minio/minio \
-- /bin/sh -c "minio server /data --console-address :9001"
kubectl -n minio expose deploy/minio --port 9000
kubectl -n minio set env deploy/minio \
MINIO_ROOT_USER=minioadmin \
MINIO_ROOT_PASSWORD=minioadmin123
Backup Credentials Secret
The S3 credentials are stored in a Secret in the namespace of the StorageCluster, under the keys access_key_id and
secret_access_key:
apiVersion: v1
kind: Secret
metadata:
name: production-backup
namespace: simplyblock
type: Opaque
stringData:
access_key_id: <ACCESS_KEY>
secret_access_key: <SECRET_KEY>
Backup Store of the StorageCluster
The backup store is set in StorageCluster.spec.backup. The block is mutable, so a cluster created without a store can
be given one later.
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageCluster
metadata:
name: production
namespace: simplyblock
spec:
# ... other fields ...
backup:
endpoint: http://minio.minio.svc.cluster.local:9000
bucket: simplyblock-backups
prefix: production/
credentialsSecretRef:
name: production-backup
| Field | Required | Description |
|---|---|---|
endpoint |
yes | S3 endpoint URL, for example, https://s3.example.com. Loopback and link-local addresses are refused. |
bucket |
yes | Bucket the backups are written to and read from. |
prefix |
no | Key prefix inside the bucket, so that several clusters can share a bucket. |
region |
no | Region of the bucket, for endpoints that do not imply one. |
credentialsSecretRef.name |
yes | Secret in the same namespace with the keys access_key_id and secret_access_key. |
The store is both the target copies are written to and the inventory the operator reads. Setting it makes every backup
already under that bucket and prefix visible as a StorageBackup object, including backups written by another
cluster that shares the bucket and prefix.
Backup Policies
A StorageBackupPolicy defines which claims are backed up, how often, and how long the copies are kept. The control
plane runs the policy, so no Kubernetes CronJob or trigger is involved.
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackupPolicy
metadata:
name: nightly
namespace: simplyblock
spec:
clusterRef: production
claimSelector:
matchLabels:
backup: nightly
schedule: "15m,4 60m,11 24h,7"
maxVersions: 22
maxAge: 30d
kubectl apply -f backup-policy.yaml
| Field | Mutability | Description |
|---|---|---|
clusterRef |
immutable | Name of the StorageCluster in the same namespace. Required. |
claimSelector |
mutable | Label selector for the claims to back up. An absent selector selects nothing, and {} selects every claim. |
schedule |
immutable | Tiered schedule as space-separated interval,count pairs, with units m, h, d, and w. |
maxVersions |
immutable | Number of backups kept per claim. 0 means no limit by count. |
maxAge |
immutable | Age after which a backup is no longer kept, for example, 30d or 720h. Empty means no limit by age. |
The schedule 15m,4 60m,11 24h,7 takes a backup every 15 minutes (keeping the 4 most recent), every 60 minutes (keeping
11), and every 24 hours (keeping 7). The intervals have to be strictly increasing. Retention is enforced by the control
plane: the oldest backup is merged into the next one, so the number of restore points shrinks while the backup chain
stays complete.
Note
schedule, maxVersions, and maxAge cannot be changed on an existing policy. A different schedule or retention
requires a new policy, and the old one is deleted afterward.
Selecting the Claims
A claim is covered by a policy when its labels match the policy's claimSelector. Labeling a claim attaches it, and
removing the label detaches it. Existing backups are kept in both cases.
kubectl label pvc my-pvc -n simplyblock backup=nightly
kubectl label pvc my-pvc -n simplyblock backup-
A policy only selects claims in its own namespace, which is the namespace of the StorageCluster. The backups it takes
are recorded in that namespace too. An absent selector is reported with a SelectorEmpty event on the policy.
The attached claims are listed in status.attachedClaims, and status.lastBackupAt holds the time of the most recent
backup.
kubectl get storagebackuppolicy -n simplyblock
NAME CLUSTER PHASE SCHEDULE CLAIMS LASTBACKUP AGE
nightly production Active 15m,4 60m,11 24h,7 3 4m 2d
Requesting a Backup
A single backup of one claim is requested by creating a StorageBackup with spec.source.claimName, the way a
VolumeSnapshot is created. No policy is involved, so a claim can be backed up before an upgrade without giving it a
schedule.
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackup
metadata:
name: my-pvc-before-upgrade
namespace: app
spec:
source:
claimName: my-pvc
kubectl apply -f backup-request.yaml
Create the request in the namespace of the claim. The claim must be bound to a simplyblock volume, and the cluster
that provisioned the volume must have spec.backup set. The operator finds the cluster from the volume, so a request
has no clusterRef.
| Field | Description |
|---|---|
source.claimName |
Name of the claim to back up, in the namespace of the request. Required, immutable. |
A StorageBackup sets exactly one of spec.source (a request) and spec.clusterRef (a record written by the
operator). The API server rejects an object that sets both or neither.
The operator snapshots the claim's volume and asks the control plane to back the snapshot up. status.backup.backupID
is filled in once the control plane assigns the ID. While the snapshot is being taken, status.phase stays empty.
kubectl get storagebackup my-pvc-before-upgrade -n app -w
| Phase | Meaning |
|---|---|
Pending |
Waiting for something that may still appear. See the list below. |
Creating |
The control plane is writing the copy. |
Available |
The copy is complete and can be restored. |
Failed |
The claim is not on a simplyblock volume, the control plane refused the request, or the backup failed. |
A request stays Pending while
- the claim does not exist or is not bound yet,
- no
StorageClusterreports the cluster that provisioned the volume, - that cluster has no
spec.backup, or - the operator has not yet received the current snapshot and backup state of the cluster from the control plane.
status.message gives the reason, and a Pending request emits a BackupRequestBlocked event. A request that is
Available or Failed is not retried. To take another backup, create another request.
The request is the only object for its copy. Deleting the request leaves the copy in the store, and the operator then
records the copy as a regular StorageBackup in the namespace of the cluster. If the store stops reporting the copy,
the request is marked Failed and is not deleted.
A request records the pool by its UUID only, so the Pool column of kubectl get sb -o wide stays empty for it.
Requests live in the namespaces of their claims, and the backups of every namespace are listed with
kubectl get sb -A.
Note
Creating and deleting a request needs the create and delete verbs on storagebackups in the claim's namespace.
A record is still refused for every identity but the operator.
Listing Backups
Every backup in the store is mirrored as one StorageBackup object, named after its backup ID. Backups taken by a
policy are always recorded in the namespace of the StorageCluster, whatever namespace the claim is in. A record is an
observation, and spec.clusterRef names the cluster whose store holds the copy. A webhook refuses to create or delete a
record for anyone but the operator, and deleting a record never deletes the copy in the bucket.
The webhook does not block backup requests. An object with spec.source is a request, not a record, and a user can
create and delete it. See Requesting a Backup.
kubectl get storagebackup -n simplyblock
NAME CLAIM PHASE SIZE COMPLETED AGE
7fab02f8-03f6-4e76-a9ac-78b63b1ce8ef my-pvc Available 1073741824 3m 3m
status.phase is one of Pending, Creating, Available, or Failed. The backup ID is in
status.backup.backupID. status.source records where the copy came from (the claim, the PersistentVolume, the
pool, the logical volume, the snapshot, and the filesystem type), and status.backup records the copy itself (its
ID, size, the previous backup of the chain, and the timestamps). The objects carry the labels storage.simplyblock.io/cluster, storage.simplyblock.io/claim, and
storage.simplyblock.io/backup-policy, which allows the backups of one claim to be selected:
kubectl get sb -n simplyblock -l storage.simplyblock.io/claim=my-pvc
Note
The first backup of a claim may take longer to complete, as there is no prior incremental state.
Restoring a Backup
A backup is restored into a new claim by a StorageBackupOps operation with action: Restore. The backup can be a
record or a request. The claim named in restore.claimName must not exist yet, because a restore creates a claim and
never replaces the data of an existing one. The target pool is required, since a backup found in a shared store may
come from a pool this cluster does not have.
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackupOps
metadata:
name: restore-my-pvc
namespace: simplyblock
spec:
clusterRef:
name: production
backupRef:
name: 7fab02f8-03f6-4e76-a9ac-78b63b1ce8ef
action: Restore
restore:
claimName: restored-pvc
targetPool: production-default
claimLabels:
backup: nightly
kubectl apply -f restore.yaml
| Field | Description |
|---|---|
clusterRef.name |
Name of the StorageCluster. Required. |
clusterRef.namespace |
Namespace of the StorageCluster. Defaults to the namespace of the operation. |
backupRef.name |
Name of the StorageBackup to restore, a record or a request. Required. |
backupRef.namespace |
Namespace of the StorageBackup. Defaults to the namespace of the operation. |
action |
Restore, the only action. Required. |
abort |
Stops the restore, as long as no logical volume has been created yet. |
restore.claimName |
Name of the claim to create. Required, and must not exist. |
restore.targetPool |
StoragePool to restore into, read from the namespace of the cluster. Required. |
restore.claimLabels |
Labels applied to the created claim, for example, to attach it to a backup policy. |
restore.claimAnnotations |
Annotations applied to the created claim. |
clusterRef and backupRef are objects. The API server rejects a string such as backupRef: 7fab02f8-....
Restoring Across Namespaces
The claim is always created in the namespace of the operation. The backup and the cluster can be in other namespaces, so a team can restore into its own namespace by creating the operation there and naming them with their namespaces:
apiVersion: storage.simplyblock.io/v1alpha2
kind: StorageBackupOps
metadata:
name: restore-my-pvc
namespace: app
spec:
clusterRef:
name: production
namespace: simplyblock
backupRef:
name: 7fab02f8-03f6-4e76-a9ac-78b63b1ce8ef
namespace: simplyblock
action: Restore
restore:
claimName: restored-pvc
targetPool: production-default
The operator reads these objects with its own permissions, so the webhook checks the requesting user first. Each
reference outside the namespace of the operation needs the get verb there:
- A
backupRefin another namespace needsgetonstoragebackupsin that namespace. - A
clusterRefin another namespace needsgetonstorageclustersand onstoragepoolsin that namespace, since the target pool is read from there.
References inside the operation's namespace need no extra permission.
The restored claim is created in the namespace of the operation, with access mode ReadWriteOnce and the size of the
backup. Its StorageClass is the default class of the target pool, or otherwise the first StorageClass assigned to the
pool (see Storage Class). The volume is mounted with the filesystem type
recorded in the backup. The claim carries the label storage.simplyblock.io/restored-by and is not owned by the
operation, so deleting the StorageBackupOps leaves the restored data in place.
kubectl get storagebackupops -n simplyblock -w
NAME BACKUP ACTION PHASE STEP AGE
restore-my-pvc 7fab02f8-03f6-4e76-a9ac-78b63b1ce8ef Restore Succeeded 79s
While the phase is Running, status.step.state moves through Validating, Restoring, AwaitingVolume, and
Binding. Once the phase is Succeeded, the claim is bound and can be attached to a pod. Two restores of the same
backup run one after the other, including restores created in different namespaces.
Restoring a Volume Group
Backups cover single claims. A crash-consistent restore of several claims from one VolumeGroupSnapshot is performed
by a VolumeGroupSnapshotOps operation, see
Restoring a Volume Group Snapshot.
Control-Plane Backups
The kinds on this page protect volume data. The control-plane database itself is backed up separately, see FoundationDB Backup and Restore. For application-level protection across sites, see Disaster Recovery.