xpeditis2.0/docs/mise-en-prod/02-provisioning-hetzner.md
David b22f4e0b74 docs: procedure de mise en production pas a pas
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
2026-09-07 21:40:50 +02:00

5.9 KiB

02 — Provisioning Hetzner

Durée : environ 1 h. Création des serveurs, du réseau privé, des firewalls et du volume de données, entièrement décrite en Terraform.

Le fichier de prévisions de coûts note explicitement le risque « bus factor DevOps — Terraform/IaC obligatoire ». C'est la raison d'être de ce dossier : l'infrastructure est reconstructible par quelqu'un d'autre, à partir du dépôt seul.


1. Token API Hetzner

Console Hetzner → projet xpeditis-prod → Security → API tokens → Generate API token.

  • Description : terraform-prod
  • Permissions : Read & Write

Le token n'est affiché qu'une fois. Copiez-le dans votre gestionnaire de mots de passe immédiatement.

Créez un second token nommé github-actions-cicd, également Read & Write. Il servira uniquement au firewall temporaire de la CI, ce qui permet de le révoquer sans casser Terraform.


2. Configuration

cd infra/prod/terraform
cp terraform.tfvars.example terraform.tfvars
$EDITOR terraform.tfvars

À renseigner :

hcloud_token       = "<token terraform-prod>"
ssh_public_key     = "ssh-ed25519 AAAA... xpeditis-prod-admin"   # cat ~/.ssh/xpeditis_prod.pub
admin_ip_allowlist = ["<votre IP>/32"]                            # curl -s https://ifconfig.me

Le reste des valeurs par défaut correspond au dimensionnement de la phase 1 : cpx41 pour l'application, cpx31 pour les données, volume de 50 Go, sauvegardes Hetzner activées.

terraform.tfvars est ignoré par Git (infra/prod/.gitignore). Vérifiez-le avant tout commit : git status ne doit pas le mentionner.


3. Vérifier avant d'appliquer

make -C .. tf-init      # ou : terraform init
make -C .. tf-plan      # ou : terraform plan

Le plan doit annoncer 8 ressources à créer :

hcloud_network.main
hcloud_network_subnet.main
hcloud_ssh_key.admin
hcloud_placement_group.spread
hcloud_firewall.app
hcloud_firewall.db
hcloud_firewall.cicd
hcloud_server.app
hcloud_server.db
hcloud_volume.pgdata

Trois points à relire dans le plan :

  1. hcloud_firewall.app — les règles 22 et 6443 portent uniquement votre IP. Si vous y voyez 0.0.0.0/0, arrêtez tout.
  2. hcloud_firewall.db — seul le port 22 est ouvert. Ni 5432, ni 6379.
  3. hcloud_firewall.cicd — aucune règle. C'est normal : la CI les injecte puis les retire.

4. Appliquer

make -C .. tf-apply

Environ 90 secondes. Puis :

terraform output

Notez précieusement :

app_public_ipv4  = "..."   → tous les enregistrements DNS Cloudflare
db_public_ipv4   = "..."   → SSH d'administration UNIQUEMENT, jamais en DNS
db_private_ip    = "10.10.1.20"
pgdata_volume_device = "/dev/disk/by-id/scsi-0HC_Volume_..."

5. Vérifier l'accès

ssh -i ~/.ssh/xpeditis_prod deploy@<app_public_ipv4> 'hostname; uptime'
ssh -i ~/.ssh/xpeditis_prod deploy@<db_public_ipv4>  'hostname; uptime'

cloud-init met une à deux minutes après la création du serveur. Si la connexion est refusée, attendez puis vérifiez :

ssh -i ~/.ssh/xpeditis_prod deploy@<ip> 'ls -l /var/log/cloud-init-xpeditis-done'

Le réseau privé doit fonctionner dans les deux sens :

ssh deploy@<app_public_ipv4> 'ping -c2 10.10.1.20'

6. Confirmer que la base est bien inaccessible

Depuis un autre réseau que votre IP d'administration (partage de connexion mobile, par exemple) :

nmap -Pn -p 22,80,443,5432,6379,6443 <db_public_ipv4>

Attendu : tous les ports filtered, y compris le 22 — puisque vous testez depuis une IP non autorisée.

nmap -Pn -p 22,80,443,5432,6379,6443 <app_public_ipv4>

Attendu à ce stade : 80 et 443 ouverts uniquement si restrict_http_to_cloudflare = false. Avec la valeur par défaut true, ils apparaissent aussi filtered tant que la requête ne vient pas de Cloudflare — c'est exactement l'effet recherché.


7. Storage Box

La Storage Box se commande séparément (Robot Hetzner, pas la console Cloud).

  1. Commandez une BX11 (1 To, 3,90 €/mois).
  2. Dans son interface : activez SSH et désactivez Samba/CIFS et WebDAV, inutiles ici et exposés sur Internet.
  3. Déposez la clé publique dédiée :
# Depuis votre poste
ssh-copy-id -p 23 -i ~/.ssh/xpeditis_storagebox.pub uXXXXXX@uXXXXXX.your-storagebox.de

# Créez l'arborescence
ssh -p 23 -i ~/.ssh/xpeditis_storagebox uXXXXXX@uXXXXXX.your-storagebox.de \
  'mkdir -p xpeditis-prod/dumps'

La clé privée correspondante devra être déposée sur db-01 en /root/.ssh/storagebox (04 — Nœud de données).

Activez les snapshots de la Storage Box (gratuits, dans son interface). Ils protègent contre le cas où un rançongiciel présent sur db-01 chiffrerait ou effacerait aussi les sauvegardes distantes via la connexion SSH existante.


8. Sauvegarder l'état Terraform

terraform.tfstate décrit toute votre infrastructure. Il est gitignoré à dessein (il contient des données sensibles), mais le perdre signifie que Terraform ne reconnaîtra plus vos ressources et proposera de tout recréer.

# Sauvegarde chiffrée dans le dépôt
sops -e terraform.tfstate > ../terraform-state-backup.sops.json

Ou, mieux, migrez vers un backend S3 sur Hetzner Object Storage : le bloc backend "s3" est prêt, en commentaire, dans versions.tf.


9. Contrôle

[ ] terraform apply passé sans erreur
[ ] SSH fonctionne sur app-01 et db-01 avec la clé d'administration
[ ] Le ping app-01 → 10.10.1.20 répond
[ ] nmap depuis une IP non autorisée : tout est filtered
[ ] Storage Box commandée, SSH activé, Samba/WebDAV désactivés
[ ] Snapshots de la Storage Box activés
[ ] État Terraform sauvegardé
[ ] Les deux IP publiques notées dans le gestionnaire de mots de passe

→ Suite : 03 — Durcissement des serveurs