165 lines
8.4 KiB
Markdown
165 lines
8.4 KiB
Markdown
# 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 qui acceptent encore `Password123!` (hash d'origine intact) sur une base migrée avant l'ajout de la garde `APP_ENV`. Un compte dont le mot de passe a été changé est conservé. 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.
|