diff --git a/jobs/freeleaps-data-backup/.gitignore b/jobs/freeleaps-data-backup/.gitignore new file mode 100644 index 00000000..01140c58 --- /dev/null +++ b/jobs/freeleaps-data-backup/.gitignore @@ -0,0 +1,36 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/Dockerfile b/jobs/freeleaps-data-backup/Dockerfile new file mode 100644 index 00000000..f50e5a2f --- /dev/null +++ b/jobs/freeleaps-data-backup/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.11-slim + +# Set working directory +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements and install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy backup script +COPY backup_script.py . + +# Make script executable +RUN chmod +x backup_script.py + +# Set environment variables +ENV PYTHONUNBUFFERED=1 + +# Run the backup script +CMD ["python", "backup_script.py"] \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/README.md b/jobs/freeleaps-data-backup/README.md new file mode 100644 index 00000000..c1002f97 --- /dev/null +++ b/jobs/freeleaps-data-backup/README.md @@ -0,0 +1,277 @@ +# Freeleaps PVC Backup Job + +This job creates daily snapshots of critical PVCs in the Freeleaps production environment using Azure Disk CSI Snapshot feature. + +## Overview + +The backup job runs daily at 00:00 PST (Pacific Standard Time) and creates snapshots for the following PVCs: +- `gitea-shared-storage` in namespace `freeleaps-prod` +- `data-freeleaps-prod-gitea-postgresql-ha-postgresql-0` in namespace `freeleaps-prod` + +## Components + +- **backup_script.py**: Python script that creates snapshots and monitors their status +- **Dockerfile**: Container image definition +- **build.sh**: Script to build the Docker image +- **deploy-argocd.sh**: Script to deploy via ArgoCD +- **helm-pkg/**: Helm Chart for Kubernetes deployment +- **argo-app/**: ArgoCD Application configuration + +## Features + +- ✅ Creates snapshots with timestamp-based naming (YYYYMMDD format) +- ✅ Uses PST timezone for snapshot naming +- ✅ Monitors snapshot status until ready +- ✅ Comprehensive logging to console +- ✅ Error handling and retry logic +- ✅ RBAC permissions for secure operation +- ✅ Resource limits and security context +- ✅ Concurrency control (prevents overlapping jobs) +- ✅ Helm Chart for flexible configuration +- ✅ ArgoCD integration for GitOps deployment +- ✅ Incremental snapshots for cost efficiency + +## Building and Deployment + +### Option 1: ArgoCD Deployment (Recommended) + +#### 1. Build and Push Docker Image + +```bash +# Make build script executable +chmod +x build.sh + +# Build the image +./build.sh + +# Push to registry +docker push freeleaps-registry.azurecr.io/freeleaps-pvc-backup:latest +``` + +#### 2. Deploy via ArgoCD + +```bash +# Deploy ArgoCD Application +./deploy-argocd.sh +``` + +#### 3. Monitor in ArgoCD + +```bash +# Check ArgoCD application status +kubectl get applications -n freeleaps-devops-system + +# Access ArgoCD UI +kubectl port-forward svc/argocd-server -n freeleaps-devops-system 8080:443 +``` + +Then visit `https://localhost:8080` in your browser. + +### Option 2: Direct Helm Deployment + +#### 1. Build and Push Docker Image + +```bash +# Build the image +./build.sh + +# Push to registry +docker push freeleaps-registry.azurecr.io/freeleaps-pvc-backup:latest +``` + +#### 2. Deploy with Helm + +```bash +# Deploy using Helm Chart +helm install freeleaps-data-backup ./helm-pkg/freeleaps-data-backup \ + --values helm-pkg/freeleaps-data-backup/values.prod.yaml \ + --namespace freeleaps-prod \ + --create-namespace +``` + +## Monitoring + +### Check CronJob Status + +```bash +kubectl get cronjobs -n freeleaps-prod +``` + +### Check Job History + +```bash +kubectl get jobs -n freeleaps-prod +``` + +### View Job Logs + +```bash +# Get the latest job name +kubectl get jobs -n freeleaps-prod --sort-by=.metadata.creationTimestamp + +# View logs +kubectl logs -n freeleaps-prod job/freeleaps-data-backup- +``` + +### Check Snapshots + +```bash +kubectl get volumesnapshots -n freeleaps-prod +``` + +## Configuration + +### Schedule + +The job runs daily at 00:00 PST. To modify the schedule, edit the `cronjob.schedule` field in `helm-pkg/freeleaps-data-backup/values.prod.yaml`: + +```yaml +cronjob: + schedule: "0 8 * * *" # UTC 08:00 = PST 00:00 +``` + +### PVCs to Backup + +To add or remove PVCs, modify the `backup.pvcs` list in `helm-pkg/freeleaps-data-backup/values.prod.yaml`: + +```yaml +backup: + pvcs: + - "gitea-shared-storage" + - "data-freeleaps-prod-gitea-postgresql-ha-postgresql-0" + # Add more PVCs here +``` + +### Snapshot Class + +The job uses the `csi-azuredisk-vsc` snapshot class with incremental snapshots enabled. This can be modified in `helm-pkg/freeleaps-data-backup/values.prod.yaml`: + +```yaml +backup: + snapshotClass: "csi-azuredisk-vsc" +``` + +### Resource Limits + +Resource limits can be configured in `helm-pkg/freeleaps-data-backup/values.prod.yaml`: + +```yaml +resources: + requests: + memory: "256Mi" + cpu: "200m" + limits: + memory: "512Mi" + cpu: "500m" +``` + +## How It Works + +### Snapshot Naming + +Snapshots are named using the format: `{PVC_NAME}-snapshot-{YYYYMMDD}` + +Examples: +- `gitea-shared-storage-snapshot-20250805` +- `data-freeleaps-prod-gitea-postgresql-ha-postgresql-0-snapshot-20250805` + +### Processing Flow + +1. **PVC Verification**: Each PVC is verified to exist before processing +2. **Snapshot Creation**: Individual snapshots are created for each PVC +3. **Status Monitoring**: Each snapshot is monitored until ready +4. **Independent Processing**: PVCs are processed independently (one failure doesn't affect others) + +### Incremental Snapshots + +The job uses Azure Disk CSI incremental snapshots, which: +- Save storage costs by only storing changed data blocks +- Create faster than full snapshots +- Maintain full recovery capability + +## Troubleshooting + +### Common Issues + +1. **Permission Denied**: Ensure RBAC is properly configured +2. **PVC Not Found**: Verify PVC names and namespace +3. **Snapshot Creation Failed**: Check Azure Disk CSI driver status +4. **Job Timeout**: Increase timeout in the values file if needed + +### Debug Mode + +To run the script locally for testing: + +```bash +# Install dependencies +pip install -r requirements.txt + +# Run with local kubeconfig +python3 backup_script.py +``` + +## Security + +- The job runs with minimal required permissions +- Non-root user execution +- Dropped capabilities +- Resource limits enforced +- No privileged access + +## Maintenance + +### Cleanup Old Snapshots + +Old snapshots can be cleaned up manually: + +```bash +# List all snapshots +kubectl get volumesnapshots -n freeleaps-prod + +# Delete specific snapshot +kubectl delete volumesnapshot -n freeleaps-prod + +# Delete snapshots older than 30 days (example) +kubectl get volumesnapshots -n freeleaps-prod -o jsonpath='{.items[?(@.metadata.creationTimestamp<"2024-07-05T00:00:00Z")].metadata.name}' | xargs kubectl delete volumesnapshot -n freeleaps-prod +``` + +### Updating Configuration + +To update the backup configuration: + +1. Modify the appropriate values file in `helm-pkg/freeleaps-data-backup/` +2. Commit and push changes to the repository +3. ArgoCD will automatically sync the changes +4. Or manually upgrade with Helm: `helm upgrade freeleaps-data-backup ./helm-pkg/freeleaps-data-backup --values values.prod.yaml` + +## Backup Data + +### What Gets Backed Up + +- **gitea-shared-storage**: Gitea repository data, attachments, and configuration +- **data-freeleaps-prod-gitea-postgresql-ha-postgresql-0**: PostgreSQL database data + +### Recovery + +To restore from a snapshot: + +```bash +# Create a PVC from snapshot +kubectl apply -f - < + kind: VolumeSnapshot + apiGroup: snapshot.storage.k8s.io + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +EOF +``` \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/argo-app/application.yaml b/jobs/freeleaps-data-backup/argo-app/application.yaml new file mode 100644 index 00000000..902d4cc2 --- /dev/null +++ b/jobs/freeleaps-data-backup/argo-app/application.yaml @@ -0,0 +1,32 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: freeleaps-data-backup + namespace: freeleaps-devops-system + labels: + app: freeleaps-data-backup + component: backup + environment: production +spec: + destination: + name: "" + namespace: freeleaps-prod + server: https://kubernetes.default.svc + source: + path: jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup + repoURL: https://freeleaps@dev.azure.com/freeleaps/freeleaps-ops/_git/freeleaps-ops + targetRevision: HEAD + helm: + parameters: [] + valueFiles: + - values.prod.yaml + sources: [] + project: freeleaps-data-backup + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - PrunePropagationPolicy=foreground + - PruneLast=true \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/backup_script.py b/jobs/freeleaps-data-backup/backup_script.py new file mode 100644 index 00000000..c21388cb --- /dev/null +++ b/jobs/freeleaps-data-backup/backup_script.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +""" +PVC Backup Script for Freeleaps Production Environment +Creates snapshots for specified PVCs and monitors their status +""" + +import os +import sys +import yaml +import time +import logging +from datetime import datetime, timezone, timedelta +from kubernetes import client, config +from kubernetes.client.rest import ApiException + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(sys.stdout) + ] +) +logger = logging.getLogger(__name__) + +class PVCBackupManager: + def __init__(self): + """Initialize the backup manager with Kubernetes client""" + try: + # Load in-cluster config when running in Kubernetes + config.load_incluster_config() + logger.info("Loaded in-cluster Kubernetes configuration") + except config.ConfigException: + # Fallback to kubeconfig for local development + try: + config.load_kube_config() + logger.info("Loaded kubeconfig for local development") + except config.ConfigException: + logger.error("Failed to load Kubernetes configuration") + sys.exit(1) + + self.api_client = client.ApiClient() + self.snapshot_api = client.CustomObjectsApi(self.api_client) + self.core_api = client.CoreV1Api(self.api_client) + + # Backup configuration + self.namespace = os.getenv("BACKUP_NAMESPACE", "freeleaps-prod") + self.pvcs_to_backup = [ + "gitea-shared-storage", + "data-freeleaps-prod-gitea-postgresql-ha-postgresql-0" + ] + self.snapshot_class = os.getenv("SNAPSHOT_CLASS", "csi-azuredisk-vsc") + self.timeout = int(os.getenv("TIMEOUT", "300")) + + def get_pst_date(self): + """Get current date in PST timezone (UTC-8)""" + pst_tz = timezone(timedelta(hours=-8)) + return datetime.now(pst_tz).strftime("%Y%m%d") + + def generate_snapshot_name(self, pvc_name, timestamp): + """Generate snapshot name with timestamp""" + return f"{pvc_name}-snapshot-{timestamp}" + + def create_snapshot_yaml(self, pvc_name, snapshot_name): + """Create VolumeSnapshot YAML configuration""" + snapshot_yaml = { + "apiVersion": "snapshot.storage.k8s.io/v1", + "kind": "VolumeSnapshot", + "metadata": { + "name": snapshot_name, + "namespace": self.namespace + }, + "spec": { + "volumeSnapshotClassName": self.snapshot_class, + "source": { + "persistentVolumeClaimName": pvc_name + } + } + } + return snapshot_yaml + + def apply_snapshot(self, snapshot_yaml): + """Apply snapshot to Kubernetes cluster""" + try: + logger.info(f"Creating snapshot: {snapshot_yaml['metadata']['name']}") + + # Create the snapshot + result = self.snapshot_api.create_namespaced_custom_object( + group="snapshot.storage.k8s.io", + version="v1", + namespace=self.namespace, + plural="volumesnapshots", + body=snapshot_yaml + ) + + logger.info(f"Successfully created snapshot: {result['metadata']['name']}") + return result + + except ApiException as e: + logger.error(f"Failed to create snapshot: {e}") + return None + + def wait_for_snapshot_ready(self, snapshot_name, timeout=None): + if timeout is None: + timeout = self.timeout + """Wait for snapshot to be ready with timeout""" + logger.info(f"Waiting for snapshot {snapshot_name} to be ready...") + + start_time = time.time() + while time.time() - start_time < timeout: + try: + # Get snapshot status + snapshot = self.snapshot_api.get_namespaced_custom_object( + group="snapshot.storage.k8s.io", + version="v1", + namespace=self.namespace, + plural="volumesnapshots", + name=snapshot_name + ) + + # Check if snapshot is ready + if snapshot.get('status', {}).get('readyToUse', False): + logger.info(f"Snapshot {snapshot_name} is ready!") + return True + + # Check for error conditions + error = snapshot.get('status', {}).get('error', {}) + if error: + logger.error(f"Snapshot {snapshot_name} failed: {error}") + return False + + logger.info(f"Snapshot {snapshot_name} still processing...") + time.sleep(10) + + except ApiException as e: + logger.error(f"Error checking snapshot status: {e}") + return False + + logger.error(f"Timeout waiting for snapshot {snapshot_name} to be ready") + return False + + def verify_pvc_exists(self, pvc_name): + """Verify that PVC exists in the namespace""" + try: + pvc = self.core_api.read_namespaced_persistent_volume_claim( + name=pvc_name, + namespace=self.namespace + ) + logger.info(f"Found PVC: {pvc_name}") + return True + except ApiException as e: + if e.status == 404: + logger.error(f"PVC {pvc_name} not found in namespace {self.namespace}") + else: + logger.error(f"Error checking PVC {pvc_name}: {e}") + return False + + def run_backup(self): + """Main backup process""" + logger.info("Starting PVC backup process...") + + timestamp = self.get_pst_date() + successful_backups = [] + failed_backups = [] + + for pvc_name in self.pvcs_to_backup: + logger.info(f"Processing PVC: {pvc_name}") + + # Verify PVC exists + if not self.verify_pvc_exists(pvc_name): + failed_backups.append(pvc_name) + continue + + # Generate snapshot name + snapshot_name = self.generate_snapshot_name(pvc_name, timestamp) + + # Create snapshot YAML + snapshot_yaml = self.create_snapshot_yaml(pvc_name, snapshot_name) + + # Apply snapshot + result = self.apply_snapshot(snapshot_yaml) + if not result: + failed_backups.append(pvc_name) + continue + + # Wait for snapshot to be ready + if self.wait_for_snapshot_ready(snapshot_name): + successful_backups.append(pvc_name) + logger.info(f"Backup completed successfully for PVC: {pvc_name}") + else: + failed_backups.append(pvc_name) + logger.error(f"Backup failed for PVC: {pvc_name}") + + # Summary + logger.info("=== Backup Summary ===") + logger.info(f"Successful backups: {len(successful_backups)}") + logger.info(f"Failed backups: {len(failed_backups)}") + + if successful_backups: + logger.info(f"Successfully backed up: {', '.join(successful_backups)}") + + if failed_backups: + logger.error(f"Failed to backup: {', '.join(failed_backups)}") + return False + + logger.info("All backups completed successfully!") + return True + +def main(): + """Main entry point""" + try: + backup_manager = PVCBackupManager() + success = backup_manager.run_backup() + + if success: + logger.info("Backup job completed successfully") + sys.exit(0) + else: + logger.error("Backup job completed with errors") + sys.exit(1) + + except Exception as e: + logger.error(f"Unexpected error: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/build.sh b/jobs/freeleaps-data-backup/build.sh new file mode 100755 index 00000000..750b3f48 --- /dev/null +++ b/jobs/freeleaps-data-backup/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Build script for Freeleaps PVC Backup Docker image + +set -e + +# Configuration +IMAGE_NAME="freeleaps-pvc-backup" +REGISTRY="freeleaps-registry.azurecr.io" +TAG="${1:-latest}" + +echo "Building Freeleaps PVC Backup Docker image..." +echo "Image: ${REGISTRY}/${IMAGE_NAME}:${TAG}" + +# Build the Docker image +docker buildx build \ + --platform linux/amd64 \ + -f Dockerfile \ + -t "${REGISTRY}/${IMAGE_NAME}:${TAG}" \ + . + +echo "Build completed successfully!" +echo "To push the image, run:" +echo "docker push ${REGISTRY}/${IMAGE_NAME}:${TAG}" \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/deploy-argocd.sh b/jobs/freeleaps-data-backup/deploy-argocd.sh new file mode 100755 index 00000000..59ecaca2 --- /dev/null +++ b/jobs/freeleaps-data-backup/deploy-argocd.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Deploy script for Freeleaps PVC Backup to ArgoCD + +set -e + +echo "Deploying Freeleaps PVC Backup to ArgoCD..." + +# Apply ArgoCD Application +echo "Applying ArgoCD Application configuration..." +kubectl apply -f argo-app/application.yaml + +echo "ArgoCD Application deployed successfully!" +echo "" +echo "To check the status:" +echo "kubectl get applications -n freeleaps-devops-system" +echo "" +echo "To view ArgoCD UI:" +echo "kubectl port-forward svc/argocd-server -n freeleaps-devops-system 8080:443" +echo "" +echo "To check the CronJob after sync:" +echo "kubectl get cronjobs -n freeleaps-prod" +echo "" +echo "To view job logs:" +echo "kubectl get jobs -n freeleaps-prod" +echo "kubectl logs -n freeleaps-prod job/freeleaps-data-backup-" \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/Chart.yaml b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/Chart.yaml new file mode 100644 index 00000000..633ababd --- /dev/null +++ b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: freeleaps-data-backup +description: Freeleaps PVC Backup CronJob for production environment +type: application +version: 0.1.0 +appVersion: "1.0.0" +keywords: + - backup + - pvc + - snapshot + - cronjob +home: https://freeleaps.com +sources: + - https://freeleaps@dev.azure.com/freeleaps/freeleaps-ops/_git/freeleaps-ops +maintainers: + - name: Freeleaps DevOps Team + email: devops@freeleaps.com \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/_helpers.tpl b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/_helpers.tpl new file mode 100644 index 00000000..8f65346b --- /dev/null +++ b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "freeleaps-data-backup.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "freeleaps-data-backup.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "freeleaps-data-backup.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "freeleaps-data-backup.labels" -}} +helm.sh/chart: {{ include "freeleaps-data-backup.chart" . }} +{{ include "freeleaps-data-backup.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "freeleaps-data-backup.selectorLabels" -}} +app.kubernetes.io/name: {{ include "freeleaps-data-backup.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "freeleaps-data-backup.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "freeleaps-data-backup.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/cronjob.yaml b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/cronjob.yaml new file mode 100644 index 00000000..7edb573d --- /dev/null +++ b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/cronjob.yaml @@ -0,0 +1,54 @@ +{{- if .Values.cronjob.enabled -}} +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ include "freeleaps-data-backup.fullname" . }} + namespace: {{ .Values.backup.namespace }} + labels: + app: {{ include "freeleaps-data-backup.name" . }} + component: backup + {{- include "freeleaps-data-backup.labels" . | nindent 4 }} + {{- with .Values.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + schedule: {{ .Values.cronjob.schedule | quote }} + concurrencyPolicy: {{ .Values.cronjob.concurrencyPolicy }} + successfulJobsHistoryLimit: {{ .Values.cronjob.successfulJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.cronjob.failedJobsHistoryLimit }} + jobTemplate: + spec: + template: + metadata: + labels: + app: {{ include "freeleaps-data-backup.name" . }} + component: backup + {{- include "freeleaps-data-backup.labels" . | nindent 12 }} + spec: + {{- if .Values.serviceAccount.enabled }} + serviceAccountName: {{ .Values.serviceAccount.name }} + {{- end }} + restartPolicy: {{ .Values.cronjob.restartPolicy }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 12 }} + {{- end }} + containers: + - name: backup + image: "{{ .Values.global.imageRegistry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: TZ + value: "UTC" + - name: BACKUP_NAMESPACE + value: {{ .Values.backup.namespace | quote }} + - name: SNAPSHOT_CLASS + value: {{ .Values.backup.snapshotClass | quote }} + - name: TIMEOUT + value: {{ .Values.backup.timeout | quote }} + resources: + {{- toYaml .Values.resources | nindent 14 }} + securityContext: + {{- toYaml .Values.securityContext | nindent 14 }} +{{- end }} \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/rbac.yaml b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/rbac.yaml new file mode 100644 index 00000000..b6d3e962 --- /dev/null +++ b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/rbac.yaml @@ -0,0 +1,37 @@ +{{- if .Values.rbac.enabled -}} +{{- if .Values.rbac.create -}} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "freeleaps-data-backup.fullname" . }}-role + labels: + app: {{ include "freeleaps-data-backup.name" . }} + component: backup + {{- include "freeleaps-data-backup.labels" . | nindent 4 }} +rules: +- apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list"] +- apiGroups: ["snapshot.storage.k8s.io"] + resources: ["volumesnapshots", "volumesnapshotclasses"] + verbs: ["get", "list", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "freeleaps-data-backup.fullname" . }}-rolebinding + labels: + app: {{ include "freeleaps-data-backup.name" . }} + component: backup + {{- include "freeleaps-data-backup.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "freeleaps-data-backup.fullname" . }}-role +subjects: +- kind: ServiceAccount + name: {{ .Values.serviceAccount.name }} + namespace: {{ .Values.backup.namespace }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/serviceaccount.yaml b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/serviceaccount.yaml new file mode 100644 index 00000000..088a5b06 --- /dev/null +++ b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/templates/serviceaccount.yaml @@ -0,0 +1,17 @@ +{{- if .Values.serviceAccount.enabled -}} +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.serviceAccount.name }} + namespace: {{ .Values.backup.namespace }} + labels: + app: {{ include "freeleaps-data-backup.name" . }} + component: backup + {{- include "freeleaps-data-backup.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/values.prod.yaml b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/values.prod.yaml new file mode 100644 index 00000000..344c7e89 --- /dev/null +++ b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/values.prod.yaml @@ -0,0 +1,40 @@ +# Production values for freeleaps-data-backup + +# Image settings +image: + repository: freeleaps-pvc-backup + tag: "latest" + pullPolicy: Always + +# CronJob settings +cronjob: + enabled: true + schedule: "0 8 * * *" # Daily at 00:00 UTC (08:00 UTC+8) + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 7 + failedJobsHistoryLimit: 3 + restartPolicy: OnFailure + +# Resource limits for production +resources: + requests: + memory: "256Mi" + cpu: "200m" + limits: + memory: "512Mi" + cpu: "500m" + +# Backup configuration for production +backup: + namespace: "freeleaps-prod" + pvcs: + - "gitea-shared-storage" + - "data-freeleaps-prod-gitea-postgresql-ha-postgresql-0" + snapshotClass: "csi-azuredisk-vsc" + timeout: 600 # 10 minutes for production + +# Labels for production +labels: + environment: "production" + team: "devops" + component: "backup" \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/values.yaml b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/values.yaml new file mode 100644 index 00000000..270669f0 --- /dev/null +++ b/jobs/freeleaps-data-backup/helm-pkg/freeleaps-data-backup/values.yaml @@ -0,0 +1,65 @@ +# Default values for freeleaps-data-backup +# This is a YAML-formatted file. + +# Global settings +global: + imageRegistry: "freeleaps-registry.azurecr.io" + imagePullSecrets: [] + +# Image settings +image: + repository: freeleaps-pvc-backup + tag: "latest" + pullPolicy: Always + +# CronJob settings +cronjob: + enabled: true + schedule: "0 8 * * *" # Daily at 00:00 UTC (08:00 UTC+8) + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 7 + failedJobsHistoryLimit: 3 + restartPolicy: OnFailure + +# Resource limits +resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "200m" + +# Security context +securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + +# RBAC settings +rbac: + enabled: true + create: true + +# ServiceAccount settings +serviceAccount: + enabled: true + create: true + name: "freeleaps-backup-sa" + annotations: {} + +# Backup configuration +backup: + namespace: "freeleaps-prod" + pvcs: + - "gitea-shared-storage" + - "data-freeleaps-prod-gitea-postgresql-ha-postgresql-0" + snapshotClass: "csi-azuredisk-vsc" + timeout: 300 # seconds + +# Labels and annotations +labels: {} +annotations: {} \ No newline at end of file diff --git a/jobs/freeleaps-data-backup/requirements.txt b/jobs/freeleaps-data-backup/requirements.txt new file mode 100644 index 00000000..6a16511c --- /dev/null +++ b/jobs/freeleaps-data-backup/requirements.txt @@ -0,0 +1,2 @@ +kubernetes==28.1.0 +PyYAML==6.0.1 \ No newline at end of file