Infrastruktur als Code für den Skalierungs-Test auf Hetzner: - deploy/terraform: privates Netz, Firewalls, 2 App-Nodes, DB-Node, Load Balancer (Health-Check /health); cloud-init bootet Docker + Stack je Node - deploy/compose/docker-compose.prod.yml + nginx.prod.conf: App-Node-Stack (PHP-FPM + Nginx) routet /api,/p,/t,/css,/health → Symfony, Rest → Vue-SPA - App-Anpassungen: HealthController (/health für LB), brand.css nach /css verschoben (kein Pfad-Clash mit SPA-Assets im Prod-Routing) - deploy/README.md: Anleitung inkl. JWT-Key-Verteilung & Cross-Node-Test - reference.php (auto-generiert) aus Versionierung entfernt Terraform validiert (terraform validate), Prod-Compose-Syntax geprüft. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
77 lines
3.7 KiB
Markdown
77 lines
3.7 KiB
Markdown
# Deployment auf Hetzner Cloud (Terraform)
|
||
|
||
Provisioniert einen **Multi-Node-Test** mit nachgewiesener Skalierbarkeit:
|
||
|
||
```
|
||
Hetzner Load Balancer (lb11, :80 → /health)
|
||
/ \
|
||
vcard4-app-1 vcard4-app-2 (zustandslose App-Nodes)
|
||
\ /
|
||
┌─────────┴───────────┐ ┌─────┴──────────────────┐
|
||
vcard4-db (MariaDB, Volume) Hetzner Object Storage (S3, Assets)
|
||
```
|
||
|
||
App-Nodes sind zustandslos (Code + Docker), **State** liegt in DB (eigene VM) und
|
||
Object Storage (Hintergrund-PDFs/Schriften). Dadurch beliebig horizontal skalierbar.
|
||
|
||
## Voraussetzungen (einmalig, manuell)
|
||
|
||
1. **Terraform** ≥ 1.6 + Hetzner **API-Token** (Projekt → Security → API Tokens, Read&Write).
|
||
2. **Object Storage** in der Hetzner Console anlegen: Bucket + Access-Key/Secret
|
||
(hcloud/Terraform verwalten Object Storage derzeit nicht).
|
||
3. **Git-Repo** erreichbar für die Nodes (öffentlich oder Deploy-Token in der `repo_url`).
|
||
4. **JWT-Schlüsselpaar** einmal erzeugen — auf **allen** Nodes identisch:
|
||
```bash
|
||
# lokal im backend/-Container oder mit openssl
|
||
docker compose exec php php bin/console lexik:jwt:generate-keypair --overwrite
|
||
# → backend/config/jwt/private.pem & public.pem + Passphrase aus .env (JWT_PASSPHRASE)
|
||
```
|
||
Inhalt der beiden PEM-Dateien + Passphrase in `terraform.tfvars` eintragen.
|
||
5. **DNS**: nach `terraform apply` einen A-Record `domain → load_balancer_ip` setzen
|
||
(Hetzner DNS ist separat; A-Record auch für die Custom-Domain-Funktion, KONZEPT §11).
|
||
|
||
## Deploy
|
||
|
||
```bash
|
||
cd deploy/terraform
|
||
cp terraform.tfvars.example terraform.tfvars # ausfüllen
|
||
terraform init
|
||
terraform plan
|
||
terraform apply
|
||
terraform output # load_balancer_ip etc.
|
||
```
|
||
|
||
cloud-init installiert auf jedem App-Node Docker, klont das Repo, schreibt
|
||
`.env.prod.local` + JWT-Keys, baut das SPA und startet
|
||
`deploy/compose/docker-compose.prod.yml`. Migrationen laufen **nur** auf `app-1`.
|
||
Deploy-Log auf dem Node: `/var/log/vcard4-deploy.log`.
|
||
|
||
## Skalierbarkeit verifizieren
|
||
|
||
```bash
|
||
# 1) Login + Token holen (LB-IP oder Domain)
|
||
TOKEN=$(curl -s -X POST http://<LB-IP>/api/login \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"email":"admin@vcard4reseller.de","password":"admin"}' | jq -r .token)
|
||
|
||
# 2) Health über den LB (verteilt auf beide Nodes)
|
||
for i in $(seq 1 6); do curl -s http://<LB-IP>/health; echo; done
|
||
|
||
# 3) Cross-Node-Beweis: Hintergrund über den LB hochladen, dann
|
||
# auf BEIDEN Nodes rendern (per SSH) – identische PDFs aus Object Storage:
|
||
ssh root@<app-1-ip> 'cd /opt/vcard4 && docker compose -f deploy/compose/docker-compose.prod.yml exec -T php php bin/console app:render-card erika-mustermann'
|
||
ssh root@<app-2-ip> 'cd /opt/vcard4 && docker compose -f deploy/compose/docker-compose.prod.yml exec -T php php bin/console app:render-card erika-mustermann'
|
||
```
|
||
|
||
Horizontal skalieren: `app_count` erhöhen → `terraform apply` (LB-Targets werden automatisch ergänzt).
|
||
|
||
## Noch offen / Hinweise
|
||
|
||
- **TLS**: aktuell HTTP am LB. Für HTTPS entweder Hetzner **Managed Certificate**
|
||
am LB (DNS bei Hetzner) oder **Caddy** auf den Nodes (On-Demand-TLS) — letzteres
|
||
ist auch der Weg für die **Custom-Domains** der Firmenkunden (§11).
|
||
- **Trusted Proxies**: für korrekte absolute URLs hinter dem LB
|
||
`framework.trusted_proxies` auf `%env(TRUSTED_PROXIES)%` setzen.
|
||
- **Seed**: optional einmalig `app:seed` auf `app-1` für Demo-Daten.
|
||
- **Updates**: neuen Stand ausrollen = auf den App-Nodes `git pull` + `docker compose ... up -d --build` (später per CI/Skript).
|