Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
212 lines
5.9 KiB
Markdown
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)**
|