diff --git a/docs/modules/ROOT/pages/explanations/how-it-works.adoc b/docs/modules/ROOT/pages/explanations/how-it-works.adoc new file mode 100644 index 0000000..ff985ff --- /dev/null +++ b/docs/modules/ROOT/pages/explanations/how-it-works.adoc @@ -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-` 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-` in the component namespace + +|espejote managed-by label +|`espejote.io/managed-by: .pvc-backup-` + +|Per-namespace ObjectBucket and k8up Schedule +|`pvc-backup-` in the target namespace + +|S3 bucket +|`-` + +|S3 credentials Secret +|`backup-bucket-creds-` 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-`. + +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-` with bucket name `-` and region, writing S3 credentials to `backup-bucket-creds-` (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. diff --git a/docs/modules/ROOT/pages/how-tos/customize-backups.adoc b/docs/modules/ROOT/pages/how-tos/customize-backups.adoc new file mode 100644 index 0000000..a82d087 --- /dev/null +++ b/docs/modules/ROOT/pages/how-tos/customize-backups.adoc @@ -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 +---- diff --git a/docs/modules/ROOT/pages/how-tos/enable-backups.adoc b/docs/modules/ROOT/pages/how-tos/enable-backups.adoc new file mode 100644 index 0000000..6bf8280 --- /dev/null +++ b/docs/modules/ROOT/pages/how-tos/enable-backups.adoc @@ -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='' +---- + +. 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-` which provisions the S3 bucket and writes the S3 credentials into the Secret `backup-bucket-creds-`. +* A k8up Schedule `pvc-backup-` 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- +---- + +== 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. diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 65c937f..b48ecaf 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -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..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. diff --git a/docs/modules/ROOT/pages/references/parameters.adoc b/docs/modules/ROOT/pages/references/parameters.adoc index 9e99f83..1c0dc5a 100644 --- a/docs/modules/ROOT/pages/references/parameters.adoc +++ b/docs/modules/ROOT/pages/references/parameters.adoc @@ -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 ---- diff --git a/docs/modules/ROOT/partials/nav.adoc b/docs/modules/ROOT/partials/nav.adoc index 08f9283..52af41b 100644 --- a/docs/modules/ROOT/partials/nav.adoc +++ b/docs/modules/ROOT/partials/nav.adoc @@ -1,2 +1,7 @@ * xref:index.adoc[Home] * xref:references/parameters.adoc[Parameters] +* How-to guides +** xref:how-tos/enable-backups.adoc[Enable backups] +** xref:how-tos/customize-backups.adoc[Customize backups] +* Explanations +** xref:explanations/how-it-works.adoc[How it works]