Déploiement avec OpenTofu sur LINSTOR DRBD : le début des problèmes
Après avoir choisi LINSTOR DRBD comme solution de stockage distribué pour mon cluster Proxmox HA, il était temps de passer à l'automatisation du déploiement avec OpenTofu (fork open-source de Terraform). Spoiler : ça ne s'est pas passé comme prévu.
Rappel du contexte
Dans mon article précédent sur le choix d'une technologie de stockage distribué, j'avais opté pour LINSTOR DRBD plutôt que Ceph pour plusieurs raisons :
- Performance supérieure : LINSTOR DRBD utilise la réplication synchrone au niveau bloc via DRBD, offrant de meilleures performances que Ceph sur un réseau 1 Gbps
- Simplicité d'architecture : Pas besoin de monitors, managers et OSDs comme avec Ceph
- Consommation de ressources : Plus léger en RAM et CPU
Mon infrastructure se compose de :
- 3 nœuds Proxmox : acemagician (192.168.100.10), elitedesk (192.168.100.20), thinkpad (192.168.100.30)
- Configuration LINSTOR : thinkpad = contrôleur, acemagician/elitedesk = satellites
- Pools de stockage :
linstor_storage(répliqué DRBD),local-lvm(stockage local)
Le problème : LINSTOR ne peut pas provisionner dynamiquement les VM
Quand j'ai tenté de déployer mes VMs K3s avec OpenTofu en utilisant le provider Proxmox, j'ai rencontré une erreur systématique :
Error: error creating VM: error cloning vm: 500 Internal Server Error:
unable to create VM 1000 - lvcreate 'linstor_storage/vm-1000-disk-0' error:
Exited with error code 1: Resource definition 'vm-1000-disk-0' not found.
Ma configuration OpenTofu était pourtant classique :
resource "proxmox_vm_qemu" "k3s_server_1" {
name = "k3s-server-1"
target_node = "acemagician"
clone = "ubuntu-2404-cloudinit"
vmid = 1000
disk {
storage = "linstor_storage"
size = "100G"
}
}
Pourquoi ça échoue ?
LINSTOR utilise un modèle d'objets hiérarchique strict :
- Resource Definition : Template pour une ressource de stockage
- Volume Definition : Taille et propriétés du volume
- Resource : Instance réelle du stockage sur un nœud
Le workflow normal de LINSTOR nécessite de créer ces objets manuellement :
linstor resource-definition create mydata
linstor volume-definition create mydata 100G
linstor resource create node1 mydata --storage-pool linstor_storage
linstor resource create node2 mydata --storage-pool linstor_storage
Le problème : Quand Proxmox tente de cloner une VM vers LINSTOR :
- Proxmox appelle
lvcreatepour créer le nouveau disque - Le plugin LINSTOR cherche la resource definition
vm-XXX-disk-0 - La resource definition n'existe pas (Proxmox assume une création dynamique)
- L'opération échoue
LINSTOR ne peut pas créer dynamiquement des resource definitions pendant les opérations de clonage Proxmox. C'est une limitation fondamentale de l'architecture.
Tentatives de contournement
J'ai essayé plusieurs approches :
1. Clone complet explicite
resource "proxmox_vm_qemu" "k3s_server_2" {
name = "k3s-server-2"
target_node = "elitedesk"
clone = "ubuntu-2404-cloudinit"
vmid = 1001
full_clone = true # Clone complet explicite
disk {
storage = "linstor_storage"
size = "100G"
}
}
Résultat : Même erreur - "Resource definition not found"
2. Linked clone
full_clone = false # Tenter un linked clone
Résultat :
Error: 400 Bad Request:
Parameter verification failed. (400)
storage: linked clone feature is not supported for 'linstor_storage'
C'est logique : DRBD ne supporte pas les snapshots, nécessaires pour les linked clones.
Les solutions envisagées
Face à ce problème, j'hésite entre plusieurs options :
Option 1 : Script de pré-création des resource definitions
Créer un script bash ou Python qui s'exécute avant OpenTofu pour pré-créer les resource definitions :
#!/usr/bin/env python3
import subprocess
import json
def create_linstor_resource(vm_id, size_gb, nodes):
"""Crée une resource definition LINSTOR pour une VM"""
resource_name = f"vm-{vm_id}-disk-0"
# Créer la resource definition
subprocess.run([
"linstor", "resource-definition", "create", resource_name
], check=True)
# Créer la volume definition
subprocess.run([
"linstor", "volume-definition", "create",
resource_name, f"{size_gb}G"
], check=True)
# Créer la resource sur chaque nœud
for node in nodes:
subprocess.run([
"linstor", "resource", "create",
node, resource_name,
"--storage-pool", "linstor_storage"
], check=True)
# Créer les resources pour les VMs K3s
vms = [
{"id": 1000, "size": 100, "nodes": ["acemagician", "elitedesk"]},
{"id": 1001, "size": 100, "nodes": ["elitedesk", "thinkpad"]},
{"id": 1002, "size": 20, "nodes": ["thinkpad", "acemagician"]},
]
for vm in vms:
create_linstor_resource(vm["id"], vm["size"], vm["nodes"])
Avantages :
- Garde l'utilisation de LINSTOR DRBD
- Permet l'automatisation via CI/CD
- Maintient les performances supérieures de DRBD
- Conserve le stockage distribué pour les VM
Inconvénients :
- Complexité supplémentaire dans le pipeline
- Risque de désynchronisation entre script et configuration OpenTofu
- Nécessite une gestion rigoureuse des VMIDs et de leur attribution
Option 2 : Création manuelle des resource definitions
Créer manuellement les resources LINSTOR avant chaque déploiement :
# Pour k3s-server-1 (VMID 1000)
linstor resource-definition create vm-1000-disk-0
linstor volume-definition create vm-1000-disk-0 100G
linstor resource create acemagician vm-1000-disk-0 --storage-pool linstor_storage
linstor resource create elitedesk vm-1000-disk-0 --storage-pool linstor_storage
Avantages :
- Solution simple et directe
- Contrôle total sur les resources LINSTOR
Inconvénients :
- Perte de l'Infrastructure as Code : Dérive de configuration garantie
- Perte du GitOps : Pas de traçabilité dans git
- Non automatisable : Intervention manuelle à chaque déploiement
- Non scalable : Impossible pour des déploiements fréquents
Cette option va complètement à l'encontre de mes objectifs d'automatisation. Cette solution est à écarter.
Option 3 : Partitionner les NVMe (stockage local + LINSTOR)
Partitionner les disques NVMe de chaque nœud en deux parties :
- Une partition pour le stockage local LVM (
local-lvm) - Une partition pour le pool LINSTOR DRBD (
linstor_storage)
Ensuite, utiliser local-lvm pour les disques de VM (provisionnement simple) et linstor_storage pour d'autres usages nécessitant de la réplication.
Note importante pour mon cas d'usage Kubernetes : L'utilisation de local-lvm (sans réplication au niveau Proxmox) est viable pour un cluster Kubernetes car c'est Kubernetes qui gère la haute disponibilité, pas Proxmox. Avec etcd distribué sur 3 nœuds et le control plane répliqué, la perte d'une VM n'impacte pas le cluster - Kubernetes continue de fonctionner avec les nœuds restants. Les VM deviennent "cattle" (remplaçables via Infrastructure as Code) tandis que les vraies données "pets" (précieuses) vivraient dans des solutions de stockage au niveau applicatif.
Avantages :
- Provisionnement des VM simple et rapide sur
local-lvm - Conservation de LINSTOR DRBD pour les besoins de stockage distribué
- Utilisation optimale du matériel disponible
- Performances maximales pour les VM (accès local direct)
- HA assurée au bon niveau : Kubernetes, pas Proxmox
Inconvénients :
- Complexité de mise en place : Repartitionnement des disques nécessaire
- Risque de perte de données : Opération invasive sur les disques existants
- Planification de capacité : Besoin de déterminer la taille de chaque partition à l'avance
- Moins de flexibilité : Tailles de partitions fixes, difficiles à modifier
- Pas de HA au niveau Proxmox : Les VM ne bénéficient plus de la réplication (acceptable si HA au niveau Kubernetes)
Option 4 : Migrer vers Ceph avec upgrade réseau
Abandonner LINSTOR DRBD et migrer vers Ceph, en upgrading le réseau à 5 Gbps (ou 10 Gbps si budget permet) :
Avantages :
- Support natif du provisionnement dynamique dans Proxmox
- Intégration parfaite avec OpenTofu/Terraform
- Écosystème mature et bien documenté
- Snapshots et clones supportés nativement
- Performances acceptables avec un NIC 5 Gbps
Inconvénients :
- Coût matériel : Achat de cartes réseau 5 Gbps (ou 10 Gbps) pour les 3 nœuds
- Complexité accrue : Monitors, Managers, OSDs à gérer
- Consommation de ressources : Plus gourmand en RAM et CPU que LINSTOR
- Migration complète : Reconstruction du stockage existant
- Performances toujours inférieures : Même avec 5 Gbps, overhead plus important que DRBD
Ma réflexion actuelle
Je suis actuellement tiraillé entre ces options :
Option 1 (Script) me séduit car elle conserve LINSTOR et automatise tout. Avec des VMIDs fixes (1000, 1001, 1002), le script serait relativement simple à maintenir. Il suffit de s'assurer que le script s'exécute avant OpenTofu dans le pipeline CI/CD.
Option 3 (Partitionnement) est techniquement intéressante mais très invasive. Le repartitionnement des NVMe en production est risqué, et je perds la haute disponibilité au niveau Proxmox pour les VM elles-mêmes. Cependant, dans mon contexte Kubernetes, ce n'est pas forcément un problème puisque la HA est gérée au niveau du cluster K3s, pas au niveau des VM individuelles. Si une VM tombe, Kubernetes continue de fonctionner avec les autres nœuds.
Option 4 (Ceph + upgrade réseau) résout tous les problèmes techniques mais implique un investissement matériel. Un switch 5 Gbps + 3 cartes réseau représente un budget non négligeable pour un homelab. Par contre, ça ouvre la porte à d'autres possibilités futures.
Ce que je retiens
LINSTOR ≠ Stockage généraliste pour Proxmox
LINSTOR excelle pour certains cas d'usage, mais le provisionnement dynamique de VM via clonage Proxmox n'en fait pas partie. La documentation LINSTOR est d'ailleurs très axée sur les resource-group et le stockage applicatif, pas sur l'intégration Proxmox.
La limitation est architecturale, pas un bug
Ce n'est pas un problème de configuration ou une erreur de ma part : LINSTOR est conçu avec un modèle de gestion explicite des ressources. Le provisionnement dynamique à la volée n'est tout simplement pas dans sa philosophie.
La HA peut être déléguée à une couche supérieure
Pour un cluster Kubernetes, la perte de HA au niveau Proxmox (VM sur stockage local) n'est pas nécessairement problématique. Kubernetes est conçu pour gérer la défaillance de nœuds - c'est même son rôle principal. Avec etcd distribué et un control plane répliqué, le cluster survit à la perte d'un ou plusieurs nœuds.
Chaque solution a son coût
- Script → Complexité logicielle
- Partitionnement → Complexité opérationnelle et perte de HA au niveau Proxmox
- Ceph → Complexité système et coût matériel
Il n'y a pas de solution miracle. Je dois choisir quel type de complexité je suis prêt à accepter.
Prochaines étapes
Je vais probablement tester l'Option 1 (script de pré-création) en premier, car elle me permet de :
- Garder LINSTOR DRBD et ses performances
- Automatiser complètement le déploiement
- Éviter un investissement matériel immédiat
- Apprendre à mieux gérer LINSTOR programmatiquement
Si cette approche s'avère trop complexe ou fragile, je reconsidérerai soit l'Option 3 (partitionnement, acceptable dans un contexte Kubernetes), soit l'Option 4 (Ceph + upgrade réseau), qui est la solution la plus "standard" et documentée dans l'écosystème Proxmox.
Je documenterai ma décision finale et sa mise en œuvre dans un prochain article.
Après avoir testé l'Option 1 avec un script Python de gestion des ressources LINSTOR, j'ai constaté que cette approche, bien que fonctionnelle, ajoutait une complexité trop importante et des risques de désynchronisation pour une utilisation en production.
La décision finale : Partitionner les disques NVMe de chaque nœud Proxmox selon la stratégie suivante :
-
300 Go alloués à LINSTOR DRBD (
linstor_storage) pour :- Les VMs et LXC nécessitant la haute disponibilité au niveau Proxmox
- Le conteneur LXC hébergeant le serveur NFS (voir projet zfs-sync-nfs-ha)
- Tout stockage distribué géré par Proxmox HA
-
200 Go alloués à local-lvm (
local-lvm) pour :- Les VMs du cluster K3S qui n'ont pas besoin de HA au niveau Proxmox
- La haute disponibilité étant assurée par le cluster Kubernetes lui-même
- Provisionnement simple et rapide via OpenTofu
Cette architecture permet d'utiliser le bon outil au bon endroit : LINSTOR DRBD pour ce qui nécessite vraiment de la réplication synchrone au niveau infrastructure, et du stockage local performant pour les workloads où la HA est gérée par la couche applicative (Kubernetes).
Un article détaillé sur cette mise en œuvre et le conteneur NFS HA suivra prochainement.
Références :
