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..47edb7f --- /dev/null +++ b/docs/modules/ROOT/pages/explanations/how-it-works.adoc @@ -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-` 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 + +|ClusterRole and ClusterRoleBinding +|`:solution-backup-manager` (cluster-scoped, name namespaced per instance to avoid cross-instance collisions) + +|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` (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.`, 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///` per `class/solution-backup.yml`; the Argo CD Application is created as `apps/`. + +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-`, the credentials Secret is `backup-bucket-creds-`, bucket names are `-` — 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-` 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 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. 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..c563efa --- /dev/null +++ b/docs/modules/ROOT/pages/how-tos/customize-backups.adoc @@ -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 +---- 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..687e548 --- /dev/null +++ b/docs/modules/ROOT/pages/how-tos/enable-backups.adoc @@ -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='' +---- + +. 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. + +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. diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 65c937f..b77a7c8 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.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. diff --git a/docs/modules/ROOT/pages/references/parameters.adoc b/docs/modules/ROOT/pages/references/parameters.adoc index 9e99f83..43ae4a6 100644 --- a/docs/modules/ROOT/pages/references/parameters.adoc +++ b/docs/modules/ROOT/pages/references/parameters.adoc @@ -2,18 +2,160 @@ The parent key for all of the following parameters is `solution_backup`. +The defaults shown below are set in `class/defaults.yml` (its `=_metadata` block is Commodore metadata, not a parameter). The per-instance compile and output layout is defined in `class/solution-backup.yml`. + == `namespace` [horizontal] type:: string -default:: `syn-solution-backup` +default:: `${_instance}` + +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:: dict +default:: `{}` + +Annotations added to the created `namespace`. + +== `namespaceLabels` + +[horizontal] +type:: dict +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`. It is also set as the region on each generated AppCat ObjectBucket (`spec.parameters.region`). + +== `objectbucket.name_prefix` + +[horizontal] +type:: string +default:: `pvc-backup-` + +Prefix for the name of the generated S3 bucket. Only the S3 bucket name follows this setting; resource names (ManagedResource, ObjectBucket, Schedule, credentials Secret) keep the fixed `pvc-backup-` prefix with the instance slug. + +== `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 -The namespace in which to deploy this component. +Number of monthly snapshots to keep. +The whole `schedule.retention` map is passed through verbatim into the k8up Schedule's `spec.prune.retention`, so any other retention key supported by k8up (e.g. `keepLast`) can be added as well. == 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]