287 lines
11 KiB
Markdown
287 lines
11 KiB
Markdown
# 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` | 2–15 | 4000 |
|
||
| **xpeditis-frontend** | `ghcr.io/<org>/xpeditis-frontend:latest` | 1–8 | 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
|