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

8.3 KiB
Raw Blame History

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/. 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

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.