Ajout script gestion ressources LINSTOR

- Ajouter script bash pour gestion automatique ressources LINSTOR
- Documenter utilisation et options script
- Inclure exemples cas d'usage
This commit is contained in:
Tellsanguis 2025-11-27 19:43:53 +01:00
parent c4b8a4d3ee
commit c35a240ec3

399
manage_linstor_resources.py Normal file
View file

@ -0,0 +1,399 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Auteur: BENE Maël
# Version: 1.0
# Date de création: 27-11-2025
"""
Script de gestion des ressources DRBD Linstor pour les VMs K3s.
Exécuté avant le déploiement Terraform pour s'assurer que les ressources
de stockage DRBD sont créées et dimensionnées correctement.
"""
import subprocess
import sys
import json
import argparse
import os
from typing import Dict, Optional, Tuple
# Configuration SSH et Linstor (via variables d'environnement)
SSH_KEY_PATH = os.environ.get("SSH_KEY_PATH")
LINSTOR_CONTROLLER_IP = os.environ.get("LINSTOR_CONTROLLER_IP", "192.168.100.30")
LINSTOR_CONTROLLER_USER = os.environ.get("LINSTOR_CONTROLLER_USER", "root")
# Noms de ressources DRBD pour chaque VM
# Format attendu par Proxmox: vm-{VMID}-disk-0
RESOURCE_NAMES = {
1000: "vm-1000-disk-0", # acemagician - k3s-server-1
1001: "vm-1001-disk-0", # elitedesk - k3s-server-2
}
# Configuration des nœuds Proxmox
NODE_CONFIG = {
1000: {"node": "acemagician", "vm_name": "k3s-server-1"},
1001: {"node": "elitedesk", "vm_name": "k3s-server-2"},
}
def run_ssh_command(command: str, ssh_key: Optional[str] = None) -> Tuple[int, str, str]:
"""
Exécute une commande SSH sur le contrôleur Linstor.
Args:
command: Commande à exécuter
ssh_key: Chemin vers la clé SSH privée (optionnel)
Returns:
Tuple (code_retour, stdout, stderr)
"""
ssh_cmd = ["ssh", "-o", "StrictHostKeyChecking=no"]
# Ajouter la clé SSH si spécifiée
if ssh_key:
ssh_cmd.extend(["-i", ssh_key])
ssh_cmd.extend([f"{LINSTOR_CONTROLLER_USER}@{LINSTOR_CONTROLLER_IP}", command])
try:
result = subprocess.run(
ssh_cmd,
capture_output=True,
text=True,
timeout=30
)
return result.returncode, result.stdout, result.stderr
except subprocess.TimeoutExpired:
return 1, "", "Timeout lors de l'exécution de la commande SSH"
except Exception as e:
return 1, "", f"Erreur lors de l'exécution SSH: {str(e)}"
def check_resource_exists(resource_name: str, ssh_key: Optional[str] = None) -> bool:
"""
Vérifie si une ressource DRBD existe.
Args:
resource_name: Nom de la ressource à vérifier
ssh_key: Chemin vers la clé SSH privée (optionnel)
Returns:
True si la ressource existe, False sinon
"""
# Méthode 1: Vérifier avec resource-definition list
returncode, stdout, stderr = run_ssh_command(
f"linstor resource-definition list",
ssh_key=ssh_key
)
if returncode == 0:
# Cherche le nom exact de la ressource dans la sortie
lines = stdout.strip().split('\n')
for line in lines:
# Ignore les lignes d'en-tête et de séparation
if line.startswith('+-') or line.startswith('| ResourceName'):
continue
# Cherche la ressource dans les lignes de données
if f"| {resource_name} " in line or f"|{resource_name}|" in line:
print(f" → Ressource trouvée dans la liste des définitions")
return True
# Méthode 2: Vérifier avec volume-definition (si resource-definition existe, volume existe aussi)
returncode, stdout, stderr = run_ssh_command(
f"linstor volume-definition list --resource {resource_name}",
ssh_key=ssh_key
)
if returncode == 0 and stdout.strip() and "VolumeNr" in stdout:
print(f" → Volume trouvé pour la ressource")
return True
print(f" → Ressource non trouvée")
return False
def get_resource_size(resource_name: str, ssh_key: Optional[str] = None) -> Optional[int]:
"""
Récupère la taille actuelle d'une ressource DRBD en GiB.
Args:
resource_name: Nom de la ressource
ssh_key: Chemin vers la clé SSH privée (optionnel)
Returns:
Taille en GiB ou None si erreur
"""
# Essayer d'abord avec machine-readable (JSON)
returncode, stdout, stderr = run_ssh_command(
f"linstor volume-definition list --resource {resource_name} --machine-readable",
ssh_key=ssh_key
)
if returncode == 0 and stdout.strip():
try:
# Parse la sortie JSON de Linstor
data = json.loads(stdout)
if data and len(data) > 0:
volume_defs = data[0].get("volume_definitions", [])
if volume_defs and len(volume_defs) > 0:
# Taille en KiB, conversion en GiB
size_kib = volume_defs[0].get("size_kib", 0)
size_gib = size_kib // (1024 * 1024)
print(f" → Taille récupérée via JSON: {size_gib}GiB")
return size_gib
except (json.JSONDecodeError, KeyError, IndexError) as e:
print(f" ⚠ Erreur parsing JSON, essai avec format texte: {e}")
# Fallback: parser la sortie texte normale
returncode, stdout, stderr = run_ssh_command(
f"linstor volume-definition list --resource {resource_name}",
ssh_key=ssh_key
)
if returncode == 0 and stdout.strip():
lines = stdout.strip().split('\n')
for line in lines:
if '|' in line and 'GiB' in line and not line.startswith('| VolumeNr'):
# Extrait la taille en GiB
parts = [p.strip() for p in line.split('|')]
for part in parts:
if 'GiB' in part:
try:
size_str = part.replace('GiB', '').strip()
size_gib = int(float(size_str))
print(f" → Taille récupérée via texte: {size_gib}GiB")
return size_gib
except ValueError:
continue
print(f" ⚠ Impossible de récupérer la taille, sortie: {stdout[:200]}")
return None
def create_resource(resource_name: str, size_gib: int, nodes: list, ssh_key: Optional[str] = None) -> bool:
"""
Crée une nouvelle ressource DRBD avec réplication.
Args:
resource_name: Nom de la ressource à créer
size_gib: Taille en GiB
nodes: Liste des nœuds pour la réplication
ssh_key: Chemin vers la clé SSH privée (optionnel)
Returns:
True si succès, False sinon
"""
print(f"Création de la ressource {resource_name} avec {size_gib}GiB...")
# 1. Créer la définition de ressource
print(f" [1/3] Création de la définition de ressource...")
returncode, stdout, stderr = run_ssh_command(
f"linstor resource-definition create {resource_name}",
ssh_key=ssh_key
)
if returncode != 0:
# Si la ressource existe déjà, ce n'est pas une erreur fatale
if "already exists" in stdout or "already exists" in stderr:
print(f" ⚠ La définition de ressource existe déjà, passage à l'étape suivante")
else:
print(f"Erreur lors de la création de la définition: {stderr}", file=sys.stderr)
if stdout:
print(f"Sortie standard: {stdout}", file=sys.stderr)
return False
else:
print(f" ✓ Définition de ressource créée")
# 2. Créer la définition de volume
print(f" [2/3] Création de la définition de volume...")
returncode, stdout, stderr = run_ssh_command(
f"linstor volume-definition create {resource_name} {size_gib}GiB",
ssh_key=ssh_key
)
if returncode != 0:
# Si le volume existe déjà, ce n'est pas une erreur fatale
if "already exists" in stdout or "already exists" in stderr:
print(f" ⚠ La définition de volume existe déjà, passage à l'étape suivante")
else:
print(f"Erreur lors de la création du volume: {stderr}", file=sys.stderr)
if stdout:
print(f"Sortie standard: {stdout}", file=sys.stderr)
return False
else:
print(f" ✓ Définition de volume créée")
# 3. Déployer la ressource sur les nœuds avec réplication
print(f" [3/3] Déploiement de la ressource sur les nœuds...")
deployed_count = 0
for node in nodes:
print(f" → Déploiement sur {node}...")
returncode, stdout, stderr = run_ssh_command(
f"linstor resource create {node} {resource_name} --storage-pool linstor_storage --resource-group pve-rg",
ssh_key=ssh_key
)
if returncode != 0:
# Si la ressource existe déjà sur ce nœud, ce n'est pas une erreur
if "already exists" in stdout or "already exists" in stderr or "already deployed" in stdout:
print(f" ⚠ Ressource déjà déployée sur {node}")
deployed_count += 1
else:
print(f"Erreur lors du déploiement sur {node}: {stderr}", file=sys.stderr)
if stdout:
print(f"Sortie standard: {stdout}", file=sys.stderr)
# Continue avec les autres nœuds même en cas d'erreur
continue
else:
print(f" ✓ Ressource déployée sur {node}")
deployed_count += 1
print(f"✓ Ressource {resource_name} déployée sur {deployed_count}/{len(nodes)} nœuds")
return True
def resize_resource(resource_name: str, new_size_gib: int, ssh_key: Optional[str] = None) -> bool:
"""
Augmente la taille d'une ressource DRBD existante.
Args:
resource_name: Nom de la ressource à redimensionner
new_size_gib: Nouvelle taille en GiB
ssh_key: Chemin vers la clé SSH privée (optionnel)
Returns:
True si succès, False sinon
"""
print(f"Redimensionnement de la ressource {resource_name} à {new_size_gib}GiB...")
returncode, stdout, stderr = run_ssh_command(
f"linstor volume-definition set-size {resource_name} 0 {new_size_gib}GiB",
ssh_key=ssh_key
)
if returncode != 0:
print(f"Erreur lors du redimensionnement: {stderr}", file=sys.stderr)
return False
print(f"✓ Ressource {resource_name} redimensionnée avec succès")
return True
def manage_vm_resource(vmid: int, size_gib: int, dry_run: bool = False, ssh_key: Optional[str] = None) -> bool:
"""
Gère la ressource DRBD pour une VM spécifique.
Args:
vmid: ID de la VM
size_gib: Taille souhaitée en GiB
dry_run: Si True, affiche les actions sans les exécuter
ssh_key: Chemin vers la clé SSH privée (optionnel)
Returns:
True si succès, False sinon
"""
if vmid not in RESOURCE_NAMES:
print(f"VMID {vmid} non configuré", file=sys.stderr)
return False
resource_name = RESOURCE_NAMES[vmid]
node_info = NODE_CONFIG[vmid]
print(f"\n{'='*60}")
print(f"Gestion de la ressource pour VM {vmid} ({node_info['vm_name']})")
print(f"Ressource DRBD: {resource_name}")
print(f"Nœud Proxmox: {node_info['node']}")
print(f"Taille souhaitée: {size_gib}GiB")
print(f"{'='*60}\n")
resource_exists = check_resource_exists(resource_name, ssh_key=ssh_key)
if not resource_exists:
print(f"La ressource {resource_name} n'existe pas.")
if dry_run:
print(f"[DRY-RUN] Créerait la ressource {resource_name} avec {size_gib}GiB")
return True
# Créer la ressource sur les 2 nœuds avec stockage (thinkpad = contrôleur uniquement)
nodes = ["acemagician", "elitedesk"]
return create_resource(resource_name, size_gib, nodes, ssh_key=ssh_key)
else:
print(f"La ressource {resource_name} existe déjà.")
current_size = get_resource_size(resource_name, ssh_key=ssh_key)
if current_size is None:
print("⚠ Impossible de récupérer la taille actuelle")
print("La ressource existe mais le volume peut ne pas être complètement configuré")
print("Tentative de création/configuration du volume...")
if dry_run:
print(f"[DRY-RUN] Tenterait de créer/configurer le volume avec {size_gib}GiB")
return True
# Tente de créer le volume (sera ignoré s'il existe déjà)
nodes = ["acemagician", "elitedesk"]
return create_resource(resource_name, size_gib, nodes, ssh_key=ssh_key)
print(f"Taille actuelle: {current_size}GiB")
if current_size == size_gib:
print(f"✓ La taille correspond déjà ({size_gib}GiB), aucune action nécessaire")
return True
elif current_size < size_gib:
print(f"La taille doit être augmentée de {current_size}GiB à {size_gib}GiB")
if dry_run:
print(f"[DRY-RUN] Redimensionnerait {resource_name} à {size_gib}GiB")
return True
return resize_resource(resource_name, size_gib, ssh_key=ssh_key)
else:
print(f"⚠ La taille actuelle ({current_size}GiB) est supérieure à la taille souhaitée ({size_gib}GiB)")
print("La réduction de taille n'est pas supportée, conservation de la taille actuelle")
return True
def main():
"""Point d'entrée principal du script."""
parser = argparse.ArgumentParser(
description="Gestion des ressources DRBD Linstor pour les VMs K3s"
)
parser.add_argument(
"--vmid",
type=int,
required=True,
choices=[1000, 1001],
help="ID de la VM (1000=acemagician, 1001=elitedesk)"
)
parser.add_argument(
"--size",
type=int,
required=True,
help="Taille du disque en GiB"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Affiche les actions sans les exécuter"
)
parser.add_argument(
"--ssh-key",
type=str,
default=SSH_KEY_PATH,
help="Chemin vers la clé SSH privée (défaut: variable d'environnement SSH_KEY_PATH)"
)
args = parser.parse_args()
success = manage_vm_resource(args.vmid, args.size, args.dry_run, ssh_key=args.ssh_key)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()