Fix Stalwart backup CronJob to handle ReadWriteOnce PVC
Implement scale-down/backup/scale-up pattern to work around PVC access mode limitation. Changes: - Add RBAC (ServiceAccount, Role, RoleBinding) with statefulsets/scale and pods permissions - Switch to alpine:3.19 base image with kubectl and restic - Scale down StatefulSet to 0 replicas before backup - Run restic backup while pod is stopped - Scale back up to 1 replica with error handling - Add cleanup trap to ensure scale-up even on failure - Set 10-minute timeout and backoff limit Tested successfully: backup completes in ~32 seconds with minimal downtime. Resolves DEV-236. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
b0f2acf5f3
commit
d8cd3638fa
1 changed files with 95 additions and 4 deletions
|
|
@ -206,6 +206,42 @@ spec:
|
||||||
port:
|
port:
|
||||||
number: 8080
|
number: 8080
|
||||||
---
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: stalwart-backup
|
||||||
|
namespace: stalwart
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: stalwart-backup
|
||||||
|
namespace: stalwart
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
resources: ["statefulsets"]
|
||||||
|
verbs: ["get", "list", "patch"]
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
resources: ["statefulsets/scale"]
|
||||||
|
verbs: ["get", "update", "patch"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: stalwart-backup
|
||||||
|
namespace: stalwart
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: stalwart-backup
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: stalwart-backup
|
||||||
|
namespace: stalwart
|
||||||
|
---
|
||||||
apiVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: CronJob
|
kind: CronJob
|
||||||
metadata:
|
metadata:
|
||||||
|
|
@ -217,43 +253,98 @@ spec:
|
||||||
failedJobsHistoryLimit: 3
|
failedJobsHistoryLimit: 3
|
||||||
jobTemplate:
|
jobTemplate:
|
||||||
spec:
|
spec:
|
||||||
|
backoffLimit: 2
|
||||||
|
activeDeadlineSeconds: 600 # 10 minute timeout
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: stalwart-backup
|
app: stalwart-backup
|
||||||
spec:
|
spec:
|
||||||
|
serviceAccountName: stalwart-backup
|
||||||
restartPolicy: OnFailure
|
restartPolicy: OnFailure
|
||||||
containers:
|
containers:
|
||||||
- name: backup
|
- name: backup
|
||||||
image: restic/restic:latest
|
image: alpine:3.19
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
set -e
|
set -e
|
||||||
echo "Starting Stalwart backup at $(date)"
|
BACKUP_START=$(date +%s)
|
||||||
|
echo "=== Starting Stalwart backup at $(date) ==="
|
||||||
|
|
||||||
|
# Install required tools
|
||||||
|
echo "Installing kubectl and restic..."
|
||||||
|
apk add --no-cache kubectl restic curl bash
|
||||||
|
|
||||||
|
# Build restic repository URL from env vars
|
||||||
|
export RESTIC_REPOSITORY="s3:${S3_ENDPOINT}/${S3_BUCKET}/stalwart"
|
||||||
|
echo "Using restic repository: $RESTIC_REPOSITORY"
|
||||||
|
|
||||||
|
# Function to scale StatefulSet
|
||||||
|
scale_statefulset() {
|
||||||
|
local replicas=$1
|
||||||
|
echo "Scaling stalwart StatefulSet to $replicas replicas..."
|
||||||
|
kubectl scale statefulset stalwart -n stalwart --replicas=$replicas
|
||||||
|
|
||||||
|
if [ "$replicas" -eq 0 ]; then
|
||||||
|
echo "Waiting for pod to terminate..."
|
||||||
|
kubectl wait --for=delete pod/stalwart-0 -n stalwart --timeout=120s || true
|
||||||
|
sleep 5
|
||||||
|
else
|
||||||
|
echo "Waiting for pod to be ready..."
|
||||||
|
kubectl wait --for=condition=ready pod/stalwart-0 -n stalwart --timeout=120s || echo "Warning: Pod not ready after 120s"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap to ensure we scale back up on any exit
|
||||||
|
cleanup() {
|
||||||
|
EXIT_CODE=$?
|
||||||
|
echo "Cleanup: scaling stalwart back to 1 replica..."
|
||||||
|
scale_statefulset 1 || echo "ERROR: Failed to scale back up!"
|
||||||
|
|
||||||
|
BACKUP_END=$(date +%s)
|
||||||
|
DURATION=$((BACKUP_END - BACKUP_START))
|
||||||
|
|
||||||
|
if [ $EXIT_CODE -eq 0 ]; then
|
||||||
|
echo "=== Backup completed successfully in ${DURATION}s at $(date) ==="
|
||||||
|
else
|
||||||
|
echo "=== Backup FAILED after ${DURATION}s at $(date) ==="
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $EXIT_CODE
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
# Scale down Stalwart
|
||||||
|
scale_statefulset 0
|
||||||
|
|
||||||
# Build restic repository URL from env vars (K8s doesn't expand $(VAR) in value fields)
|
# Build restic repository URL from env vars (K8s doesn't expand $(VAR) in value fields)
|
||||||
export RESTIC_REPOSITORY="s3:${S3_ENDPOINT}/${S3_BUCKET}/stalwart"
|
export RESTIC_REPOSITORY="s3:${S3_ENDPOINT}/${S3_BUCKET}/stalwart"
|
||||||
echo "Using repository: $RESTIC_REPOSITORY"
|
echo "Using repository: $RESTIC_REPOSITORY"
|
||||||
|
|
||||||
# Initialize restic repo if needed
|
# Initialize restic repo if needed
|
||||||
|
echo "Initializing restic repository..."
|
||||||
restic snapshots || restic init
|
restic snapshots || restic init
|
||||||
|
|
||||||
# Backup the data directory
|
# Backup the data directory
|
||||||
|
echo "Running backup..."
|
||||||
restic backup /var/lib/stalwart \
|
restic backup /var/lib/stalwart \
|
||||||
--tag stalwart \
|
--tag stalwart \
|
||||||
--tag daily \
|
--tag daily \
|
||||||
--host stalwart-k8s
|
--host stalwart-k8s \
|
||||||
|
--verbose
|
||||||
|
|
||||||
# Prune old backups (keep 7 daily, 4 weekly, 6 monthly)
|
# Prune old backups (keep 7 daily, 4 weekly, 6 monthly)
|
||||||
|
echo "Pruning old backups..."
|
||||||
restic forget \
|
restic forget \
|
||||||
|
--tag stalwart \
|
||||||
--keep-daily 7 \
|
--keep-daily 7 \
|
||||||
--keep-weekly 4 \
|
--keep-weekly 4 \
|
||||||
--keep-monthly 6 \
|
--keep-monthly 6 \
|
||||||
--prune
|
--prune
|
||||||
|
|
||||||
echo "Backup completed successfully at $(date)"
|
echo "Backup operations completed"
|
||||||
env:
|
env:
|
||||||
- name: RESTIC_PASSWORD
|
- name: RESTIC_PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue