Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
161 lines
No EOL
6.8 KiB
YAML
161 lines
No EOL
6.8 KiB
YAML
{{- if .Values.persistence.enabled }}
|
|
{{- if eq .Values.persistence.type "persistentVolumeClaim" }}
|
|
{{- if not .Values.persistence.existingClaim }}
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: {{ include "go-bananas.dataVolumeName" . }}
|
|
namespace: {{ .Release.Namespace }}
|
|
labels:
|
|
{{- include "go-bananas.labels" . | nindent 4 }}
|
|
annotations:
|
|
{{- /*
|
|
Retain the volume when the release is uninstalled. The themes in it are the
|
|
only copy of an operator's work: deleting a volume on uninstall would make
|
|
`helm uninstall` destructive in a way that is very easy to do by accident.
|
|
*/}}
|
|
helm.sh/resource-policy: keep
|
|
{{- with .Values.global.extraAnnotations }}
|
|
{{- toYaml . | nindent 4 }}
|
|
{{- end }}
|
|
spec:
|
|
accessModes:
|
|
- {{ .Values.persistence.accessMode }}
|
|
resources:
|
|
requests:
|
|
storage: {{ .Values.persistence.size }}
|
|
{{- with .Values.persistence.storageClassName }}
|
|
storageClassName: {{ . | quote }}
|
|
{{- end }}
|
|
{{- end }}
|
|
{{- end }}
|
|
{{- if and .Values.persistence.snapshot.enabled (eq .Values.persistence.type "persistentVolumeClaim") }}
|
|
---
|
|
{{/*
|
|
A scheduled SQLite snapshot.
|
|
|
|
Three things this gets right that the obvious implementation does not:
|
|
|
|
1. *It is a CronJob, not a Helm hook.* Combining `schedule` with
|
|
`helm.sh/hook: pre-upgrade` is a contradiction: a hook runs on every upgrade
|
|
regardless of the schedule, and with `hook-succeeded` the resource is then
|
|
deleted. As a hook it would both ignore its own schedule and never produce
|
|
anything to restore.
|
|
|
|
2. *The snapshots land on the same volume.* An `emptyDir` destination would be
|
|
discarded the moment the pod exits, which makes the whole exercise pointless.
|
|
|
|
3. *It runs the service's own `backup` command.* Copying a database file while a
|
|
write is in flight produces a torn copy. `go-bananas backup` uses SQLite's
|
|
VACUUM INTO, which reads through the database's own backup machinery and is
|
|
consistent without blocking writers. Using the application binary rather than a
|
|
`sqlite3` from some other image also guarantees the SQLite library is the same
|
|
version the schema was written for, and means no third-party image and no
|
|
package installation into a read-only root filesystem.
|
|
|
|
The known limitation is stated rather than hidden: the job mounts the same
|
|
ReadWriteOnce volume as the Deployment, so on a cluster whose CSI driver enforces
|
|
single-node attachment strictly the job can fail to mount while the application is
|
|
running. If your cluster reports MultiAttach errors, give the job a node affinity
|
|
that pins it alongside the running pod, or take snapshots from outside the cluster.
|
|
A release using `emptyDir` data has nothing to snapshot and is unaffected.
|
|
|
|
Restoring is manual, because a restore should be a deliberate act:
|
|
|
|
kubectl -n <namespace> exec <pod> -- /usr/local/bin/go-bananas backup --to /tmp/check.db
|
|
# then stop the service, replace the database file, and start it again.
|
|
*/}}
|
|
apiVersion: batch/v1
|
|
kind: CronJob
|
|
metadata:
|
|
name: {{ include "go-bananas.fullname" . }}-snapshot
|
|
namespace: {{ .Release.Namespace }}
|
|
labels:
|
|
{{- include "go-bananas.labels" . | nindent 4 }}
|
|
annotations:
|
|
{{- with .Values.global.extraAnnotations }}
|
|
{{- toYaml . | nindent 4 }}
|
|
{{- end }}
|
|
spec:
|
|
schedule: {{ .Values.persistence.snapshot.schedule | quote }}
|
|
concurrencyPolicy: Forbid
|
|
# Snapshots are named with a timestamp, so two runs cannot collide, and an
|
|
# overlap is a sign the volume is too slow rather than a problem to solve by
|
|
# running them at once.
|
|
startingDeadlineSeconds: 600
|
|
successfulJobsHistoryLimit: 3
|
|
failedJobsHistoryLimit: 3
|
|
jobTemplate:
|
|
metadata:
|
|
labels:
|
|
{{- include "go-bananas.labels" . | nindent 8 }}
|
|
spec:
|
|
backoffLimit: 2
|
|
template:
|
|
metadata:
|
|
labels:
|
|
{{- include "go-bananas.selectorLabels" . | nindent 12 }}
|
|
app.kubernetes.io/component: snapshot
|
|
spec:
|
|
restartPolicy: OnFailure
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 65532
|
|
runAsGroup: 65532
|
|
fsGroup: 65532
|
|
containers:
|
|
- name: snapshot
|
|
# The service image itself: the snapshot command is part of the
|
|
# binary, so there is no second image to keep in step with this one.
|
|
image: {{ include "go-bananas.image" . }}
|
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop: ["ALL"]
|
|
env:
|
|
- name: DATABASE_PATH
|
|
value: {{ include "go-bananas.databasePath" . | quote }}
|
|
- name: SNAPSHOT_RETENTION_DAYS
|
|
value: {{ .Values.persistence.snapshot.retention | quote }}
|
|
# A shell, because the snapshot filename needs a timestamp and an argv
|
|
# list cannot expand one.
|
|
command: ["/bin/sh", "-euc"]
|
|
args:
|
|
- |
|
|
dest="/snapshot/go-bananas-$(date -u +%Y%m%dT%H%M%SZ).db"
|
|
/usr/local/bin/go-bananas backup --to "$dest"
|
|
|
|
# Retention by age, run here rather than as a lifecycle hook so it
|
|
# happens on the same schedule as the snapshot and cannot be skipped
|
|
# by a container that failed to start. An old snapshot is removed even
|
|
# though the Job that produced it has long since been garbage
|
|
# collected.
|
|
find /snapshot -name 'go-bananas-*.db' \
|
|
-mtime "+$SNAPSHOT_RETENTION_DAYS" -delete
|
|
ls -la /snapshot
|
|
volumeMounts:
|
|
- name: data
|
|
mountPath: {{ .Values.persistence.mountPath }}
|
|
readOnly: true
|
|
- name: snapshot
|
|
# The writable half of the same volume. Only the snapshot
|
|
# directory is writable; the database file itself is mounted
|
|
# read-only above.
|
|
mountPath: /snapshot
|
|
subPath: snapshots
|
|
volumes:
|
|
- name: data
|
|
persistentVolumeClaim:
|
|
claimName: {{ .Values.persistence.existingClaim | default (include "go-bananas.dataVolumeName" .) }}
|
|
readOnly: true
|
|
- name: snapshot
|
|
persistentVolumeClaim:
|
|
claimName: {{ .Values.persistence.existingClaim | default (include "go-bananas.dataVolumeName" .) }}
|
|
# Writable, and only for the subPath above. The pod as a whole can
|
|
# still reach the database, which is why the read-only mount and the
|
|
# `runAsNonRoot` security context are both kept.
|
|
readOnly: false
|
|
{{- end }}
|
|
{{- end }} |