Backups¶
Postgres: Barman Cloud plugin to SeaweedFS¶
CloudNativePG backs up through the Barman Cloud plugin, not the in-tree
spec.backup.barmanObjectStore, which is deprecated. Three objects make it up:
| Object | File | Does |
|---|---|---|
ObjectStore postgres |
flux/40_services/postgres/object-store.yaml |
bucket path, endpoint, credentials, retention, compression |
Cluster.spec.plugins |
flux/40_services/postgres/cluster.yaml |
points at the ObjectStore, isWALArchiver: true for continuous WAL archiving |
ScheduledBackup postgres-daily |
flux/40_services/postgres/object-store.yaml |
a base backup every day at 03:00, method: plugin |
Continuous WAL plus daily base backups give point-in-time recovery to any moment
inside the 30-day retentionPolicy. Retention is Barman's job, not a bucket
lifecycle rule: Barman knows which WAL a base backup still needs, a lifecycle
rule does not. immediate: true takes the first base backup as soon as the
ScheduledBackup exists, because archived WAL is useless without one.
The plugin runs in cnpg-system beside the operator, from the controllers
layer (flux/20_controllers/cloudnative-pg/plugin-barman-cloud.yaml). It talks to
the operator over mTLS with certificates from cert-manager, so its HelmRelease
depends on both. The ObjectStore CRD it installs therefore exists before
services declares one.
The bucket and its credentials¶
The postgres bucket is one entry in terraform/40_seaweedfs-buckets, the same
flow as the LGTM buckets: OpenBao holds the key at seaweedfs/postgres/postgres
and an ExternalSecret syncs it into the postgres-s3 Secret, region included.
Nothing secret is in git.
That makes services wait on the root: the ExternalSecret is not Ready
until 40_seaweedfs-buckets has written the key, so on a fresh cluster apply it
while services is reconciling.
Details that are easy to get wrong¶
destinationPathis the bucket, the server name is the cluster.serverNameis left empty, so Barman stores unders3://postgres/postgres/. Two clusters archiving to one prefix corrupt each other's WAL timeline, so a cluster restored from this one must archive under a new name.- CloudNativePG cron has six fields, seconds first:
0 0 3 * * *is 03:00. - Checksums: current boto3 adds CRC32 checksums to every upload, which
non-AWS stores can reject.
instanceSidecarConfiguration.envsetsAWS_REQUEST_CHECKSUM_CALCULATIONandAWS_RESPONSE_CHECKSUM_VALIDATIONtowhen_required. - Metrics are renamed with the plugin, from
cnpg_collector_*tobarman_cloud_cloudnative_pg_io_*. Dashboards or alerts written for the in-tree path will not match.
Longhorn stays out of it¶
Database volumes carry no Longhorn recurring jobs (see Postgres). A Longhorn backup of a running database is only crash-consistent and duplicates what Barman already keeps.
What this does not cover¶
SeaweedFS is a single node on one disk, and
seaweedfs-buckets/index.md states plainly that
nothing in it is backed up by being there. These backups survive a lost cluster,
not a lost s3-01; an off-site copy of the bucket is what covers that.
A backup is only proven by a restore: recover into a throwaway Cluster with
bootstrap.recovery and an externalClusters entry pointing at the same
ObjectStore through the plugin.
MariaDB: physical backups and binlog archiving¶
mariadb-operator gives the same shape as Barman: a base backup plus a continuous
log archive, both in SeaweedFS. All of it is in
flux/40_services/mariadb/backup.yaml:
| Object | Does |
|---|---|
PhysicalBackup mariadb-daily |
a mariadb-backup base backup every day at 03:00, immediate: true, from a replica when one is ready, kept 30 days (maxRetention: 720h) |
PhysicalBackup mariadb-replica |
never scheduled; the template the operator uses to rebuild or add a replica, written under replica/ |
PointInTimeRecovery mariadb |
binary log archiving, referenced from the MariaDB by pointInTimeRecoveryRef |
The agent sidecar on the primary uploads each closed binary log. max_binlog_size
is 128M so a quiet database still closes logs often; the RPO is the time until
the current log closes or the next archive cycle. Recovery bootstraps a new
MariaDB with bootstrapFrom.pointInTimeRecoveryRef and a targetRecoveryTime.
All of them write to the mariadb bucket, under base/, replica/ and binlog/. The PhysicalBackups pass --disable-ssl-verify-server-cert to mariadb-backup, see MariaDB. The bucket is an
entry in terraform/40_seaweedfs-buckets; its key reaches the mariadb-s3
Secret through an ExternalSecret, like postgres-s3.
Details that are easy to get wrong¶
- The binlog prefix must be empty when archiving starts. A cluster restored from these backups must archive to a different prefix, or it writes on top of the timeline it came from.
- Compression is fixed once binary logs are archived:
gzipfor both objects. - Archive state is not a metric. It is in
.status.pointInTimeRecoveryof theMariaDB:
The backup alerts watch the backup Jobs; nothing alerts on a stalled binlog archive. - The operator's cron has five fields, unlike CloudNativePG's six.