Files
cozystack/examples/backups/postgres
Aleksei Sviridkin 289acb2ed6 fix(tests): let the mariadb backup example cover a first boot
The walkthrough gave each MariaDB HelmRelease 300s, less than the
release can legitimately take, so a slow first boot failed the harness
and a failed install surfaced as a bare timeout with no reason.

A wait here has two floors and must beat the larger. The chart grants a
310s startup budget, and the install can span it: helm-controller
defaults to the poller strategy, and kstatus reads the operator CR's
Ready=False as InProgress. The generated release also carries a 600s
install timeout, and a wait equal to it expires first, because its
clock starts at the apply and the install's only once the chart is
fetched. The install timeout is the binding floor, hence 660s.

On timeout and on Stalled, wait_hr_ready now dumps every condition and
the release history: a scheduled retry can overwrite the Ready message
before anyone reads it. It also stops folding different answers into one
"(none recorded)" line. An absent release, a release deleted during the
wait, a lookup kubectl could not answer, an empty history and a failed
history read each say so, with kubectl's own stderr kept, because that
message is usually the cause. A release seen earlier still gets its dump
when only the last lookup failed.

Every backup example whose wait_hr_ready carries this body takes the
same change, so the copies stay byte-identical.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <3811295@gmail.com>
2026-09-23 11:56:10 +03:00
..

PostgreSQL backup & restore (barman-cloud plugin)

End-to-end demo of backing up a cozystack Postgres application to S3 and restoring it, using the CloudNativePG barman-cloud plugin (the native spec.backup.barmanObjectStore path is deprecated in CNPG 1.27 and removed in 1.29). Backups flow through the platform BackupClass + CNPG strategy so one strategy CR serves every Postgres instance in the tenant.

What it provisions

The numbered manifests are the documented flow; a human can read and apply them by hand, and run-all.sh drives the same files for a smoke test.

  • 00-bucket.yaml — a cozystack-managed S3 Bucket for the backups, with a backup user.
  • 05-postgres-src.yaml — the source Postgres, with backup.enabled: true so archive_command streams WALs from helm-install onwards.
  • 10-cnpg-strategy.yaml — the CNPG strategy templated against the live application.
  • 15-backupclass.yaml — maps Kind=Postgres to that strategy.
  • 20-plan.yaml — an optional cron Plan for recurring backups.
  • 25-backupjob-adhoc.yaml — an ad-hoc BackupJob.
  • 30-postgres-target.yaml + 40-restorejob-to-copy.yaml — restore into a fresh copy, leaving the source running.
  • 35-restorejob-in-place.yaml — the destructive in-place restore variant.
  • 45-restorejob-pitr.yaml — point-in-time recovery: restore into the copy as of a specific spec.options.recoveryTime (RFC3339).

Placeholders and derived Secrets

05-postgres-src.yaml and 10-cnpg-strategy.yaml carry REPLACE_WITH_COSI_BUCKET_NAME, REPLACE_WITH_S3_ENDPOINT and REPLACE_WITH_PASSWORD. They also reference two Secrets, <app>-cnpg-backup-creds (the S3 credentials the barman-cloud sidecar reads) and <app>-cnpg-backup-ca (the CA it trusts for a self-signed endpoint). The strategy template renders those names against whichever application it drives, so the restore target needs its own pair too — a restore against pg-target looks up pg-target-cnpg-backup-*. run-all.sh resolves the placeholders from the provisioned Bucket's BucketInfo Secret and materialises the pair for both the source and the target; editing by hand, you copy the coordinates from kubectl -n <ns> get secret bucket-<name>-backup -o jsonpath='{.data.BucketInfo}' and the CA from the seaweedfs CA secret in tenant-root (seaweedfs-ca-cert by default; run-all.sh auto-discovers it from the cert-manager Certificate if the name differs).

Drop the endpointCA block from 05/10 (and set S3_CA_SECRET="" for run-all.sh) when the S3 endpoint is signed by a publicly-trusted CA.

Run it

# Defaults to NAMESPACE=tenant-root; override for a tenant namespace.
NAMESPACE=tenant-root examples/backups/postgres/run-all.sh
# Tear everything down afterwards (idempotent).
NAMESPACE=tenant-root examples/backups/postgres/cleanup.sh

run-all.sh writes a sentinel row into the source, waits for the BackupJob to reach Succeeded, restores to a copy, and asserts the sentinel round-tripped through S3 into the restored copy. It then runs a point-in-time recovery: it writes a before marker, captures the server timestamp, writes an after marker, and restores the copy to that timestamp (45-restorejob-pitr.yaml), asserting the before row survived and the after row did not. Finally it submits a RestoreJob with a recoveryTime an hour in the future and asserts it fails fast with status.phase: Failed and reason RecoveryTargetUnreachable (rather than hanging to the restore deadline). Set SKIP_RESTORE=1 to stop after a successful backup, or SKIP_PITR=1 to stop after the latest-point restore.

Automated e2e

The Chainsaw suite at hack/e2e-chainsaw/postgres/ drives this same run-all.sh as a second test (postgres-2-backup-roundtrip), selected by CI whenever the postgres app (or these scripts) change and on every release cut. It runs in tenant-root against the in-cluster seaweedfs endpoint — the isolated e2e tenant cannot reach it across the Cilium egress policy, and the external ingress endpoint is an unroutable placeholder in the sandbox.