RAILIANCE-WP-0016: finish unattended hybrid Option A backups
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run

Core in-cluster CronJobs with cnpg-option-a-backup image + NetworkPolicy;
R01 activity-core schedule enabled; status healthy; evidence recorded.
This commit is contained in:
tegwick 2026-07-22 20:34:24 +02:00
parent 65e71884a9
commit 4a06c0884a
6 changed files with 196 additions and 158 deletions

View file

@ -3,10 +3,10 @@
# Decided approach (not barman ScheduledBackup). Applies to CoulombCore
# databases namespace clusters: apps-pg, gitea-db, net-kingdom-pg, state-hub-db.
#
# Unattended path (primary): workstation cron (same pattern as forgejo-backup).
# CoulombCore pods cannot reach github.com / Alpine CDN to fetch `age`, so
# in-cluster CronJobs are shipped **suspended**. Enable only after an image
# with kubectl+age+curl is available in-cluster.
# Unattended path (primary):
# - CoulombCore: in-cluster CronJobs using image cnpg-option-a-backup:v1
# (kubectl+age+curl; import via k3s ctr; imagePullPolicy Never)
# - railiance01: activity-core ActivityDefinition daily-cnpg-option-a-backup
#
# Prereqs:
# 1. OpenBao lane CCR-2026-0004 resolvable
@ -28,8 +28,8 @@ metadata:
app.kubernetes.io/part-of: railiance-apps
railiance.apps/backup-lane: option-a
data:
mode: workstation-cron
next_mode: activity-core
mode: in-cluster-cron
next_mode: activity-core+in-cluster
schedule: "30 2 * * *"
command: "cd $HOME/railiance-apps && make cnpg-logical-backup"
activity_core_definition: daily-cnpg-option-a-backup
@ -109,16 +109,12 @@ data:
WORK=/tmp/backup-work
mkdir -p "$WORK"
# Prefer distro package (cluster often cannot reach github.com).
if ! command -v age >/dev/null 2>&1; then
echo "install age via apk"
apk add --no-cache age >/dev/null
fi
if ! command -v age >/dev/null 2>&1; then
echo "ERROR: age not available (apk install failed; github fallback disabled)" >&2
# Image cnpg-option-a-backup:v1 ships /usr/local/bin/age (no egress installs).
AGE_BIN="$(command -v age || true)"
if [ -z "$AGE_BIN" ]; then
echo "ERROR: age binary missing from image PATH" >&2
exit 1
fi
AGE_BIN="$(command -v age)"
NC_TOKEN="$(cat /secrets/offsite/NC_WEBDAV_TOKEN)"
NC_URL="$(cat /secrets/offsite/NC_WEBDAV_URL)"
@ -189,7 +185,7 @@ metadata:
railiance.apps/backup-cluster: apps-pg
spec:
schedule: "30 2 * * *"
suspend: true
suspend: false
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
@ -207,8 +203,8 @@ spec:
restartPolicy: Never
containers:
- name: backup
image: alpine/k8s:1.31.12
imagePullPolicy: IfNotPresent
image: cnpg-option-a-backup:v1
imagePullPolicy: Never
env:
- name: CLUSTER
value: apps-pg
@ -251,7 +247,7 @@ metadata:
railiance.apps/backup-cluster: gitea-db
spec:
schedule: "35 2 * * *"
suspend: true
suspend: false
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
@ -269,8 +265,8 @@ spec:
restartPolicy: Never
containers:
- name: backup
image: alpine/k8s:1.31.12
imagePullPolicy: IfNotPresent
image: cnpg-option-a-backup:v1
imagePullPolicy: Never
env:
- name: CLUSTER
value: gitea-db
@ -313,7 +309,7 @@ metadata:
railiance.apps/backup-cluster: net-kingdom-pg
spec:
schedule: "40 2 * * *"
suspend: true
suspend: false
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
@ -331,8 +327,8 @@ spec:
restartPolicy: Never
containers:
- name: backup
image: alpine/k8s:1.31.12
imagePullPolicy: IfNotPresent
image: cnpg-option-a-backup:v1
imagePullPolicy: Never
env:
- name: CLUSTER
value: net-kingdom-pg
@ -375,7 +371,7 @@ metadata:
railiance.apps/backup-cluster: state-hub-db
spec:
schedule: "45 2 * * *"
suspend: true
suspend: false
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
@ -393,8 +389,8 @@ spec:
restartPolicy: Never
containers:
- name: backup
image: alpine/k8s:1.31.12
imagePullPolicy: IfNotPresent
image: cnpg-option-a-backup:v1
imagePullPolicy: Never
env:
- name: CLUSTER
value: state-hub-db

View file

@ -0,0 +1,72 @@
# Allow Option A backup Jobs to reach kube-api, DNS, and HTTPS offsite (Nextcloud).
# databases/ has default-deny; CNPG cluster pods already have kube-api egress by label.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-cnpg-option-a-backup
namespace: databases
labels:
app.kubernetes.io/part-of: railiance-apps
railiance.apps/backup-lane: option-a
spec:
podSelector:
matchLabels:
railiance.apps/backup-lane: option-a
policyTypes:
- Egress
egress:
# kube-api (in-cluster service + node port forms)
- ports:
- port: 443
protocol: TCP
- port: 6443
protocol: TCP
# DNS
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
# Nextcloud WebDAV HTTPS (and general HTTPS for future mirrors)
- ports:
- port: 443
protocol: TCP
to:
- namespaceSelector: {}
# allow all namespaces + external via empty podSelector not possible;
# for external CIDR use 0.0.0.0/0
- ipBlock:
cidr: 0.0.0.0/0
---
# Some CNI stacks need separate CIDR-only rules for kube-api ClusterIP
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-cnpg-option-a-backup-kubeapi-cidr
namespace: databases
labels:
app.kubernetes.io/part-of: railiance-apps
railiance.apps/backup-lane: option-a
spec:
podSelector:
matchLabels:
railiance.apps/backup-lane: option-a
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.43.0.0/16
ports:
- port: 443
protocol: TCP
- port: 6443
protocol: TCP
- to:
- ipBlock:
cidr: 10.42.0.0/16
ports:
- port: 443
protocol: TCP
- port: 6443
protocol: TCP