# 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//xpeditis-backend:latest` | 2–15 | 4000 | | **xpeditis-frontend** | `ghcr.io//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= AWS_SECRET_ACCESS_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