refactor(k8s): schlanker Umfang, Postgres und Medien auf SMB
Docker-Images bauen und veroeffentlichen / build-and-push (push) Successful in 2m17s
Docker-Images bauen und veroeffentlichen / build-and-push (push) Successful in 2m17s
Auf Wunsch des Betreibers auf das Noetige reduziert: StatefulSet PostgreSQL, Services, Ingress, App-Deployment, zwei SMB-PersistentVolumes (Postgres-Daten, Medien). Beibehalten nach Absprache: Worker-CronJob (Folgeraten, Reservierungsfreigabe, Mail-Wiederholung), ConfigMap und die NetworkPolicies (Sicherheitsbefund Rate-Limit-Umgehung). - Migration als initContainer im App-Deployment statt eigenem Job - Entfernt: Backup-CronJob/-Volume (NAS sichert die Freigabe), Migrations-CronJob, PodDisruptionBudget, HTTP-Redirect-Ingress und Middleware, Namespace-Objekt (einmalig von Hand anzulegen) - PostgreSQL auf SMB als bewusste, dokumentierte Ausnahme mit hard, cache=strict, SMB 3.1.1 + seal, uid 70 / 0700 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
974c403a8b
commit
c84eb1e740
12 files changed
+200
-327
No files matched your search
+31
-24
@@ -25,19 +25,27 @@ Der Build braucht Internet für `next/font` (Schriften werden eingebettet), aber
|
||||
|
||||
## 2. Einmalig einrichten
|
||||
|
||||
1. **Overlay anpassen** (`k8s/overlays/production/`): Image-Namen und Tag, Domain in `kustomization.yaml` (APP_URL) und `ingress.yaml`, `ingressClassName` (`kubectl get ingressclass`), ClusterIssuer (Beispiel `letsencrypt-prod`) und TLS-Secret-Name.
|
||||
2. **Speicher:**
|
||||
- **PostgreSQL** bekommt Blockspeicher mit ReadWriteOnce (StorageClass im StatefulSet), auf keinen Fall eine SMB-Freigabe (Begründung in `k8s/base/postgres.yaml`).
|
||||
- **Medien und Sicherungen** liegen auf SMB, über `k8s/overlays/production/smb-volumes.yaml`:
|
||||
1. csi-driver-smb installieren, Befehl steht im Kopf der Datei.
|
||||
2. Auf dem NAS die Ordner `modelcamp-media` und `modelcamp-backup` anlegen und `source` in der Datei anpassen.
|
||||
3. Das SMB-Secret anlegen: `kubectl -n artista create secret generic artista-modelcamp-smb --from-literal=username='…' --from-literal=password='…'`. Empfohlen ist ein eigener NAS-Benutzer, der nur diese Ordner sieht.
|
||||
- Die PVCs sind statisch an die PVs gebunden. Die Rechte setzen die `mountOptions`: `uid=1001` für die App, `uid=70` für den Backup-Job, der mit `0700` bzw. `0600` geschützt ist, weil die Dumps personenbezogene Daten enthalten. Auf SMB wirkt `fsGroup` nicht. Das Medienvolume ist für die App nur lesbar (`0550`/`0440`), das Backup-Volume nutzt ReadWriteOnce.
|
||||
- Die Verbindungen sind verschlüsselt (`vers=3.1.1`, `seal`). Das NAS muss SMB 3.1.1 mit Verschlüsselung erlauben, z. B. Synology unter „SMB-Verschlüsselung für Übertragung“, Windows mit `Set-SmbShare -EncryptData $true`.
|
||||
- Ohne SMB: `smb-volumes.yaml` und die beiden PVC-Patches im Overlay entfernen, dann greift die Default-StorageClass. Das Medienvolume braucht dann ReadWriteMany, sonst `replicas: 1`.
|
||||
3. **Secret anlegen:** Befehl steht in `k8s/secret.example.yaml`, besser über Sealed Secrets oder den External Secrets Operator.
|
||||
4. **Anwenden:** `kubectl apply -k k8s/overlays/production`
|
||||
5. **Migration** (siehe Abschnitt 3), danach das erste Admin-Konto:
|
||||
**Was ausgerollt wird:** StatefulSet PostgreSQL, Services (App, Postgres), Ingress, App-Deployment (mit Migration als initContainer), zwei SMB-Volumes (Postgres-Daten, Medien) und zusätzlich:
|
||||
- der Worker-CronJob, nötig für Folgeraten, Reservierungsfreigabe und Mail-Wiederholung
|
||||
- ConfigMap
|
||||
- zwei NetworkPolicies (Schutz der Rate Limits und der Datenbank)
|
||||
|
||||
Eine eigene Sicherung im Cluster gibt es nicht, die SMB-Freigabe wird auf dem NAS gesichert.
|
||||
|
||||
1. **Namespace:** `kubectl create namespace artista` und `kubectl label namespace artista pod-security.kubernetes.io/enforce=restricted`
|
||||
2. **Overlay anpassen** (`k8s/overlays/production/`): Image-Tag, Domain in `kustomization.yaml` (APP_URL) und `ingress.yaml`, `ingressClassName` (`kubectl get ingressclass`), ClusterIssuer (Beispiel `letsencrypt-prod`) und TLS-Secret-Name. Die HTTP→HTTPS-Umleitung erwartet das Manifest in Traefik selbst.
|
||||
3. **SMB-Speicher** (`k8s/overlays/production/smb-volumes.yaml`):
|
||||
1. csi-driver-smb installieren, Befehl steht im Kopf der Datei.
|
||||
2. Auf dem NAS die Ordner `modelcamp-postgres` (leer) und `modelcamp-media` anlegen und `source` in der Datei anpassen.
|
||||
3. SMB-Secret anlegen: `kubectl -n artista create secret generic artista-modelcamp-smb --from-literal=username='…' --from-literal=password='…'`. Empfohlen ist ein eigener NAS-Benutzer, der nur diese Ordner sieht.
|
||||
4. Das NAS muss SMB 3.1.1 mit Verschlüsselung erlauben (`seal`), z. B. Synology unter „SMB-Verschlüsselung für Übertragung“, Windows mit `Set-SmbShare -EncryptData $true`.
|
||||
|
||||
Die Rechte setzen die `mountOptions`, auf SMB wirkt `fsGroup` nicht: Postgres `uid=70`, `0700`/`0600`; Medien `uid=1001`, für die App nur lesbar (`0550`/`0440`).
|
||||
|
||||
**PostgreSQL auf SMB ist eine bewusste Ausnahme.** SMB garantiert Sperren und fsync nicht in jedem Fall wie ein lokales Dateisystem. Bei einem NAS- oder Netzwerkausfall während Schreibvorgängen kann die Datenbank beschädigt werden. Gemildert wird das durch `hard`, `cache=strict`, Verschlüsselung und ein 120 Sekunden langes, sauberes Herunterfahren. Die NAS-Sicherung ist nur mit **atomaren Snapshots** der Freigabe (Btrfs/ZFS) konsistent. Ein Dateikopie-Backup eines laufenden Datenverzeichnisses lässt sich nicht zuverlässig wiederherstellen. Zusätzlicher Schutz bei Bedarf: gelegentlich einen `pg_dump` ziehen (Abschnitt 7).
|
||||
4. **Secret anlegen:** Befehl steht in `k8s/secret.example.yaml`, besser über Sealed Secrets oder den External Secrets Operator.
|
||||
5. **Anwenden:** `kubectl apply -k k8s/overlays/production`. Der initContainer migriert die Datenbank vor dem App-Start.
|
||||
6. **Erstes Admin-Konto:**
|
||||
```bash
|
||||
kubectl -n artista run admin-create --rm -it --restart=Never --labels=artista/db-client=true \
|
||||
--image=git.encry.de/nick.rosenloecher/artista-modelcamp-tools:0.1.0 \
|
||||
@@ -45,17 +53,16 @@ Der Build braucht Internet für `next/font` (Schriften werden eingebettet), aber
|
||||
```
|
||||
Das einmalige Passwort wird ausgegeben. Bei der ersten Anmeldung unter `/admin` wird die 2FA eingerichtet.
|
||||
|
||||
## 3. Update ausrollen (kontrollierter Migrationsschritt)
|
||||
## 3. Update ausrollen
|
||||
|
||||
```bash
|
||||
# 1. Neuen Tag im Overlay setzen, anwenden (App rollt noch NICHT aus, solange Tag gleich bleibt)
|
||||
# Neuen Tag im Overlay setzen und anwenden. Jeder neue App-Pod migriert per
|
||||
# initContainer "migrate" zuerst die Datenbank; scheitert das, startet er nicht
|
||||
# und die alten Pods bleiben aktiv (maxUnavailable: 0).
|
||||
kubectl apply -k k8s/overlays/production
|
||||
# 2. Migration bewusst starten und abwarten
|
||||
JOB=migrate-$(date +%Y%m%d%H%M)
|
||||
kubectl -n artista create job $JOB --from=cronjob/artista-modelcamp-migrate
|
||||
kubectl -n artista wait --for=condition=complete job/$JOB --timeout=5m
|
||||
# 3. Rollout beobachten
|
||||
kubectl -n artista rollout status deploy/artista-modelcamp-app
|
||||
# Migrationsausgabe bei Bedarf:
|
||||
kubectl -n artista logs deploy/artista-modelcamp-app -c migrate
|
||||
```
|
||||
|
||||
Migrationen sind vorwärtskompatibel zu schreiben (erst Spalten ergänzen, später entfernen), damit alte und neue Pods während des Rollouts parallel laufen können.
|
||||
@@ -88,8 +95,8 @@ Ein fehlgeschlagener Lauf erscheint als fehlgeschlagener Job (`kubectl -n artist
|
||||
|
||||
## 7. Sicherung und Wiederherstellung
|
||||
|
||||
- **Sicherung:** Der CronJob `artista-modelcamp-db-backup` erstellt täglich um 3:15 Uhr einen Dump im `pg_dump`-Custom-Format nach `artista-modelcamp-db-backup` und bewahrt ihn 30 Tage auf.
|
||||
- **Aus dem Cluster sichern (regelmäßig):**
|
||||
- **Sicherung:** Die NAS-Sicherung der SMB-Freigabe übernimmt das Speichersystem. Für PostgreSQL gilt das nur mit atomaren Snapshots, siehe Abschnitt 2. Im Cluster läuft kein Backup-Job.
|
||||
- **Logischer Dump** (empfohlen vor Updates und vor dem Live-Verkauf):
|
||||
```bash
|
||||
POD=$(kubectl -n artista get pod -l app.kubernetes.io/name=artista-modelcamp-postgres -o name | head -1)
|
||||
kubectl -n artista exec $POD -- pg_dump -U artista -d artista --format=custom > artista-$(date +%F).dump
|
||||
@@ -97,9 +104,9 @@ Ein fehlgeschlagener Lauf erscheint als fehlgeschlagener Job (`kubectl -n artist
|
||||
- **Wiederherstellen:**
|
||||
1. App stoppen: `kubectl -n artista scale deploy/artista-modelcamp-app --replicas=0` und den Worker-CronJob pausieren: `kubectl -n artista patch cronjob artista-modelcamp-worker -p '{"spec":{"suspend":true}}'`
|
||||
2. Einspielen: `kubectl -n artista exec -i $POD -- pg_restore -U artista -d artista --clean --if-exists < artista-YYYY-MM-DD.dump`
|
||||
3. Migration laufen lassen (Abschnitt 3), App wieder hochskalieren, Worker fortsetzen.
|
||||
3. App wieder hochskalieren (der initContainer migriert dabei), Worker fortsetzen.
|
||||
4. Abgleich: Zahlungen, die nach dem Sicherungszeitpunkt eingingen, im Stripe-Dashboard prüfen. Stripe stellt Webhooks bis zu drei Tage erneut zu (Dashboard → Webhooks → „Erneut senden“).
|
||||
- **Medien:** Das Volume `artista-modelcamp-media` separat sichern, z. B. mit Volume-Snapshots oder der Sicherung des Speichersystems.
|
||||
- **Medien:** liegen im SMB-Ordner `modelcamp-media` und sind in der NAS-Sicherung enthalten.
|
||||
- **Wiederherstellung üben:** mindestens einmal vor dem Live-Verkauf in einen Test-Namespace.
|
||||
|
||||
## 8. Stornierung und Erstattung
|
||||
|
||||
Reference in new issue
Block a user