Docker-Images bauen und veroeffentlichen / build-and-push (push) Successful in 2m49s
- Team-Abschnitt mit neun Personen, Eric Heitz als Head of Modelcamp, Portraets im Rundbogen mit Initialen-Platzhaltern bis Fotos vorliegen - Unterkunftsbilder der Villa La Chiesina lokal in public/images/unterkunft (vom Auftraggeber freigegeben), Hero-Leitbild, Ausstattung, Booking-Link - Migration: Termin 08.05.-15.05.2027, Ort Arcidosso (Toskana), eigenes Bett im geteilten Zimmer, 13 Plaetze; Admin-Werte bleiben erhalten - Reveal: kein Hydration-Fehler mehr bei reduzierter Bewegung - Doku zu offenen Punkten aktualisiert Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
110 lines
8.3 KiB
Markdown
110 lines
8.3 KiB
Markdown
# Betrieb auf RKE2
|
|
|
|
## 1. Images bauen
|
|
|
|
**Automatisch (Gitea Actions):** `.gitea/workflows/docker-build.yml` baut bei jedem Push auf `main`, bei Tags `v*` oder manuell per „Run workflow“ beide Images und pusht sie in die Gitea-Registry:
|
|
|
|
- `main` ergibt die Tags `latest` und `sha-<kurz>`
|
|
- `v0.1.0` ergibt zusätzlich den Tag `0.1.0`
|
|
|
|
Einmalig einrichten:
|
|
- ein Personal Access Token mit Scope `package` als Repo-Secret `REGISTRY_TOKEN` hinterlegen (Repo-Einstellungen → Actions → Secrets)
|
|
- einen Runner mit dem Label `docker-builder` bereitstellen (wie bei Wabenhain)
|
|
|
|
Die Manifeste in `k8s/` nutzen den Tag `latest`. Für feste Versionen dort `:latest` durch z. B. `:0.1.0` ersetzen (`git tag v0.1.0 && git push origin v0.1.0`).
|
|
|
|
**Manuell:**
|
|
|
|
```bash
|
|
docker build --target runner -t git.encry.de/nick.rosenloecher/artista-modelcamp:latest .
|
|
docker build --target tools -t git.encry.de/nick.rosenloecher/artista-modelcamp-tools:latest .
|
|
docker push … # beide Images, immer mit identischem Tag
|
|
```
|
|
|
|
Der Build braucht Internet für `next/font` (Schriften werden eingebettet), aber weder Datenbank noch Secrets.
|
|
|
|
## 2. Einmalig einrichten
|
|
|
|
**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.
|
|
|
|
**Schritt für Schritt zum Kopieren: [k8s/README.md](../k8s/README.md).** Die Manifeste liegen flach in `k8s/` (`00-namespace` bis `08-networkpolicy`, Namensschema wie bei Wabenhain) und werden in dieser Reihenfolge einzeln angewendet. Die Secrets kommen aus `k8s/02-secret.yaml.example` und werden nicht eingecheckt. Pod Security: Erzwungen wird `baseline`, damit der Zertifikats-Solver von cert-manager starten kann. `restricted` wird nur gemeldet, die eigenen Pods erfüllen es.
|
|
|
|
Zu PostgreSQL:
|
|
- **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`, SMB-Verschlüsselung (`seal`) und ein 120 Sekunden langes, sauberes Herunterfahren. Die Rechte setzen die `mountOptions`, weil `fsGroup` auf SMB nicht wirkt: Postgres `uid=70`, `0700`; Medien `uid=1001`, nur lesbar.
|
|
- **Sicherung:** Die NAS-Sicherung ist für die laufende Datenbank nur mit **atomaren Snapshots** der Freigabe (Btrfs/ZFS) konsistent. Zusätzlicher Schutz bei Bedarf: gelegentlich einen `pg_dump` ziehen (Abschnitt 7).
|
|
|
|
## 3. Update ausrollen
|
|
|
|
```bash
|
|
kubectl -n artista rollout restart deploy/artista-modelcamp-app
|
|
kubectl -n artista rollout status deploy/artista-modelcamp-app
|
|
kubectl -n artista logs deploy/artista-modelcamp-app -c migrate # Migrationsausgabe
|
|
```
|
|
|
|
Jeder neue App-Pod migriert per initContainer zuerst die Datenbank. Scheitert das, startet der Pod nicht, und die alten Pods laufen weiter.
|
|
|
|
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.
|
|
|
|
## 4. Stripe
|
|
|
|
- **Schlüssel:** Im Test `sk_test_…`, live `sk_live_…` (nur mit `APP_ENV=production`). Empfohlen ist ein eingeschränkter Schlüssel mit Schreibrechten auf Customers, Checkout Sessions, PaymentIntents, PaymentMethods und Refunds.
|
|
- **Webhook-Endpunkt:** `https://<domain>/api/stripe/webhook`. Diese Events abonnieren:
|
|
`checkout.session.completed`, `checkout.session.async_payment_succeeded`, `checkout.session.async_payment_failed`, `checkout.session.expired`, `payment_intent.succeeded`, `payment_intent.payment_failed`, `charge.refunded`, `refund.created`, `refund.updated`, `charge.dispute.created`.
|
|
Das Signing Secret gehört als `STRIPE_WEBHOOK_SECRET` ins Secret. Test- und Live-Endpunkt haben getrennte Secrets.
|
|
- **Zahlungsarten** (Dashboard → Zahlungsmethoden): Für Ratenzahlung nur wiederverwendbare Methoden aktivieren (Karten, gegebenenfalls SEPA-Lastschrift). Checkout blendet bei Ratenzahlung ohnehin nur Methoden ein, die `setup_future_usage` unterstützen.
|
|
- **Test lokal:** `stripe listen --forward-to localhost:3000/api/stripe/webhook`
|
|
- **Sicherheitsnetz:** Der Worker gleicht bei abgelaufenen Reservierungen und hängenden Einzügen immer mit Stripe ab. Ein verlorener Webhook führt so nicht zu verlorenen Zahlungen.
|
|
|
|
## 5. E-Mail
|
|
|
|
`MAIL_TRANSPORT=smtp` mit `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER` und `SMTP_PASSWORD`. Den Absender (`MAIL_FROM`) per SPF, DKIM und DMARC für die Absenderdomain freischalten. `ADMIN_NOTIFY_EMAIL` erhält Hinweise zu „Prüfung nötig“. Nicht zustellbare Mails zeigt das Admin-Dashboard. Der Worker versucht jede Mail bis zu achtmal.
|
|
|
|
## 6. Worker
|
|
|
|
Der CronJob `artista-modelcamp-worker` läuft alle 10 Minuten und kümmert sich um:
|
|
- abgelaufene Reservierungen, vorher mit Rückfrage bei Stripe
|
|
- hängende Einzüge
|
|
- fällige Raten
|
|
- das Trennen der Zahlungsmethode nach der letzten Rate
|
|
- den E-Mail-Versand
|
|
- das Aufräumen der Rate Limits
|
|
|
|
Ein fehlgeschlagener Lauf erscheint als fehlgeschlagener Job (`kubectl -n artista get jobs`).
|
|
|
|
## 7. Sicherung und Wiederherstellung
|
|
|
|
- **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
|
|
```
|
|
- **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. 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:** 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
|
|
|
|
Die Software trifft keine Storno-Entscheidung, sie setzt sie nur nachvollziehbar um.
|
|
|
|
1. **Stornieren** (Admin → Buchung → „Stornieren“, Grund ist Pflicht): Offene Raten werden beendet, Platz und Gutschein freigegeben, die gespeicherte Zahlungsmethode getrennt und offene Checkout-Sessions geschlossen. Danach wird nichts mehr abgebucht.
|
|
2. **Erstatten** (je bezahlter Rate, Betrag frei wählbar, höchstens bis zum gezahlten Betrag): Welcher Betrag erstattet wird, bestimmen die freigegebenen Stornobedingungen. Die Erstattung wird bei Stripe ausgelöst, der Zahlungsstatus aktualisiert sich per Webhook (`charge.refunded`). Ein doppeltes Abschicken wird über einen Idempotenzschlüssel abgefangen.
|
|
3. **Prüfung nötig** entsteht bei verspäteter Zahlung ohne freien Platz, Doppelzahlung, Rückbuchung oder endgültig fehlgeschlagener Rate. Dann entweder „Buchung bestätigen“ (nur mit freiem Platz, sonst vorher Kapazität erhöhen) oder stornieren und erstatten.
|
|
4. **Dokumentation:** Alle Schritte stehen im Protokoll der Buchung (AuditLog) mit handelnder Person.
|
|
|
|
## 9. Medien
|
|
|
|
Dateien (jpg, png, webp, avif, mp4, webm) direkt in den SMB-Ordner `modelcamp-media` legen, z. B. über den Windows-Explorer (`\\nas…\artista\modelcamp-media`). Die App sieht sie sofort unter `/media/<dateiname>`. Im Pod ist das Volume schreibgeschützt eingehängt.
|
|
|
|
Danach in `src/content/site.ts` eintragen. Hotlinking fremder Bilder ist nicht vorgesehen: Nur Bilder mit geklärter Nutzungsberechtigung ablegen. Die Unterkunftsbilder liegen als lokale Kopie in `public/images/unterkunft/` und sind Teil des Container-Images.
|