xpeditis2.0/infra/prod/README.md
2026-09-14 11:19:29 +02:00

165 lines
8.3 KiB
Markdown
Raw 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.

# infra/prod — Production Hetzner
Tout ce qui est nécessaire pour déployer et exploiter Xpeditis en production.
> **La procédure pas à pas est dans [`docs/mise-en-prod/`](../../docs/mise-en-prod/README.md).**
> Ce README décrit *ce que contient le dossier* ; la doc décrit *quoi faire, dans quel ordre*.
---
## Architecture cible
```
Internet
|
Cloudflare (DNS + WAF + anti-DDoS)
| seules les IP Cloudflare passent le firewall
v
┌─────────────────────────────────────────────┐
│ app-01 CPX41 8 vCPU / 16 Go fsn1 │
│ │
│ k3s (mono-nœud) │
│ Traefik ──┬── api.xpeditis.com → backend │
│ ├── app / www / apex → frontend│
│ └── grafana.xpeditis.com │
│ backend NestJS x2 (HPA 2→4) │
│ frontend Next.js x2 │
│ log-exporter │
│ monitoring : Loki, Promtail, Prometheus, │
│ Alertmanager, Grafana │
└──────────────────┬──────────────────────────┘
│ réseau privé 10.10.1.0/24
│ TLS obligatoire (hostssl)
v
┌─────────────────────────────────────────────┐
│ db-01 CPX31 4 vCPU / 8 Go fsn1 │
│ │
│ PostgreSQL 15 + WAL-G (Docker Compose) │
│ Redis 7 │
│ postgres-exporter │
│ volume dédié 50 Go │
└──────────────────┬──────────────────────────┘
│
┌───────────┴────────────┐
v v
Hetzner Object Storage Hetzner Storage Box
(documents + WAL-G) (dumps chiffrés age)
```
**Pourquoi la base hors de Kubernetes** — PostgreSQL en StatefulSet ajoute des
volumes persistants, un ordre de démarrage et des montées de version délicates,
sans aucun gain à cette échelle. Hors cluster, les sauvegardes, la restauration
à un instant T et les tests de restauration sont triviaux, et le nœud
applicatif reste entièrement reconstructible sans toucher aux données.
---
## Contenu du dossier
| Chemin | Rôle |
|---|---|
| `terraform/` | Serveurs, réseau privé, firewalls, volume, clé SSH. La seule source de vérité de l'infrastructure. |
| `scripts/00-bootstrap-common.sh` | Durcissement système commun (SSH, UFW, fail2ban, auditd, sysctl, mises à jour auto). |
| `scripts/01-setup-data-node.sh` | Installe db-01 : volume, Docker, certificat TLS Postgres, timers de sauvegarde. |
| `scripts/02-setup-k3s-server.sh` | Installe k3s durci (secrets chiffrés au repos, audit API, Traefik configuré). |
| `scripts/03-install-cluster-addons.sh` | cert-manager, namespaces, accès registre, ClusterIssuer. |
| `scripts/secrets-apply.sh` | Déchiffre SOPS → applique sur le cluster, sans passer par le disque. |
| `scripts/harden-seed-data.sh` | Secours et audit : neutralise les comptes de démonstration (`admin@xpeditis.com` / `Password123!`) sur une base migrée avant l'ajout de la garde `NODE_ENV`. Le cas nominal est traité par les migrations. |
| `scripts/deploy.sh` | Déploiement complet : migrations → images → attente → tests → retour arrière. |
| `scripts/ssh-deploy-wrapper.sh` | Restreint la clé SSH de la CI à quatre commandes. Une clé volée ne donne pas un shell. |
| `scripts/deploy-monitoring.sh` | Pile d'observabilité. |
| `scripts/smoke-test.sh` | Vérifie ce qu'un utilisateur constate réellement, de l'extérieur. |
| `scripts/preflight-check.sh` | Contrôle go / no-go avant ouverture au public. |
| `scripts/refresh-cloudflare-ips.sh` | Met à jour les rangs IP Cloudflare (firewall + Traefik). |
| `data-node/` | Docker Compose, `postgresql.conf`, `pg_hba.conf`, `redis.conf`, sauvegardes WAL-G. |
| `k8s/base/` | Namespaces, quotas, ConfigMap, gabarit de secrets, déploiements, Ingress, middlewares, politiques réseau, certificats. |
| `k8s/cluster/` | ClusterIssuer Let's Encrypt (DNS-01 Cloudflare). |
| `k8s/monitoring/` | Loki, Promtail, Prometheus, Alertmanager, Grafana. |
| `env/` | Gabarits de variables d'environnement et liste des secrets GitHub. |
| `cloudflare/` | Enregistrements DNS et règles WAF à créer côté Cloudflare. |
| `Makefile` | Raccourcis d'exploitation (`make help`). |
---
## Démarrage rapide
```bash
cd infra/prod
make help
# Infrastructure
cp terraform/terraform.tfvars.example terraform/terraform.tfvars
$EDITOR terraform/terraform.tfvars
make tf-init && make tf-plan && make tf-apply
make tf-output # IP + enregistrements DNS à créer
# ... provisioning des serveurs : voir docs/mise-en-prod/03 et 04 ...
# Secrets
cp k8s/base/03-secrets.template.yaml /tmp/secrets.yaml
$EDITOR /tmp/secrets.yaml
sops -e /tmp/secrets.yaml > k8s/base/03-secrets.sops.yaml && shred -u /tmp/secrets.yaml
make secrets-apply
# Déploiement
make deploy TAG=prod-a1b2c3d
make deploy-monitoring
# Avant d'ouvrir au public
make preflight
```
---
## Règles de sécurité non négociables
PostgreSQL : avant de déployer les correctifs TLS, renseigner `DATABASE_SSL_CA`
dans le Secret backend chiffré SOPS avec le contenu PEM du certificat **public**
`/var/lib/xpeditis/certs/server.crt` de db-01, récupéré par un canal d’administration
authentifié. Ne jamais copier `server.key`. Le gabarit de secrets contient le
champ à renseigner. Le backend et le Job de migration utilisent ce même Secret.
Conserver `DATABASE_SSL=true` et un `DATABASE_HOST` présent dans les SAN du
certificat (IP privée ou nom DNS). Les certificats non approuvés et les noms
incorrects sont désormais refusés ; le réseau privé ne remplace pas ce contrôle.
Lors d’un renouvellement, distribuer le nouveau certificat de confiance avant
la bascule serveur et redémarrer les clients concernés. Ne pas désactiver la
vérification TLS pour contourner une erreur de certificat.
1. **Aucun secret en clair dans Git.** Uniquement des fichiers `*.sops.yaml`
chiffrés avec age. `make secrets-check` refuse le contraire.
2. **La base de données n'est jamais joignable depuis Internet.** Réseau privé,
bind explicite sur l'IP privée, UFW, et `pg_hba` en `hostssl` seulement.
3. **Personne ne contourne Cloudflare.** Le firewall Hetzner n'accepte 80/443
que depuis les rangs Cloudflare.
4. **SSH et l'API Kubernetes ne sont ouverts qu'à vos IP d'administration.**
Jamais `0.0.0.0/0` — Terraform refuse cette valeur.
5. **Une sauvegarde non restaurée n'est pas une sauvegarde.** Le test de
restauration tourne toutes les semaines et doit être passé avant l'ouverture.
6. **Les migrations passent par un Job**, jamais en concurrence entre replicas.
7. **L'image frontend est reconstruite pour la production**, jamais promue
depuis la preprod : `NEXT_PUBLIC_API_URL` est figée au build.
8. **Aucun mot de passe d'administrateur n'est stocké nulle part.**
`SeedTestUsers` ne s'exécute plus en production, une migration de secours
neutralise ses comptes s'ils existent, et le premier administrateur est créé
sans mot de passe utilisable — vous définissez le vôtre via « mot de passe
oublié ».
---
## Coûts (référence : `Xpeditis_Previsions_Couts.xlsx`, feuille « Hetzner »)
| Poste | € HT / mois |
|---|---|
| app-01 CPX41 | 29 |
| db-01 CPX31 | 15 |
| Volume 50 Go | 2,40 |
| Sauvegardes Hetzner (20 %) | 8,80 |
| Storage Box BX11 (1 To) | 3,90 |
| Object Storage (documents + WAL-G, < 1 To) | ~6 |
| IPv4 supplémentaire | 1 |
| Cloudflare Free | 0 |
| **Total infrastructure** | **≈ 66 €** |
Les services tiers (Brevo, Stripe, Sentry, Pappers, assurance) s'ajoutent :
voir la feuille « Synthèse comparatif » du fichier de prévisions.