feat: Docker-, Kubernetes- und Gitea-Actions-Deployment-Pipeline
Docker Image bauen und veroeffentlichen / build-and-push (push) Waiting to run
Docker Image bauen und veroeffentlichen / build-and-push (push) Waiting to run
Dockerfile (Multi-Stage, "output: standalone"), docker-compose.yml mit Caddy als einzigem nach aussen offenem Reverse-Proxy (verhindert X-Forwarded-For-Faelschung fuer das App-eigene Rate-Limiting), Gitea Actions Workflow der bei jedem Push zwei Image-Tags in die Gitea- Container-Registry baut (schlankes Laufzeit-Image + ein Migrations- Image mit Prisma-CLI), sowie rohe Kubernetes-Manifeste (Namespace, ConfigMap, Secret-Vorlage, Postgres-/MySQL-StatefulSet, Migrations-Job, App-Deployment, Ingress) fuer den Cluster-Betrieb. Siehe k8s/README.md fuer den vollstaendigen Einrichtungs-/Deployment-Ablauf.
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: wabenhain
|
||||
@@ -0,0 +1,23 @@
|
||||
# Nicht-geheime Konfiguration. Geheimnisse (Passwoerter, API-Keys) gehoeren
|
||||
# in 02-secret.yaml (aus 02-secret.example.yaml kopiert), NIE hierher.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: wabenhain-config
|
||||
namespace: wabenhain
|
||||
data:
|
||||
# "postgresql" oder "mysql" — muss zum Service passen, den du unter
|
||||
# 03-postgres.yaml/03-mysql.yaml tatsaechlich ausrollst, UND zum
|
||||
# DATABASE_URL-Host in 02-secret.yaml. Ein einzelnes Image traegt beide
|
||||
# Prisma-Client-Varianten, siehe src/lib/prisma.ts.
|
||||
DATABASE_PROVIDER: "postgresql"
|
||||
NODE_ENV: "production"
|
||||
# Oeffentliche URL des Shops (fuer absolute Links in E-Mails/Sitemaps/CSP).
|
||||
NEXT_PUBLIC_SITE_URL: "https://shop.example.com"
|
||||
SMTP_PORT: "587"
|
||||
SMTP_FROM: "Wabenhain <[email protected]>"
|
||||
# Optional, siehe README-Abschnitte "Google-Rezensionen"/"CAPTCHA" — leer
|
||||
# lassen, wenn nicht genutzt, die App faellt automatisch zurueck.
|
||||
GOOGLE_PLACES_API_KEY: ""
|
||||
GOOGLE_PLACE_ID: ""
|
||||
NEXT_PUBLIC_TURNSTILE_SITE_KEY: ""
|
||||
@@ -0,0 +1,44 @@
|
||||
# Vorlage OHNE echte Geheimnisse — analog zu .env.example. Kopieren nach
|
||||
# 02-secret.yaml (per .gitignore ausgeschlossen, NIE committen), Werte
|
||||
# ausfuellen, dann "kubectl apply -f k8s/02-secret.yaml".
|
||||
#
|
||||
# Alternative ohne Datei mit Klartext-Secrets auf der Platte:
|
||||
# kubectl create secret generic wabenhain-secrets -n wabenhain \
|
||||
# --from-literal=DATABASE_URL='postgresql://wabenhain:CHANGE-ME@wabenhain-postgres:5432/wabenhain?schema=public' \
|
||||
# --from-literal=DB_PASSWORD='CHANGE-ME' \
|
||||
# --from-literal=SESSION_SECRET="$(openssl rand -base64 32)" \
|
||||
# --from-literal=ADMIN_EMAIL='[email protected]' \
|
||||
# --from-literal=ADMIN_PASSWORD="$(openssl rand -base64 24 | tr -d '=+/' | head -c 28)" \
|
||||
# --from-literal=STRIPE_SECRET_KEY='' \
|
||||
# --from-literal=STRIPE_PUBLISHABLE_KEY='' \
|
||||
# --from-literal=STRIPE_WEBHOOK_SECRET='' \
|
||||
# --from-literal=SMTP_HOST='' --from-literal=SMTP_USER='' --from-literal=SMTP_PASSWORD='' \
|
||||
# --from-literal=TURNSTILE_SECRET_KEY=''
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: wabenhain-secrets
|
||||
namespace: wabenhain
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Host "wabenhain-postgres" bzw. "wabenhain-mysql" passend zum Service aus
|
||||
# 03-postgres.yaml/03-mysql.yaml waehlen; Port 5432 (Postgres) bzw. 3306
|
||||
# (MySQL). Muss zu DATABASE_PROVIDER in 01-configmap.yaml passen.
|
||||
DATABASE_URL: "postgresql://wabenhain:CHANGE-ME@wabenhain-postgres:5432/wabenhain?schema=public"
|
||||
# Dasselbe Passwort wie oben in DATABASE_URL — wird vom Postgres-/MySQL-
|
||||
# StatefulSet (je nachdem, welches du anwendest) zum Anlegen des
|
||||
# Datenbank-Nutzers verwendet.
|
||||
DB_PASSWORD: "CHANGE-ME"
|
||||
# Mindestens 32 Zeichen, z.B.: openssl rand -base64 32
|
||||
SESSION_SECRET: "CHANGE-ME-min-32-zeichen-zufaelliger-string"
|
||||
# Unvorhersehbar waehlen (nicht "admin@..."), siehe README "Admin-Zugang" —
|
||||
# sonst koennte sich jemand die erwartete Adresse vorregistrieren.
|
||||
ADMIN_EMAIL: "[email protected]"
|
||||
ADMIN_PASSWORD: "CHANGE-ME"
|
||||
STRIPE_SECRET_KEY: ""
|
||||
STRIPE_PUBLISHABLE_KEY: ""
|
||||
STRIPE_WEBHOOK_SECRET: ""
|
||||
SMTP_HOST: ""
|
||||
SMTP_USER: ""
|
||||
SMTP_PASSWORD: ""
|
||||
TURNSTILE_SECRET_KEY: ""
|
||||
@@ -0,0 +1,70 @@
|
||||
# Alternative zu 03-postgres.yaml (DATABASE_PROVIDER=mysql in
|
||||
# 01-configmap.yaml). Nur eines von beiden anwenden.
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: wabenhain-mysql
|
||||
namespace: wabenhain
|
||||
spec:
|
||||
serviceName: wabenhain-mysql
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: wabenhain-mysql
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: wabenhain-mysql
|
||||
spec:
|
||||
containers:
|
||||
- name: mysql
|
||||
image: mysql:8.4.11
|
||||
ports:
|
||||
- containerPort: 3306
|
||||
env:
|
||||
- name: MYSQL_USER
|
||||
value: wabenhain
|
||||
- name: MYSQL_DATABASE
|
||||
value: wabenhain
|
||||
- name: MYSQL_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: wabenhain-secrets
|
||||
key: DB_PASSWORD
|
||||
- name: MYSQL_RANDOM_ROOT_PASSWORD
|
||||
value: "yes"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/mysql
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["mysqladmin", "ping", "-h", "localhost", "-uwabenhain", "-p$(MYSQL_PASSWORD)"]
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: wabenhain-mysql
|
||||
namespace: wabenhain
|
||||
spec:
|
||||
clusterIP: None
|
||||
selector:
|
||||
app: wabenhain-mysql
|
||||
ports:
|
||||
- port: 3306
|
||||
targetPort: 3306
|
||||
@@ -0,0 +1,72 @@
|
||||
# Standard-Datenbank (DATABASE_PROVIDER=postgresql in 01-configmap.yaml).
|
||||
# Nur anwenden, wenn du Postgres nutzt — fuer MySQL stattdessen 03-mysql.yaml
|
||||
# anwenden (nicht beide gleichzeitig).
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: wabenhain-postgres
|
||||
namespace: wabenhain
|
||||
spec:
|
||||
serviceName: wabenhain-postgres
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: wabenhain-postgres
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: wabenhain-postgres
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:16.15-alpine
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
env:
|
||||
- name: POSTGRES_USER
|
||||
value: wabenhain
|
||||
- name: POSTGRES_DB
|
||||
value: wabenhain
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: wabenhain-secrets
|
||||
key: DB_PASSWORD
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["pg_isready", "-U", "wabenhain"]
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
# Bei Bedarf anpassen — abhaengig vom StorageClass deines Clusters.
|
||||
storage: 5Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: wabenhain-postgres
|
||||
namespace: wabenhain
|
||||
spec:
|
||||
# Headless (kein eigener ClusterIP) — Standard fuer StatefulSets, DNS-Name
|
||||
# "wabenhain-postgres.wabenhain.svc.cluster.local" bleibt stabil.
|
||||
clusterIP: None
|
||||
selector:
|
||||
app: wabenhain-postgres
|
||||
ports:
|
||||
- port: 5432
|
||||
targetPort: 5432
|
||||
@@ -0,0 +1,37 @@
|
||||
# Einmalig VOR jedem Deployment (bzw. bei jedem neuen Image-Tag) ausfuehren:
|
||||
# kubectl delete job wabenhain-migrate -n wabenhain --ignore-not-found
|
||||
# kubectl apply -f k8s/04-migrate-job.yaml
|
||||
# kubectl wait --for=condition=complete job/wabenhain-migrate -n wabenhain --timeout=120s
|
||||
# "kubectl delete...--ignore-not-found" zuerst, weil Jobs nicht ueberschrieben
|
||||
# werden koennen (Kubernetes-Job-Objekte sind unveraenderlich) — ohne das
|
||||
# schlaegt ein erneutes "kubectl apply" mit demselben Namen fehl.
|
||||
#
|
||||
# Nutzt das ":migrate-<sha>"-Image aus der Gitea-Actions-Pipeline (hat die
|
||||
# Prisma-CLI + tsx an Bord, die "pnpm db:deploy"/"pnpm db:seed" brauchen —
|
||||
# das schlanke Laufzeit-Image aus 05-app-deployment.yaml bewusst nicht,
|
||||
# siehe Dockerfile). image: unten mit der tatsaechlichen Registry/Tag
|
||||
# ersetzen.
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: wabenhain-migrate
|
||||
namespace: wabenhain
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: migrate
|
||||
image: git.example.com/OWNER/wabenhain:migrate-REPLACE-ME
|
||||
# prisma migrate deploy (nicht "db push"): wendet die committeten
|
||||
# Migrationsdateien nicht-interaktiv an, das ist Prismas
|
||||
# vorgesehener Weg fuer Produktions-Deployments. db:seed danach
|
||||
# legt/erneuert nur das Admin-Konto + Beispieldaten, ist idempotent
|
||||
# (siehe prisma/seed.ts).
|
||||
command: ["sh", "-c", "pnpm db:deploy && pnpm db:seed"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: wabenhain-config
|
||||
- secretRef:
|
||||
name: wabenhain-secrets
|
||||
@@ -0,0 +1,67 @@
|
||||
# image: unten mit der tatsaechlichen Registry/Tag aus der Gitea-Actions-
|
||||
# Pipeline ersetzen (das schlanke Laufzeit-Image, NICHT ":migrate-...").
|
||||
# Vor dem ersten Rollout einmal den Migrations-Job (04-migrate-job.yaml)
|
||||
# erfolgreich durchlaufen lassen, sonst startet die App gegen eine leere,
|
||||
# ungemigrateet Datenbank.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: wabenhain-app
|
||||
namespace: wabenhain
|
||||
spec:
|
||||
# ACHTUNG bevor du hochskalierst: lib/rate-limit.ts haelt Login-/2FA-/
|
||||
# Checkout-Limits aktuell im Prozessspeicher, nicht in einem gemeinsamen
|
||||
# Store. Mit replicas > 1 verteilt sich derselbe Nutzer per Load-Balancing
|
||||
# auf mehrere Prozesse mit je eigenem Zaehler — das Limit wird effektiv um
|
||||
# den Skalierungsfaktor schwaecher (2 Replicas = 2x mehr Versuche moeglich),
|
||||
# ohne dass das im Frontend sichtbar waere. Fuer replicas > 1 zuerst
|
||||
# lib/rate-limit.ts auf einen gemeinsamen Store (z.B. Redis) umstellen.
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: wabenhain-app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: wabenhain-app
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: git.example.com/OWNER/wabenhain:latest
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: wabenhain-config
|
||||
- secretRef:
|
||||
name: wabenhain-secrets
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: wabenhain-app
|
||||
namespace: wabenhain
|
||||
spec:
|
||||
selector:
|
||||
app: wabenhain-app
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 3000
|
||||
@@ -0,0 +1,37 @@
|
||||
# Setzt einen bereits im Cluster laufenden Ingress-Controller voraus (z.B.
|
||||
# ingress-nginx) sowie optional cert-manager fuer automatisches TLS. Host
|
||||
# unten anpassen (muss zu NEXT_PUBLIC_SITE_URL in 01-configmap.yaml passen).
|
||||
#
|
||||
# X-Forwarded-For: ingress-nginx setzt den Header standardmaessig korrekt
|
||||
# (verwirft einen vom Client mitgeschickten Wert), lib/rate-limit.ts vertraut
|
||||
# genau darauf — bei einem ANDEREN Ingress-Controller/zusaetzlichen Proxy
|
||||
# davor unbedingt pruefen, dass er sich genauso verhaelt, siehe Kommentar in
|
||||
# lib/rate-limit.ts.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: wabenhain
|
||||
namespace: wabenhain
|
||||
annotations:
|
||||
# Nur relevant, falls cert-manager installiert ist — sonst diese beiden
|
||||
# Zeilen und den "tls:"-Block unten entfernen und Host per HTTP betreiben
|
||||
# oder TLS auf andere Weise terminieren.
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- shop.example.com
|
||||
secretName: wabenhain-tls
|
||||
rules:
|
||||
- host: shop.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: wabenhain-app
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,92 @@
|
||||
# Wabenhain in Kubernetes
|
||||
|
||||
Rohe Manifeste, mit `kubectl apply -f k8s/<datei>` einzeln oder als Ordner
|
||||
anwendbar. Nummerierung = empfohlene Reihenfolge.
|
||||
|
||||
## Einmalige Vorbereitung
|
||||
|
||||
1. **Gitea Actions**: `.gitea/workflows/docker-build.yml` baut bei jedem
|
||||
Push auf `main` zwei Image-Tags in der Gitea-Container-Registry:
|
||||
- `<registry>/<owner>/wabenhain:latest` (+ `:sha-<kurzhash>`, `:vX.Y.Z` bei
|
||||
Tags) — das schlanke Laufzeit-Image fuer `05-app-deployment.yaml`.
|
||||
- `<registry>/<owner>/wabenhain:migrate-<sha>` — enthaelt zusaetzlich die
|
||||
Prisma-CLI/tsx, fuer `04-migrate-job.yaml`.
|
||||
|
||||
Voraussetzung: ein Repo-/Org-Secret `REGISTRY_TOKEN` in Gitea anlegen
|
||||
(Personal Access Token mit `package`-Scope, unter Benutzereinstellungen →
|
||||
Anwendungen → Access Tokens erzeugen, dann unter Repo-Einstellungen →
|
||||
Actions → Secrets als `REGISTRY_TOKEN` hinterlegen). Der automatisch von
|
||||
Gitea Actions bereitgestellte `GITEA_TOKEN` reicht dafuer aktuell NICHT
|
||||
aus (fehlendes Package-Write-Recht, Stand heute).
|
||||
|
||||
2. **Ein Docker-Image ist Provider-uebergreifend**: Prisma generiert den
|
||||
Client Provider-spezifisch — ein `.next/standalone`-Build kann seinen
|
||||
generierten Client nicht zur Laufzeit wechseln. Deshalb generiert
|
||||
`scripts/generate-prisma-clients.mjs` (Teil von `pnpm build`, laeuft also
|
||||
automatisch bei jedem Image-Build) BEIDE Varianten ins selbe Image; die
|
||||
Wahl zwischen ihnen passiert zur Laufzeit ueber `DATABASE_PROVIDER`, siehe
|
||||
`src/lib/prisma.ts`. Ein Image reicht also fuer Postgres UND MySQL — nur
|
||||
`DATABASE_PROVIDER`/`DATABASE_URL` in `01-configmap.yaml`/
|
||||
`02-secret.yaml` muessen zur tatsaechlich gewaehlten Datenbank passen.
|
||||
|
||||
3. `k8s/02-secret.example.yaml` nach `k8s/02-secret.yaml` kopieren (per
|
||||
`.gitignore` ausgeschlossen — NIE mit echten Werten committen) und
|
||||
ausfuellen. Alternativ direkt per `kubectl create secret`, siehe
|
||||
Kommentar in der Beispieldatei.
|
||||
|
||||
4. `01-configmap.yaml`: `DATABASE_PROVIDER` (postgresql/mysql) und
|
||||
`NEXT_PUBLIC_SITE_URL` (echte Domain) anpassen.
|
||||
|
||||
5. In `04-migrate-job.yaml` und `05-app-deployment.yaml` die
|
||||
`image:`-Platzhalter (`git.example.com/OWNER/wabenhain:...`) durch die
|
||||
tatsaechliche Registry/Owner/Tag aus Schritt 1 ersetzen. In
|
||||
`06-ingress.yaml` `shop.example.com` durch die echte Domain ersetzen
|
||||
(zweimal: `host:` und `tls.hosts`).
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
kubectl apply -f k8s/00-namespace.yaml
|
||||
kubectl apply -f k8s/01-configmap.yaml
|
||||
kubectl apply -f k8s/02-secret.yaml
|
||||
|
||||
# Nur EINE der beiden Datenbanken anwenden, passend zu DATABASE_PROVIDER:
|
||||
kubectl apply -f k8s/03-postgres.yaml
|
||||
# kubectl apply -f k8s/03-mysql.yaml
|
||||
|
||||
kubectl wait --for=condition=ready pod -l app=wabenhain-postgres -n wabenhain --timeout=120s
|
||||
|
||||
# Migrationen + Seed (Admin-Konto, Beispieldaten) — vor jedem neuen
|
||||
# Image-Tag erneut ausfuehren:
|
||||
kubectl delete job wabenhain-migrate -n wabenhain --ignore-not-found
|
||||
kubectl apply -f k8s/04-migrate-job.yaml
|
||||
kubectl wait --for=condition=complete job/wabenhain-migrate -n wabenhain --timeout=120s
|
||||
|
||||
kubectl apply -f k8s/05-app-deployment.yaml
|
||||
kubectl apply -f k8s/06-ingress.yaml
|
||||
```
|
||||
|
||||
## Bei einem neuen Image (Redeploy)
|
||||
|
||||
```bash
|
||||
kubectl delete job wabenhain-migrate -n wabenhain --ignore-not-found
|
||||
kubectl apply -f k8s/04-migrate-job.yaml
|
||||
kubectl wait --for=condition=complete job/wabenhain-migrate -n wabenhain --timeout=120s
|
||||
kubectl rollout restart deployment/wabenhain-app -n wabenhain
|
||||
```
|
||||
|
||||
## Bekannte Grenzen dieser Manifeste
|
||||
|
||||
- **`replicas: 1`** in `05-app-deployment.yaml`, bewusst nicht mehr: das
|
||||
Login-/2FA-/Checkout-Rate-Limiting (`lib/rate-limit.ts`) haelt seinen
|
||||
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.
|
||||
- Die Postgres-/MySQL-StatefulSets sind Single-Instance ohne Replikation/
|
||||
Backup-Automation. Fuer produktiven Betrieb einen Managed-Datenbank-Dienst
|
||||
oder einen dedizierten DB-Operator (z.B. CloudNativePG) in Erwaegung
|
||||
ziehen — `DATABASE_URL` in `02-secret.yaml` einfach auf den externen
|
||||
Endpunkt zeigen lassen, `03-postgres.yaml`/`03-mysql.yaml` dann nicht
|
||||
anwenden.
|
||||
Reference in New Issue
Block a user