From cf9eae14b43ac1fdf9649ec4cb239d2e01ac4ad2 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Tue, 1 Apr 2025 19:36:10 -0500 Subject: [PATCH] restic: Add restic-prune CronJob This CronJob schedules a periodic run of `restic forget`, which deletes snapshots according to the specified retention period (14 daily, 4 weekly, 12 monthly). This task used to run on my workstation, scheduled by a systemd timer unit. I've kept the same schedule and retention period as before. Now, instead of relying on my PC to be on and awake, the cleanup will occur more regularly. There's also the added benefit of getting the logs into Loki. --- restic/.gitignore | 2 ++ restic/kustomization.yaml | 50 +++++++++++++++++++++++++++++++ restic/namespace.yaml | 6 ++++ restic/network-policy.yaml | 24 +++++++++++++++ restic/restic-prune.yaml | 60 ++++++++++++++++++++++++++++++++++++++ restic/restic.env | 4 +++ restic/secrets.yaml | 17 +++++++++++ 7 files changed, 163 insertions(+) create mode 100644 restic/.gitignore create mode 100644 restic/kustomization.yaml create mode 100644 restic/namespace.yaml create mode 100644 restic/network-policy.yaml create mode 100644 restic/restic-prune.yaml create mode 100644 restic/restic.env create mode 100644 restic/secrets.yaml diff --git a/restic/.gitignore b/restic/.gitignore new file mode 100644 index 0000000..7fb2ddf --- /dev/null +++ b/restic/.gitignore @@ -0,0 +1,2 @@ +credentials +password diff --git a/restic/kustomization.yaml b/restic/kustomization.yaml new file mode 100644 index 0000000..4a7d39c --- /dev/null +++ b/restic/kustomization.yaml @@ -0,0 +1,50 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: restic + +labels: +- pairs: + app.kubernetes.io/instance: restic + includeSelectors: true +- pairs: + app.kubernetes.io/part-of: restic + includeTemplates: true + +resources: +- namespace.yaml +- network-policy.yaml +- restic-prune.yaml +- secrets.yaml +- ../dch-root-ca + +configMapGenerator: +- name: restic-env + envs: + - restic.env + +patches: +- patch: |- + apiVersion: batch/v1 + kind: CronJob + metadata: + name: restic-prune + spec: + jobTemplate: + spec: + template: + spec: + containers: + - name: restic-prune + env: + - name: RESTIC_CACERT + value: /run/dch-ca/dch-root-ca.crt + volumeMounts: + - mountPath: /run/dch-ca + name: dch-ca + readOnly: true + volumes: + - name: dch-ca + configMap: + name: dch-root-ca + diff --git a/restic/namespace.yaml b/restic/namespace.yaml new file mode 100644 index 0000000..8c804ed --- /dev/null +++ b/restic/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: restic + labels: + app.kubernetes.io/name: restic diff --git a/restic/network-policy.yaml b/restic/network-policy.yaml new file mode 100644 index 0000000..3fbddb0 --- /dev/null +++ b/restic/network-policy.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: restic + labels: + app.kubernetes.io/name: restic + app.kubernetes.io/component: restic +spec: + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - to: + - ipBlock: + cidr: 172.30.0.15/32 + ports: + - port: 443 + podSelector: {} diff --git a/restic/restic-prune.yaml b/restic/restic-prune.yaml new file mode 100644 index 0000000..032385e --- /dev/null +++ b/restic/restic-prune.yaml @@ -0,0 +1,60 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: restic-prune + labels: + app.kubernetes.io/name: restic-prune + app.kubernetes.io/component: restic +spec: + schedule: 38 9 * * 5 + timeZone: America/Chicago + concurrencyPolicy: Forbid + jobTemplate: + metadata: + labels: &labels + app.kubernetes.io/name: restic-prune + app.kubernetes.io/component: restic + spec: + template: + metadata: + labels: *labels + spec: + restartPolicy: Never + containers: + - name: restic-prune + image: ghcr.io/restic/restic + args: + - forget + - --keep-daily=14 + - --keep-weekly=4 + - --keep-monthly=12 + env: + - name: XDG_CACHE_HOME + value: /var/cache + envFrom: + - configMapRef: + name: restic-env + securityContext: + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /run/secrets/restic + name: secrets + readOnly: true + - mountPath: /var/cache + name: cache + - mountPath: /tmp + name: tmp + securityContext: + runAsUser: 32142 + runAsGroup: 32142 + fsGroup: 32142 + runAsNonRoot: true + volumes: + - name: cache + emptyDir: {} + - name: secrets + secret: + secretName: restic-secrets + - name: tmp + emptyDir: + medium: Memory diff --git a/restic/restic.env b/restic/restic.env new file mode 100644 index 0000000..ffb2d21 --- /dev/null +++ b/restic/restic.env @@ -0,0 +1,4 @@ +RESTIC_REPOSITORY=s3:s3.backups.pyrocufflink.blue/restic +RESTIC_PASSWORD_FILE=/run/secrets/restic/password + +AWS_SHARED_CREDENTIALS_FILE=/run/secrets/restic/credentials diff --git a/restic/secrets.yaml b/restic/secrets.yaml new file mode 100644 index 0000000..0aec915 --- /dev/null +++ b/restic/secrets.yaml @@ -0,0 +1,17 @@ +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: restic-secrets + namespace: restic + labels: &labels + app.kubernetes.io/name: restic + app.kubernetes.io/component: restic +spec: + encryptedData: + credentials: AgBegS5P+JvhbhBb7/gZ1h+eyvqbXbj5bL5nN/MUbSjmrc/eR93ipe+kAKsfe1DCOtrEKYxO52RY2RdQHcUtgowD9LChCWPVPNZSDJnOId4nVRh9dSqSdYe+fkbp2wqHZf67AjpsP6/Ga3/3RFgNoVXmSDUmn8+5ZwTCKeC304XaCXnPj3iudpO52Wr6x+GpVfZ+yuVBMKMFHIFnnMt0AmjXCqbz+LPIUP/BKZ0/15DYTj4SO3heoSYy5y8suIiF9RZW6ne5dQ3w28V7hq18TNjATahtz+csLzUCRAKYoJfuZ0+YShqn2B/ZFItS1HbsWpoM23UwrNgx5TjSpNzTsh1wqlOztRcS47vyWu9ztDuXjDm2lSJMqoLAjbumV8QU+95LIVK8OCD9ziFCYCXm+7C2fKHG9Z3eEv83bFFQBVRbUg6zgv78FWb5PKrwg55rAM6C/U3AdvVFKYu8sBN30uaGIf6tCwZiHKuzp7JvVGffe6YFmBMU2oxGiLc4ckZozIGXBS26Uk79XM3ywUx+CWOClkqRLfeIjEP0rzseyh9kfXN2rAfn1o1WH6RJLopeE7IO7vnbM8t2CqVfKiBIsIXtKQ6t7JO/8On68AWTw+iqseK3iW6ZiComdYsP5RR5pZMfKa9iSAD6CaFLflMVFslVy2BQgtxWJ9SsQrB48iq3GFSBS2GG4x8NO0gZxY+AE1dvykRZ4U/YYheoClDvibQqPKFsvq83O7zaNwD2tGYiurxoaqAI6FtRCGjA2YGW+FzfikINNTsU21+GrkeNpHYVEXcL00gVR/RtO4JRM7ndCoTUCwCtrJitYLsGS9ZK7D2EXIqe/UqQCzsoVVEwN+irHXGzaA== + password: AgAXFkg2MdfuYD7XcQanf4rrkdO4dbB0IFJRU2oECcMNzGkQKC2+LfyqZv+TgBa0VnmCh3V+suVq48vG+W3Mr9g9LGzcjb3NVsKY8oxNsidEIU6ZksI/ijU7CF7/E5snpd7DowXCye+uA9QzxolarcVucF0S6CFoj5b5O2YFz7RsozJqsfRfXl+SbGbOzjPsKAmE6jBMHGfDlGA9VdgcGFECudBXZDyaaRQtpPITqRMzDdq/fKffA+r117deXzSXW0MB6RDsWubwSSdQXVtgbeAFn2oKGaEeloreRiNwaZ4nR6a6efkvlhz5prQ7sFFhGVDjdwhkdspXly1jHIFqTIoIul9/hRIZ8gGUfDMWq7YOYnwnPpkcY4+fE6LpfY/+BC2//t2VW5uk2lYRZin62bATIZpLE2hFGym3O4Mk5hz6O/jSMvDzDNJ6zYU3oN9C94qxgoLv2IL9tV1mpLvt8hcil0wWuj7qISIf88jTsneQ3quWz8xj6kOjLO+FJD5TjFU6qIhcXTS3/M3XqQbZRucPlVVeROFB9HJpqa51AOxV5i0yuosvlmOaLpOS8UPd3fzMYNY0bopuBrktndy1sY6VdV8ELHZXHFZxruAuYJnRzoZ4EjR5/Mo9BT7JKZLpRUgnLdPQTe5VwL5E1FgjMr/b3nwSyenb63ulOzLcfcy/fpBgOk5AwgOOqjDKLUMJzLppYZT7K2wAI/81IqdclRxblGTa1smyFt7nrUUc86pSWy+/lEhZ+nFrkSF9GFmf3hQDIK/OmqVoV46uMadQGi+d + template: + metadata: + name: restic-secrets + namespace: restic + labels: *labels