Every backup walkthrough carried its own wait_for_field, and most their own wait_hr_ready and wait_deleted: twelve, eight and two copies, which had already drifted into five, three and two bodies. A fix made in one copy did not reach the others, and a diagnostics change carried by hand between two of them once went into the wrong function and was caught only by reading the diff. The helpers now live in examples/backups/_lib/wait-helpers.sh, sourced by each walkthrough's 00-helpers.sh. Where copies differed, the shared body keeps the diagnostics any of them had: wait_for_field leaves kubectl's stderr attached, and wait_hr_ready prints the conditions and history dump on Stalled or timeout. Call sites and budgets are unchanged. A new unit test fails when any walkthrough defines one of these helpers again, which replaces the byte-identity pin between copies. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
Etcd backup/restore example
Heads up — most clusters do not need this walk-through. Cozystack ships a platform-managed
cozy-defaultBackupClasstogether with the system bucketcozy-backups. Tenants referencecozy-defaultdirectly from BackupJob / Plan / RestoreJob without provisioning a Bucket or supplying S3 credentials. See Backup Classes. The walk-through below covers the legacy path that wires a per-app Bucket and bespoke BackupClass — useful for tuned non-default policies.
This directory shows how to back up and restore a Cozystack-managed Etcd
application using the cluster's Etcd backup strategy driver. The driver
delegates to the etcd-operator: each Cozystack BackupJob
materialises an etcd-operator.cozystack.io/v1alpha2 EtcdSnapshot CR (a one-shot Job that
streams etcdctl snapshot save output to S3), and each RestoreJob
performs an in-place restore by suspending the Etcd HelmRelease,
deleting the chart-rendered EtcdCluster, and recreating it with
spec.bootstrap.restore.source.s3 populated from the artefact.
Supported destinations
This driver supports S3 destinations only. The strategy CR's
spec.template.destination schema rejects pvc at admission time. The
upstream etcd-operator's PVC backup-write path
(<mount>/<subPath>/<backupName>.db, plus
BACKUP_INCLUDE_REVISION / BACKUP_TIMESTAMP suffixes the agent adds at
write time) disagrees with its PVC restore-read path (opens
<mount>/<subPath> as a file, or <mount>/snapshot.db when subPath is
empty), so a PVC backup taken through this strategy would be
unrestoreable. The S3 path uses the operator's BackupSnapshot.URI
status field to recover the full final S3 object key including all
agent-injected suffixes — see
s3KeyFromArtifactURI in the driver.
PVC support will land once upstream gains symmetric semantics
(equivalent of BackupSnapshot.URI for PVC) or makes the restore-side
read the same path the backup-write side produced.
Restore semantics
This driver supports in-place restore only. To-copy restore is
rejected by the driver with a terminal phase=Failed. The PRIMARY
reason is API-level: RestoreJob.spec.targetApplicationRef is a
TypedLocalObjectReference, which has no namespace field — the
restore target is always the SAME namespace as the source Backup,
so there is no API surface today for a "fresh cluster in a different
namespace" flow. A SECONDARY chart-level constraint compounds this:
packages/extra/etcd/templates/check-release-name.yaml pins the
Helm release name to etcd, so two apps.cozystack.io/Etcd
applications cannot coexist in the same namespace regardless of
whether targetApplicationRef were extended to cross namespaces.
To get "a fresh cluster with the snapshot's data" today, deploy a
new Etcd app in a different namespace and hand-author an
EtcdCluster with spec.bootstrap.restore.source.s3 pointing at
the Backup's S3 coordinates from Backup.spec.driverMetadata.
Enabling a managed to-copy flow in this driver would require both
extending RestoreJob.spec.targetApplicationRef to a namespaced
reference upstream AND lifting the chart's release-name pin — the
chart change alone is not sufficient.
Step order
| File | Role | Triggered by |
|---|---|---|
00-helpers.sh |
Shared bash helpers and env defaults; sourced by every 01..05 step and by cleanup.sh. |
n/a |
01-create-strategy.sh |
Creates the cluster-scoped Etcd strategy. |
admin |
02-create-bucket.sh |
Provisions a Bucket, mints an <app>-etcd-backup-creds Secret in the source namespace, and creates the BackupClass bound to the Etcd strategy with resolved S3 coordinates. Caches raw creds in .bucket-info.env (chmod 600); cleanup.sh removes it. |
tenant |
03-create-etcd-src.sh |
Provisions the source Etcd application, waits for EtcdCluster.status.conditions[Available]=True, and writes a sentinel key under etcdctl. |
tenant |
04-create-backupjob.sh |
Submits a BackupJob and waits for phase=Succeeded. |
tenant |
05-restore-in-place.sh |
Mutates the sentinel, submits a RestoreJob (no targetApplicationRef), and verifies the sentinel is round-tripped back to its pre-mutation value. |
tenant |
cleanup.sh |
Best-effort teardown. | admin or tenant |
run-all.sh |
Convenience runner: 01..05 in order. | demo |
Environment variables (overrides)
All variables come from 00-helpers.sh:
| Variable | Default | Description |
|---|---|---|
NAMESPACE |
tenant-root |
Tenant namespace for the demo. |
ETCD_NAME |
etcd |
Source Etcd application name. Must be etcd because the chart pins the release name. |
BUCKET_NAME |
etcd-backups |
Cozystack Bucket to provision. |
BACKUPCLASS_NAME |
etcd-default |
BackupClass name (cluster-scoped). |
STRATEGY_NAME |
etcd-strategy-default |
Strategy name (cluster-scoped). |
BACKUPJOB_NAME |
etcd-backup-job |
BackupJob name. |
RESTOREJOB_INPLACE_NAME |
etcd-restore-inplace |
In-place RestoreJob name. |
Prerequisites
- Cozystack cluster with
backup-controllerandbackupstrategy-controllerrunning, with the Etcd dispatch wired (seeinternal/backupcontroller/etcdstrategy_controller.go). kubectl,jq.- The
etcd-operator.cozystack.ioCRDs installed bypackages/system/etcd-operator-crds. The Cozystack flow specifically depends onEtcdCluster(the driver re-creates it withspec.bootstrap.restore.source.s3during in-place restore) andEtcdSnapshot(one materialised perBackupJob). There is no separateEtcdRestoreCRD — restore goes throughEtcdCluster.spec.bootstrap. - The Cozystack
etcd-rdApplicationDefinition installed soapps.cozystack.io/Etcdrenders a HelmRelease.
Notes
- The chart's
backup.*values block (backup.enabled,backup.schedule,backup.destinationPath, ...) is marked DEPRECATED. It continues to render unchanged for existing tenants; new tenants should use thisBackupClassflow instead. Seepackages/extra/etcd/README.md. 02-create-bucket.shreads the bucket'sregionfrom theBucketInfoSecret. SeaweedFS (the default object-storage backend for CozystackBucketresources) does not populate.spec.secretS3.region, so the script falls back tous-east-1. The fallback only matters for S3 SDKs that require a non-empty region; override it by settingETCD_REGIONin your shell before sourcing00-helpers.shif you point the demo at a non-SeaweedFS endpoint whose region differs.- Step 05 only verifies the happy path. Edge cases (stuck HR resume, failed bootstrap, mid-restore controller crash) belong in the etcd Chainsaw suite at
hack/e2e-chainsaw/etcd/. - The destructive in-place flow deletes the
EtcdCluster(and the operator drops the member PVCs alongside it). All client traffic to etcd is unavailable for the duration of the restore. Plan a window.