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
83 changes: 83 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,83 @@
= 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

|ClusterRole and ClusterRoleBinding
|`<namespace>:solution-backup-manager` (cluster-scoped, name namespaced per instance to avoid cross-instance collisions)

|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` (slug `b58b0cb4`) gets bucket name `pvc-backup-0f37e81d-b58b0cb4`.

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

== Multiple component instances

`class/defaults.yml` advertises `parameters.solution_backup._metadata.multi_instance: true`, so Commodore deploys the component once per declared instance.

Instances are declared in the Commodore `applications` list, e.g. `solution-backup as foo`; the component reads its parameters from `parameters.<instance_name_underscored>`, and Commodore sets the instance name via `parameters._instance`.

Per instance: `namespace` defaults to the instance name (`${_instance}`), so instances do not collide by default, and the compile output goes to `compiled/<target>/<instance>/` per `class/solution-backup.yml`; the Argo CD Application is created as `apps/<instance>`.

Names carry the instance slug (first 8 hex chars of SHA-256 of the instance name): ManagedResource and the per-namespace ObjectBucket/Schedule use `pvc-backup-<instanceSlug>`, the credentials Secret is `backup-bucket-creds-<instanceSlug>`, bucket names are `<name_prefix><instanceSlug>-<namespaceSlug>` — see xref:#_naming_scheme[naming scheme] above.

Caveat: two instances must not be configured with the same namespace, otherwise their ClusterRoles, ClusterRoleBindings and rendered resources collide.

== 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 never prunes objects absent from a render. Removing the labels therefore stops rendering but does not remove the previously created ObjectBucket or k8up Schedule; delete them manually if you want the bucket and backup schedule gone. If the whole namespace is deleted, the objects are garbage-collected with it by Kubernetes itself. The component's own resources (namespace, ServiceAccount, ClusterRole/Binding, ManagedResource) stay until the component instance is removed.
64 changes: 64 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,64 @@
= 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 file (`hierarchy.yml` entries or a class).

== Adjust schedules and retention

Structure of the generated `k8up 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. 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
----
67 changes: 67 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,67 @@
= 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.

As explained in xref:explanations/how-it-works.adoc[How it works], removing the labels stops rendering but does not delete the previously created ObjectBucket or Schedule: delete them manually, or let Kubernetes garbage-collect them with the namespace.

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.rma.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.
Loading