xpeditis2.0/docs/deployment/hetzner/01-architecture.md
2026-03-26 18:08:28 +01:00

287 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 01 — Architecture de production sur Hetzner
---
## Vue d'ensemble
```
┌─────────────────────────────────────────────────────────────────────────┐
│ INTERNET │
└───────────────────────────────┬─────────────────────────────────────────┘
┌───────────▼───────────┐
│ Cloudflare │
│ WAF + CDN + DNS │
│ TLS termination │
└───────────┬───────────┘
│ HTTPS (443)
┌───────────▼───────────┐
│ Hetzner Load │
│ Balancer (LB11) │
│ €7.49/mois │
└─────┬─────────┬───────┘
│ │
┌──────────────▼──┐ ┌───▼──────────────┐
│ Worker Node 1 │ │ Worker Node 2 │
│ CX42 (8c/16G) │ │ CX42 (8c/16G) │
│ €21.49/mois │ │ €21.49/mois │
│ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ NestJS Pod │ │ │ │ NestJS Pod │ │
│ │ (backend) │ │ │ │ (backend) │ │
│ └─────────────┘ │ │ └─────────────┘ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ Next.js Pod │ │ │ │ Next.js Pod │ │
│ │ (frontend) │ │ │ │ (frontend) │ │
│ └─────────────┘ │ │ └─────────────┘ │
└────────┬────────┘ └────────┬─────────┘
│ Réseau privé Hetzner (10.0.0.0/16)
┌────────▼────────────────────▼─────────┐
│ Control Plane Node │
│ CX22 (2c/4G) €5.11/mois │
│ k3s server (etcd) │
└────────────────────────────────────────┘
┌───────────────────────┼───────────────────────┐
│ │ │
┌───────▼───────┐ ┌───────────▼──────────┐ ┌───────▼───────┐
│ PostgreSQL │ │ Redis │ │ Hetzner │
│ Neon.tech │ │ Upstash (serverless) │ │ Object │
│ ou self-host │ │ ou self-hosted │ │ Storage │
│ $19/mois │ │ $0-10/mois │ │ S3-compat. │
│ │ │ │ │ €4.99/mois │
└───────────────┘ └──────────────────────┘ └───────────────┘
```
---
## Composants et rôles
### Couche réseau
| Composant | Rôle | Port |
|---|---|---|
| **Cloudflare** | DNS, WAF, CDN, protection DDoS, cache assets | 443 (HTTPS) |
| **Hetzner Load Balancer** | Distribution trafic entre workers, sticky sessions WebSocket | 80, 443 |
| **Réseau privé Hetzner** | Communication inter-nœuds (10.0.0.0/16), base de données | Interne |
### Couche Kubernetes (k3s)
| Composant | Rôle | Ressource |
|---|---|---|
| **Control Plane (CX22)** | etcd, kube-apiserver, scheduler, controller-manager | 2 vCPU / 4 GB |
| **Worker Nodes (CX42)** | Exécution des pods NestJS + Next.js | 8 vCPU / 16 GB chacun |
| **Traefik Ingress** | Routage HTTP/HTTPS, sticky sessions Socket.IO | Built-in k3s |
| **cert-manager** | TLS automatique via Let's Encrypt | In-cluster |
| **Hetzner Cloud Controller** | Provisionne LB + volumes depuis Kubernetes | In-cluster |
| **Hetzner CSI Driver** | PersistentVolumes sur Hetzner Volumes | In-cluster |
### Couche application
| Pod | Image | Replicas | Ports |
|---|---|---|---|
| **xpeditis-backend** | `ghcr.io/<org>/xpeditis-backend:latest` | 215 | 4000 |
| **xpeditis-frontend** | `ghcr.io/<org>/xpeditis-frontend:latest` | 18 | 3000 |
### Couche données
| Service | Option MVP | Option Production | Protocole |
|---|---|---|---|
| **PostgreSQL 15** | Neon.tech Pro ($19/mois) | Self-hosted sur CX32 | 5432 |
| **Redis 7** | Upstash free ($0-10/mois) | Self-hosted StatefulSet | 6379 |
| **Stockage fichiers** | Hetzner Object Storage (€4.99/mois) | Idem (scale automatique) | HTTPS/S3 API |
---
## Flux réseau détaillé
### Requête API standard (rate search)
```
Client Browser
│ HTTPS
Cloudflare (cache miss → forward)
│ HTTPS, header CF-Connecting-IP
Hetzner Load Balancer :443
│ HTTP (TLS terminé par Cloudflare ou cert-manager)
Traefik Ingress (api.xpeditis.com)
│ HTTP :80 interne
NestJS Pod (port 4000)
├── Redis (cache rate:FSN:HAM:20ft) → HIT → retour direct
└── MISS → 5× appels APIs carriers (Maersk/MSC/etc.)
└── Réponse → Store Redis TTL 15min
└── Réponse client
```
### Connexion WebSocket (notifications temps réel)
```
Client Browser
│ wss:// upgrade
Cloudflare (WebSocket proxy activé)
Hetzner LB (sticky session cookie activé)
│ Même backend pod à chaque reconnexion
Traefik (annotation sticky cookie)
NestJS Pod /notifications namespace (Socket.IO)
├── Auth: JWT validation on connect
├── Join room: user:{userId}
└── Redis pub/sub → broadcast cross-pods
```
### Upload de document (carrier portal)
```
Carrier Browser
NestJS POST /api/v1/csv-bookings/{id}/documents
│ Validation: type (PDF/XLS/IMG), taille max 10 MB
S3StorageAdapter.upload()
│ AWS SDK v3, forcePathStyle: true
Hetzner Object Storage
│ Endpoint: https://fsn1.your-objectstorage.com
└── Stocké: xpeditis-docs/{orgId}/{bookingId}/{filename}
```
---
## Ports et protocoles
### Ports externes (ouverts sur Hetzner Firewall)
| Port | Protocole | Source | Destination | Usage |
|---|---|---|---|---|
| 22 | TCP | Votre IP uniquement | Tous nœuds | SSH administration |
| 80 | TCP | 0.0.0.0/0 | LB | Redirection HTTP → HTTPS |
| 443 | TCP | 0.0.0.0/0 | LB | HTTPS + WebSocket |
| 6443 | TCP | Votre IP + workers | Control plane | Kubernetes API |
### Ports internes (réseau privé 10.0.0.0/16 uniquement)
| Port | Protocole | Usage |
|---|---|---|
| 5432 | TCP | PostgreSQL (si self-hosted) |
| 6379 | TCP | Redis (si self-hosted) |
| 4000 | TCP | NestJS API (pod → pod) |
| 3000 | TCP | Next.js (pod → pod) |
| 10250 | TCP | kubelet API |
| 2379-2380 | TCP | etcd (control plane) |
---
## Namespaces Kubernetes
```
cluster
├── xpeditis-prod # Application principale
│ ├── Deployments: backend, frontend
│ ├── Services: backend-svc, frontend-svc
│ ├── ConfigMaps: backend-config, frontend-config
│ ├── Secrets: backend-secrets, frontend-secrets
│ ├── HPA: backend-hpa, frontend-hpa
│ └── Ingress: xpeditis-ingress
├── cert-manager # Gestion certificats TLS
│ └── ClusterIssuer: letsencrypt-prod, letsencrypt-staging
├── monitoring # Observabilité
│ ├── Prometheus
│ ├── Grafana
│ └── Loki
└── kube-system # Système k3s
├── Traefik (Ingress Controller)
├── Hetzner Cloud Controller Manager
└── Hetzner CSI Driver
```
---
## Pourquoi k3s plutôt que k8s complet
| Critère | k3s (choisi) | k8s complet |
|---|---|---|
| **RAM control plane** | 512 MB | 2-4 GB |
| **CPU control plane** | 1 vCPU | 2-4 vCPU → serveur plus cher |
| **Temps install** | 5 min (hetzner-k3s) | 30-60 min |
| **Maintenance** | System Upgrade Controller inclus | Manuelle |
| **Compatibilité** | 100% compatible kubectl/helm | — |
| **Traefik** | Inclus par défaut | Installation séparée |
| **Coût** | CX22 (€5.11/mois) comme control plane | Minimum CX42 (€21.49) |
| **Production** | Oui (utilisé par des milliers de startups) | Oui |
---
## Stratégie de scaling
### Horizontal Pod Autoscaler (HPA)
```
Métriques surveillées :
- CPU > 70% → Scale up
- CPU < 30% (5 min) → Scale down
- Mémoire > 80% → Scale up (custom metric)
Backend : min 2 → max 15 pods
Frontend : min 1 → max 8 pods
```
### Cluster Autoscaler
```
Worker nodes : min 2 → max 8
Déclenché par : pods en état "Pending" (pas assez de ressources)
Délai scale-down : 10 min d'utilisation < 50%
```
---
## Décisions d'architecture
### Pourquoi Hetzner Object Storage plutôt que MinIO self-hosted
Le code utilise déjà `AWS SDK v3` avec `forcePathStyle: true` et un endpoint configurable. Hetzner Object Storage est 100% compatible S3 → **zéro modification de code**, juste les variables d'environnement :
```bash
# Avant (MinIO local)
AWS_S3_ENDPOINT=http://localhost:9000
# Après (Hetzner Object Storage)
AWS_S3_ENDPOINT=https://fsn1.your-objectstorage.com
AWS_ACCESS_KEY_ID=<hetzner_access_key>
AWS_SECRET_ACCESS_KEY=<hetzner_secret_key>
AWS_REGION=eu-central-1
AWS_S3_BUCKET=xpeditis-prod
```
### Pourquoi Neon.tech pour PostgreSQL (MVP)
- PostgreSQL 15 managé, compatible TypeORM
- Extensions `uuid-ossp` et `pg_trgm` disponibles (requis par Xpeditis)
- Backups automatiques inclus
- Connection pooling built-in (via PgBouncer)
- Pas de gestion de HA à faire manuellement
- Free tier pour le dev, $19/mois pour la prod
- Migration vers self-hosted possible à tout moment
### Pourquoi Cloudflare devant Hetzner LB
- CDN mondial (cache des assets Next.js)
- Protection DDoS free
- WAF avec règles OWASP
- DNS avec failover automatique
- Certificats TLS optionnels (on peut laisser cert-manager gérer le TLS)
- Cache des PDFs générés → économise les appels S3