Skip to content
Open
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
68 changes: 68 additions & 0 deletions docs/modules/ROOT/pages/explanations/how-it-works.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
= How it works

The component's core is a single espejote ManagedResource that watches namespaces and renders two resources (an AppCat ObjectBucket and a k8up Schedule) for every namespace that opts in to backups.

== Reconciliation model

The ManagedResource `pvc-backup-<instanceSlug>` lives in the component's namespace (xref:references/parameters.adoc[`namespace`], by default the instance name) and defines:

* A *context* named `backup_namespaces`: the list of cluster Namespaces matching the label selector `vshn.ch/solution-base-backup: "true"`.
* A *trigger* named `namespace` with `watchContextResource` pointing at that context: whenever a namespace in the context changes (label added, removed, updated, or namespace deleted), the template runs again.

When the trigger fires for a specific namespace event, only that namespace is evaluated — the render is quick and per-namespace. During a general (non-namespace-triggered) reconciliation, the template iterates over all namespaces in the `backup_namespaces` context.

== The wantsBackup guard

For each candidate namespace the template checks:

* The namespace is not being deleted (no deletion timestamp).
* The opt-in label value equals the expected value (`true` by default).
* *And* the encryption-secret label is present — its value names the Secret in the namespace holding the restic repository password under the key `password`. There is no default Secret name.

All three conditions must hold. A namespace carrying only the opt-in label is silently skipped — nothing is rendered for it. This is why xref:how-tos/enable-backups.adoc[enabling backups] requires both labels.

== Naming scheme

|===
|Resource |Name

|espejote ManagedResource
|`pvc-backup-<instanceSlug>` in the component namespace

|espejote managed-by label
|`espejote.io/managed-by: <namespace>.pvc-backup-<instanceSlug>`

|Per-namespace ObjectBucket and k8up Schedule
|`pvc-backup-<instanceSlug>` in the target namespace

|S3 bucket
|`<name_prefix><instanceSlug>-<namespaceSlug>`

|S3 credentials Secret
|`backup-bucket-creds-<instanceSlug>` in the target namespace
|===

== Slugs

The instance slug is the first 8 hex characters of the SHA-256 hash of the instance name. The namespace slug is the first 8 hex characters of the SHA-256 hash of the target namespace's name. Example: for instance `defaults`, the slug is `0f37e81d`, so a namespace `test-app` gets bucket name `pvc-backup-0f37e81d-<test-app-slug>`.

Slugs keep resource names deterministic and within DNS label length limits regardless of how long instance or namespace names are.

== Endpoint resolution

`s3.endpoint` is a template: the `%(region)s` placeholder is substituted with `s3.region`. The default `https://objects.%(region)s.cloudscale.ch` with region `rma` resolves to `https://objects.rma.cloudscale.ch`. The resolved endpoint is baked into each generated k8up Schedule's backend configuration.

== Rendering outcome

For each namespace passing the guard the template renders, both in the target namespace:

* An AppCat ObjectBucket (`appcat.vshn.io/v1`) named `pvc-backup-<instanceSlug>` with bucket name `<name_prefix><instanceSlug>-<namespaceSlug>` and region, writing S3 credentials to `backup-bucket-creds-<instanceSlug>` (keys `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`).
* A k8up Schedule (`k8up.io/v1`) with the same name, referencing the credentials Secret and the encryption Secret (from the label, key `password`) in its backend, plus the configured backup/check/prune schedules and retention.

== Argo CD integration

component/app.jsonnet creates the Argo CD Application for this component via the standard Kubernetes application library, with the sync option `ServerSideApply=true` so large generated manifests apply reliably.

== Teardown

When a namespace stops backing up (either label removed, or namespace deleted), `wantsBackup` no longer holds and the ObjectBucket and Schedule pair drops out of the rendered output. Espejote prunes resources it previously managed but no longer render, so the pair is removed on the next reconciliation. The component's own resources (namespace, ServiceAccount, ClusterRole/Binding, ManagedResource) stay until the component instance is removed.
66 changes: 66 additions & 0 deletions docs/modules/ROOT/pages/how-tos/customize-backups.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
= Customize backups

Override the parameters of the `solution_backup` component to adjust schedules, retention, and storage settings. This page shows common overrides; see xref:references/parameters.adoc[Parameters] for the full reference.

All examples are inventory fragments: they live under `parameters:` with the parent key `solution_backup`, in a Commodore hierarchy hierarchy file (`hierarchy.yml` entries or a class).

== Adjust schedules and retention

[k8up Schedule] structure of the generated Schedule:

[source,yaml]
----
parameters:
solution_backup:
schedule:
backup: "0 1 * * *"
check: "0 3 * * *"
prune: "0 4 * * *"
retention:
keepDaily: 3
keepWeekly: 2
keepMonthly: 1
----

== Use a different S3 endpoint or region

`s3.endpoint` supports the `%(region)s` placeholder, which is substituted from `s3.region`:

[source,yaml]
----
parameters:
solution_backup:
s3:
endpoint: https://objects.%(region)s.cloudscale.ch
region: rma
----

The default resolves to `https://objects.rma.cloudscale.ch`.

== Customize bucket names and namespace

[source,yaml]
----
parameters:
solution_backup:
objectbucket:
name_prefix: my-backup-prefix-
----

== Namespace management

By default the component creates and labels its own namespace. Adjust it like this:

By default the component creates its own namespace and labels it. Disable namespace creation or add labels and annotations:

[source,yaml]
----
parameters:
solution_backup:
namespace: my-backup-namespace
createNamespace: false
namespaceAnnotations:
example.com/owner: team-x
namespaceLabels:
example.com/tier: backup
----
65 changes: 65 additions & 0 deletions docs/modules/ROOT/pages/how-tos/enable-backups.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
= Enable backups

This page explains how to opt in a namespace for PVC backups and how to verify the generated resources.

Both of the following labels are required on the namespace. Without both, nothing is rendered.

* `vshn.ch/solution-base-backup: "true"` — the opt-in flag.
* `vshn.ch/solution-base-backup-encryption-secret` — its value is the name of a Secret in the namespace holding the restic repository password under the key `password`. There is no default value for this Secret's name.

== Prerequisites

* The component is deployed in the cluster (see xref:index.adoc[solution-backup] for prerequisites).
* You have cluster access to label namespaces and create Secrets.

== Steps

. Ensure the encryption Secret exists in the target namespace. If it does not exist yet, create one with the restic repository password:

[source,terminal]
----
kubectl create secret generic backup-encryption \
--namespace test-app \
--from-literal=password='<restic-repository-password>'
----

. Opt in the namespace by applying both labels:

[source,terminal]
----
kubectl label namespace test-app \
vshn.ch/solution-base-backup="true" \
vshn.ch/solution-base-backup-encryption-secret=backup-encryption
----

You may use any Secret name; it must match the label's value and contain the key `password`.

== What happens next

On the next reconciliation the component renders the following resources in the target namespace `test-app`:

* An AppCat ObjectBucket `pvc-backup-<instanceSlug>` which provisions the S3 bucket and writes the S3 credentials into the Secret `backup-bucket-creds-<instanceSlug>`.
* A k8up Schedule `pvc-backup-<instanceSlug>` that backs up the namespace's PVCs to the bucket, referencing the encryption Secret from the label.

Verify with:

[source,terminal]
----
kubectl -n test-app get objectbucket,schedule
kubectl -n test-app get secret backup-bucket-creds-<instanceSlug>
----

== Removing backups

To stop backing up a namespace, remove either label:

[source,terminal]
----
kubectl label namespace test-app \
vshn.ch/solution-base-backup- \
vshn.ch/solution-base-backup-encryption-secret-
----

Alternatively, deleting the namespace also stops the backups. On the next reconciliation, the generated ObjectBucket and Schedule for that namespace are removed.

Note that removing a single one of the two labels is sufficient — the generated pair (ObjectBucket and Schedule) is managed as one.
31 changes: 29 additions & 2 deletions docs/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
= solution-backup

solution-backup is a Commodore component to manage solution-backup.
solution-backup is a Commodore component that sets up per-namespace backups of persistent volume claims (PVCs) using link:https://k8up.io[k8up], backed by S3-compatible object storage provisioned through AppCat ObjectBucket resources.

See the xref:references/parameters.adoc[parameters] reference for further details.
Instead of configuring each backup by hand, you opt in individual namespaces with labels. The component uses link:https://espejote.vshn.net[espejote] to watch namespaces and, for every opted-in namespace, renders an ObjectBucket claim and a k8up Schedule that back up the namespace's PVCs to a dedicated S3 bucket.

== What the component creates

For each instance the component creates:

* An Argo CD Application that deploys the generated resources (synchronized with `ServerSideApply=true`).
* Optionally, a Namespace to hold the component's own resources (enabled by default).
* A ServiceAccount `solution-backup-manager` with a ClusterRole and ClusterRoleBinding allowing it to read namespaces and manage ObjectBucket and k8up Schedule resources.
* An espejote ManagedResource that reacts to namespace opt-in and renders, per opted-in namespace:
* An AppCat ObjectBucket (which provisions the S3 bucket and stores access credentials in a Secret).
* A k8up Schedule with backup, check, and prune jobs.

See xref:explanations/how-it-works.adoc[How it works] for the generated resources' names and details.

== Prerequisites

* The cluster provides the AppCat `ObjectBucket` CRD (`appcat.vshn.io/v1`) and can provision S3-compatible storage.
* k8up and espejote are installed in the cluster.
* The S3 endpoint (by default `https://objects.<region>.cloudscale.ch`) is reachable from the cluster.
* A Secret holding the restic repository password under the key `password` exists in each backed-up namespace, referenced by label.

== Usage

* xref:how-tos/enable-backups.adoc[Enable backups] — opt in namespaces and verify the generated resources.
* xref:how-tos/customize-backups.adoc[Customize backups] — adjust schedules, retention, storage settings, and more.
* xref:explanations/how-it-works.adoc[How it works] — the reconciliation model and naming scheme.
* xref:references/parameters.adoc[Parameters] — the full parameter reference.
144 changes: 141 additions & 3 deletions docs/modules/ROOT/pages/references/parameters.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,152 @@ The parent key for all of the following parameters is `solution_backup`.

[horizontal]
type:: string
default:: `syn-solution-backup`
default:: `${_instance}`

The namespace in which to deploy this component.
Namespace in which the component's own resources (including the espejote ManagedResource) are deployed. By default this is the Commodore instance name, so each instance deploys into its own namespace.

== `createNamespace`

[horizontal]
type:: boolean
default:: true

If `true`, the component creates the `namespace` (with `namespaceAnnotations` and `namespaceLabels` applied). If `false`, the namespace must already exist.

== `namespaceAnnotations`

[horizontal]
type:: map
default:: `{}`

Annotations added to the created `namespace`.

== `namespaceLabels`

[horizontal]
type:: map
default:: `{}`

Labels added to the created `namespace`.

== `backup_namespaces.label`

[horizontal]
type:: string
default:: `vshn.ch/solution-base-backup`

Namespace label key used to opt in namespaces for backups, and as the context label selector.

== `backup_namespaces.value`

[horizontal]
type:: string
default:: `"true"`

Required value of the `backup_namespaces.label` label for a namespace to be opted in.

== `backup_encryption_secret.label`

[horizontal]
type:: string
default:: `vshn.ch/solution-base-backup-encryption-secret`

Namespace label key whose value names the Secret in the target namespace that holds the restic repository password under the key `password`. There is no default Secret name; a namespace without this label is not backed up.

== `s3.endpoint`

[horizontal]
type:: string
default:: `https://objects.%(region)s.cloudscale.ch`

S3-compatible endpoint for the backup bucket. The `%(region)s` placeholder is substituted from `s3.region`.

== `s3.region`

[horizontal]
type:: string
default:: `rma`

Region substituted into the `s3.endpoint` placeholder. With the defaults this resolves to `https://objects.rma.cloudscale.ch`.

== `objectbucket.name_prefix`

[horizontal]
type:: string
default:: `pvc-backup-`

Prefix for the generated S3 bucket names.

== `schedule.backup`

[horizontal]
type:: string
default:: `0 1 * * *`

Cron schedule for k8up backup jobs.

== `schedule.check`

[horizontal]
type:: string
default:: `0 3 * * *`

Cron schedule for k8up restic consistency checks.

== `schedule.prune`

[horizontal]
type:: string
default:: `0 4 * * *`

Cron schedule for k8up prune jobs.

== `schedule.retention.keepDaily`

[horizontal]
type:: integer
default:: 3

Number of daily snapshots to keep.

== `schedule.retention.keepWeekly`

[horizontal]
type:: integer
default:: 2

Number of weekly snapshots to keep.

== `schedule.retention.keepMonthly`

[horizontal]
type:: integer
default:: 1

Number of monthly snapshots to keep.

== Example

[source,yaml]
----
namespace: example-namespace
parameters:
solution_backup:
namespace: solution-backup
createNamespace: true
backup_namespaces:
label: vshn.ch/solution-base-backup
value: "true"
s3:
endpoint: https://objects.%(region)s.cloudscale.ch
region: rma
objectbucket:
name_prefix: pvc-backup-
schedule:
backup: "0 1 * * *"
check: "0 3 * * *"
prune: "0 4 * * *"
retention:
keepDaily: 3
keepWeekly: 2
keepMonthly: 1
----
Loading