docs: README, k8s/README und EINRICHTUNG auf Stand main (2026-10-16)
Docker Image bauen und veroeffentlichen / lint (push) Successful in 1m41s
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 1m42s
Docker Image bauen und veroeffentlichen / test (push) Successful in 2m40s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 8m21s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 1m41s
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 1m42s
Docker Image bauen und veroeffentlichen / test (push) Successful in 2m40s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 8m21s
README: Abschnitt Projektstand (Issues #34, #33), veraltete Aussagen zu Platzhaltern und Rechnungsnummern korrigiert, CI-Gate, Rollout per set image, Client-IP hinter Cloudflare, VAT_RATE_PERCENT_SONSTIGES, Teststand. k8s/README: Image-Pinning, Abweichungen Repo/Cluster, Sicherung auf der Storage Box, SMB 3.1.1 mit seal. EINRICHTUNG: APICORE_WEBHOOK_SECRET, ERP_EXPORT_TOKEN, Abschnitt Neuen Build ausrollen. .env.example: DATABASE_POOL_SIZE. Refs #32 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
24df111495
commit
96c101804d
4 files changed
+112
-40
No files matched your search
+58
-24
@@ -9,9 +9,12 @@ alles laeuft: [../docs/EINRICHTUNG.md](../docs/EINRICHTUNG.md).
|
||||
|
||||
## Einmalige Vorbereitung
|
||||
|
||||
1. **Gitea Actions**: `.gitea/workflows/docker-build.yml` baut bei jedem
|
||||
Push auf `main` das Image `<registry>/<owner>/wabenhain:latest` (+
|
||||
`:sha-<kurzhash>`, `:vX.Y.Z` bei Tags) in der Gitea-Container-Registry.
|
||||
1. **Gitea Actions**: `.gitea/workflows/docker-build.yml` prueft bei jedem
|
||||
Push auf `main` lint, audit, typecheck und test und baut erst danach das
|
||||
Image `<registry>/<owner>/wabenhain:latest` (+ `:sha-<kurzhash>`,
|
||||
`:vX.Y.Z` bei Tags) in der Gitea-Container-Registry. Daneben entsteht
|
||||
mit denselben Tags `wabenhain-tools` fuer die CronJobs in
|
||||
`07-backup-cronjob.yaml`.
|
||||
Ein einzelnes Image reicht fuer `05-app-deployment.yaml` — es traegt
|
||||
sowohl die Next.js-App als auch die Prisma-CLI/tsx und migriert/seedet
|
||||
die Datenbank beim Start selbst (siehe Punkt 2 unten), kein separates
|
||||
@@ -53,8 +56,9 @@ alles laeuft: [../docs/EINRICHTUNG.md](../docs/EINRICHTUNG.md).
|
||||
fuer Produkt- und Erntenfotos.
|
||||
|
||||
6. `image:` in `05-app-deployment.yaml` zeigt bereits auf
|
||||
`git.encry.de/nick.rosenloecher/wabenhain` — nur noch den Tag pruefen
|
||||
(`:latest` passt fuer den jeweils neuesten `main`-Build). Die Domain steht
|
||||
`git.encry.de/nick.rosenloecher/wabenhain` und ist auf einen festen Build
|
||||
gepinnt (`:sha-<commit>@sha256:<digest>`), nie auf `:latest` — siehe
|
||||
„Bei einem neuen Image“ unten. Die Domain steht
|
||||
in `06-ingress.yaml` (`host:` und `tls.hosts`) und in `01-configmap.yaml`
|
||||
(`NEXT_PUBLIC_SITE_URL`) bereits als `wabenhain.com` — beide Stellen
|
||||
muessen uebereinstimmen, sonst zeigen Sitemap, Canonical-Tags und die
|
||||
@@ -91,10 +95,25 @@ der erste Pod bereit ist.
|
||||
|
||||
## Bei einem neuen Image (Redeploy)
|
||||
|
||||
Das Deployment ist auf Tag **und** Digest gepinnt. Ein `rollout restart`
|
||||
holt deshalb kein neues Image, sondern startet nur denselben Build neu (das
|
||||
genuegt nach einer Aenderung an Secret oder ConfigMap). Einen neuen Build
|
||||
rollt man so aus:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment/wabenhain-app -n wabenhain
|
||||
# Digest des gewuenschten Builds aus der Gitea-Registry (Paket "wabenhain",
|
||||
# Tag sha-<kurzer Commit>) uebernehmen:
|
||||
kubectl -n wabenhain set image deploy/wabenhain-app \
|
||||
app=git.encry.de/nick.rosenloecher/wabenhain:sha-<commit>@sha256:<digest>
|
||||
kubectl -n wabenhain rollout status deploy/wabenhain-app
|
||||
```
|
||||
|
||||
Danach dieselbe Zeile in `05-app-deployment.yaml` nachziehen und
|
||||
committen, damit Repo und Cluster nicht auseinanderlaufen. Laufen die
|
||||
CronJobs aus `07-backup-cronjob.yaml`, gehoert ihr `wabenhain-tools`-Image
|
||||
auf denselben Build (sie nutzen denselben Prisma-Client und muessen zum
|
||||
Datenbankschema passen).
|
||||
|
||||
Migrationen + Seed laufen automatisch erneut beim Start jedes neuen Pods
|
||||
(idempotent, siehe oben).
|
||||
|
||||
@@ -114,13 +133,24 @@ Redeploy.
|
||||
Zustand im Prozessspeicher, nicht in einem gemeinsamen Store. Mehrere
|
||||
Replicas wuerden das Limit unbemerkt proportional aufweichen. Fuer
|
||||
horizontale Skalierung zuerst `lib/rate-limit.ts` auf Redis o.ae. umstellen.
|
||||
- Kein `HorizontalPodAutoscaler`, kein `PodDisruptionBudget`, keine
|
||||
`NetworkPolicy` — je nach Cluster/Anforderungen ergaenzen.
|
||||
- Kein `HorizontalPodAutoscaler`, kein `PodDisruptionBudget`. Die
|
||||
`NetworkPolicy`s (`default-deny-ingress`, `allow-same-namespace`,
|
||||
`allow-from-traefik`) und die Traefik-Middleware `cloudflare-only` liegen
|
||||
nicht hier, sondern in den Cluster-Manifesten (Haertung vom 2026-10-12,
|
||||
Ordner `rke2-cluster/`, nicht unter Git).
|
||||
- Repo und Cluster weichen voneinander ab (Stand 2026-10-16): Im Cluster
|
||||
sind `wabenhain-uploads` (5Gi) und `wabenhain-invoices` (50Gi)
|
||||
`ReadWriteMany` auf SMB mit eigenen PersistentVolumes, hier stehen
|
||||
`ReadWriteOnce`-Claims gegen die Default-StorageClass (5Gi/2Gi).
|
||||
`STRIPE_PUBLISHABLE_KEY` liegt im Cluster im Secret, hier in der
|
||||
ConfigMap (beides funktioniert, weil die App beide Quellen per `envFrom`
|
||||
liest). Vor einem `kubectl apply` auf ein bestehendes Cluster die
|
||||
Unterschiede pruefen.
|
||||
- Die Postgres-/MySQL-StatefulSets sind Single-Instance **ohne
|
||||
Replikation**. Eine taegliche Sicherung gibt es inzwischen
|
||||
(`07-backup-cronjob.yaml`, siehe "Sicherung und Wiederherstellung"), sie
|
||||
liegt aber im selben Cluster und ersetzt damit weder Replikation noch
|
||||
eine Auslagerung an einen anderen Ort. Faellt der Knoten aus, ist der
|
||||
liegt aber auf derselben Storage Box wie die Datenbank und ersetzt damit
|
||||
weder Replikation noch eine Auslagerung an einen anderen Ort. Faellt der Knoten aus, ist der
|
||||
Shop bis zur Wiederherstellung offline. Fuer produktiven Betrieb mit
|
||||
hoeherem Anspruch einen Managed-Datenbank-Dienst oder einen dedizierten
|
||||
DB-Operator (z.B. CloudNativePG) in Erwaegung ziehen — `DATABASE_URL` in
|
||||
@@ -263,7 +293,8 @@ PersistentVolume:
|
||||
```yaml
|
||||
spec:
|
||||
mountOptions:
|
||||
- vers=3.0
|
||||
- vers=3.1.1
|
||||
- seal # SMB-Verschluesselung, die Storage Box liegt im Internet
|
||||
- uid=1001 # muss zum Containerbenutzer passen
|
||||
- gid=1001
|
||||
- dir_mode=0770
|
||||
@@ -334,9 +365,14 @@ ffmpeg -i original.mp4 -an -vf "scale='min(1920,iw)':-2" \
|
||||
|
||||
## Sicherung und Wiederherstellung
|
||||
|
||||
`07-backup-cronjob.yaml` legt jede Nacht um 03:15 Uhr eine
|
||||
gzip-komprimierte `pg_dump`-Sicherung auf einem eigenen Volume ab und
|
||||
haelt sie 30 Tage vor. Ohne diese Sicherung liegen Bestellungen,
|
||||
`07-backup-cronjob.yaml` legt jede Nacht um 03:15 Uhr (Zeitzone des
|
||||
Clusters, meist UTC) eine gzip-komprimierte `pg_dump`-Sicherung und ein
|
||||
`rechnungen-<Zeitstempel>.tar.gz` der Rechnungsablage auf einem eigenen
|
||||
Volume ab und haelt beides 30 Tage vor. Das Volume ist ein SMB-PV auf der
|
||||
Storage Box (`//u652065.your-storagebox.de/backup/Server/Disks/Customer/Wabenhain`,
|
||||
Unterordner `db-backup`). Im Cluster ist der Job seit 2026-10-10
|
||||
angelegt; die letzten drei Laeufe waren erfolgreich, zuletzt am 2026-10-16
|
||||
um 03:15 UTC. Ohne diese Sicherung liegen Bestellungen,
|
||||
Rechnungen und Kundenkonten in einem einzelnen PersistentVolume ohne
|
||||
jedes Netz — und Rechnungen unterliegen in Deutschland einer
|
||||
zehnjaehrigen Aufbewahrungspflicht (§ 147 AO).
|
||||
@@ -354,16 +390,14 @@ kubectl logs -n wabenhain -l job-name --tail=50 # Ausgabe des letzten Laufs
|
||||
kubectl create job --from=cronjob/wabenhain-db-backup manuell-$(date +%s) -n wabenhain
|
||||
```
|
||||
|
||||
**Aus dem Cluster herausziehen.** Die Sicherung im Cluster schuetzt gegen
|
||||
Volume-Verlust und Bedienfehler, nicht gegen den Ausfall des gesamten
|
||||
Clusters oder Standorts. Deshalb regelmaessig (oder per eigenem Cron auf
|
||||
einem Rechner ausserhalb) kopieren:
|
||||
|
||||
```bash
|
||||
POD=$(kubectl get pod -n wabenhain -l job-name -o name | head -1)
|
||||
kubectl exec -n wabenhain "$POD" -- ls -1 /backup
|
||||
kubectl cp wabenhain/"${POD#pod/}":/backup/wabenhain-2026-01-31_0315.sql.gz ./wabenhain-2026-01-31.sql.gz
|
||||
```
|
||||
**Aus dem Cluster herausziehen.** Die Sicherung schuetzt gegen
|
||||
Volume-Verlust und Bedienfehler, nicht gegen den Ausfall der Storage Box
|
||||
oder des Standorts. Deshalb regelmaessig (oder per eigenem Cron auf einem
|
||||
Rechner ausserhalb) an einen zweiten Ort kopieren. Die Dateien liegen auf
|
||||
der Storage Box unter `Server/Disks/Customer/Wabenhain/db-backup/` und
|
||||
lassen sich dort direkt abholen (SMB, SFTP oder rsync der Storage Box). Ein
|
||||
`kubectl exec` in den Sicherungs-Pod geht nicht: der Pod ist nach dem Lauf
|
||||
beendet (`Completed`).
|
||||
|
||||
**Wiederherstellen.** Vorher die App anhalten, damit waehrend des
|
||||
Einspielens niemand schreibt — `--clean --if-exists` im Dump loescht die
|
||||
|
||||
Reference in new issue
Block a user