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

212 lines
5.9 KiB
Markdown

# 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
```bash
cd infra/prod/terraform
cp terraform.tfvars.example terraform.tfvars
$EDITOR terraform.tfvars
```
À renseigner :
```hcl
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
```bash
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
```bash
make -C .. tf-apply
```
Environ 90 secondes. Puis :
```bash
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
```bash
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 :
```bash
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 :
```bash
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) :
```bash
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.
```bash
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 :
```bash
# 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](./04-noeud-donnees.md)).
> **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.
```bash
# 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](./03-durcissement-serveurs.md)**