Une Digital Factory sur Harvester : provisionner des centaines de clusters RKE2 à la demande
Hyperconvergence en source ouverte, machines virtuelles pilotées par KubeVirt, clusters RKE2 livrés en quinze minutes. L'architecture complète d'une usine à clusters, du socle matériel au catalogue en libre-service.
La demande revient dans toutes les grandes organisations : des équipes veulent un cluster Kubernetes, tout de suite, sans ouvrir un ticket qui mettra trois semaines. La réponse classique — un cluster mutualisé avec des espaces de noms — ne convient pas à tous les cas : essais de version, tests de montée de version, environnements réglementés qui exigent une isolation matérielle.
Harvester apporte le socle qui manquait. C'est une solution d'hyperconvergence en source ouverte bâtie sur Kubernetes, qui expose des machines virtuelles via KubeVirt et du stockage répliqué via Longhorn. Combinée à Rancher, elle transforme un parc de serveurs en usine à clusters RKE2.
L'empilement, de bas en haut
┌──────────────────────────────────────────────┐
│ Rancher — catalogue et libre-service │
│ demande → cluster RKE2 livré en 15 min │
├──────────────────────────────────────────────┤
│ Harvester — hyperconvergence │
│ KubeVirt (calcul) · Longhorn (stockage) │
│ Multus + SR-IOV (réseau) │
├──────────────────────────────────────────────┤
│ RKE2 sous-jacent — socle Harvester │
├──────────────────────────────────────────────┤
│ Matériel — serveurs, NVMe, 25 GbE │
└──────────────────────────────────────────────┘Le point d'élégance : Harvester est lui-même un cluster Kubernetes. Provisionner une machine virtuelle revient à créer une ressource personnalisée. Tout l'outillage habituel s'applique — GitOps compris.
Dimensionner le socle
Trois erreurs reviennent systématiquement sur ce type de plateforme.
Sous-dimensionner le réseau de stockage. Longhorn réplique chaque volume trois fois de façon synchrone. Une écriture de 100 Mio/s génère 300 Mio/s de trafic. Sur du 10 GbE partagé avec le trafic applicatif, la saturation arrive vite. Prévoyez un réseau de stockage dédié en 25 GbE.
Mélanger le trafic de gestion et le trafic des machines virtuelles. Un pic de trafic applicatif ne doit jamais perturber les battements de cœur du cluster.
Oublier la marge de reprise. Si un nœud tombe, ses machines virtuelles redémarrent ailleurs. Sans réserve de 25 % au minimum, elles restent en attente.
| Rôle | Quantité | Configuration |
|---|---|---|
| Nœuds de gestion | 3 | 16 cœurs, 64 Gio, 2 × 480 Gio NVMe |
| Nœuds de calcul | 12 à 40 | 64 cœurs, 512 Gio, 4 × 3,84 Tio NVMe |
| Réseau de gestion | — | 2 × 10 GbE agrégés |
| Réseau des machines | — | 2 × 25 GbE, VLAN étiquetés |
| Réseau de stockage | — | 2 × 25 GbE dédiés à Longhorn |
Configurer le réseau
Harvester pilote le réseau par ClusterNetwork et VlanConfig. Chaque locataire reçoit son VLAN.
apiVersion: network.harvesterhci.io/v1beta1
kind: ClusterNetwork
metadata:
name: vm-workload
---
apiVersion: network.harvesterhci.io/v1beta1
kind: VlanConfig
metadata:
name: vm-workload-uplink
spec:
clusterNetwork: vm-workload
nodeSelector:
harvesterhci.io/managed: "true"
uplink:
nics: ["ens5f0", "ens5f1"]
bondOptions: { mode: 802.3ad, miimon: 100 }
linkAttributes: { mtu: 9000 } # trames étendues : +15 % de débit utile
---
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: vlan-equipe-paiement
namespace: harvester-public
labels:
network.harvesterhci.io/clusternetwork: vm-workload
network.harvesterhci.io/vlan-id: "412"
spec:
config: |
{
"cniVersion": "0.3.1",
"name": "vlan-equipe-paiement",
"type": "bridge",
"bridge": "vm-workload-br",
"promiscMode": true,
"vlan": 412,
"ipam": {}
}Les trames étendues à 9000 octets ne relèvent pas du détail : sur du trafic de stockage répliqué, elles apportent environ 15 % de débit utile supplémentaire.
Le modèle de machine virtuelle
La clé du provisionnement rapide tient dans l'image. Une image Ubuntu générique demande huit minutes d'installation de paquets à chaque démarrage. Une image préparée avec Packer, contenant déjà le moteur de conteneur et les binaires RKE2, démarre en quatre-vingt-dix secondes.
apiVersion: harvesterhci.io/v1beta1
kind: VirtualMachineImage
metadata:
name: socle-rke2-1-31
namespace: harvester-public
spec:
displayName: "Socle RKE2 1.31 — durci"
sourceType: download
url: http://depot.interne/images/socle-rke2-1.31.4-durci.qcow2
checksum: "sha512:4f2a9c8e..."Le durcissement se fait une fois, dans l'image, plutôt qu'à chaque démarrage. C'est ce qui permet de tenir la promesse des quinze minutes.
Le catalogue en libre-service
Rancher expose le provisionnement RKE2 sur Harvester. Une équipe déclare son besoin, la plateforme livre.
apiVersion: provisioning.cattle.io/v1
kind: Cluster
metadata:
name: bac-a-sable-paiement
namespace: fleet-default
spec:
kubernetesVersion: v1.31.4+rke2r1
rkeConfig:
machinePools:
- name: plan-controle
quantity: 3
etcdRole: true
controlPlaneRole: true
machineConfigRef:
kind: HarvesterConfig
name: gabarit-plan-controle
- name: travail
quantity: 3
workerRole: true
machineConfigRef:
kind: HarvesterConfig
name: gabarit-travail
machineGlobalConfig:
cni: calico
disable: ["rke2-ingress-nginx"] # l'entrée vient du catalogue interne
etcd-snapshot-schedule-cron: "0 */6 * * *"
etcd-snapshot-retention: 20
kube-apiserver-arg:
- "audit-log-path=/var/log/audit/audit.log"
- "audit-policy-file=/etc/rancher/rke2/audit-policy.yaml"
upgradeStrategy:
controlPlaneConcurrency: "1"
workerConcurrency: "25%"
controlPlaneDrainOptions: { enabled: true, timeout: 600 }Le machineGlobalConfig porte la conformité : sauvegarde etcd toutes les six heures, journalisation d'audit activée, contrôleur d'entrée par défaut désactivé au profit de celui du catalogue interne. Chaque cluster naît conforme.
Automatiser la demande
Le libre-service sans garde-fou produit trois cents clusters orphelins en six mois. Un opérateur interne interpose une validation et une date d'expiration.
// Réconciliation d'une DemandeCluster : validation du quota, puis création.
func (r *ReconciliateurDemande) Reconcile(ctx context.Context,
req ctrl.Request) (ctrl.Result, error) {
var demande platformev1.DemandeCluster
if err := r.Get(ctx, req.NamespacedName, &demande); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Expiration : un cluster de bac à sable ne vit pas éternellement.
if demande.Spec.Duree != "" && demande.Status.CreeLe != nil {
duree, _ := time.ParseDuration(demande.Spec.Duree)
if time.Since(demande.Status.CreeLe.Time) > duree {
r.Recorder.Event(&demande, corev1.EventTypeNormal,
"Expiration", "durée de vie atteinte, suppression engagée")
return ctrl.Result{}, r.supprimerCluster(ctx, &demande)
}
}
// Quota par équipe : évite qu'une seule équipe absorbe la plateforme.
utilise, err := r.calculerQuotaEquipe(ctx, demande.Spec.Equipe)
if err != nil {
return ctrl.Result{}, err
}
if utilise+demande.Spec.Vcpu > r.quotaMax(demande.Spec.Equipe) {
demande.Status.Phase = "Refusee"
demande.Status.Message = fmt.Sprintf(
"quota dépassé : %d vCPU utilisés, %d demandés, %d autorisés",
utilise, demande.Spec.Vcpu, r.quotaMax(demande.Spec.Equipe))
return ctrl.Result{}, r.Status().Update(ctx, &demande)
}
return ctrl.Result{RequeueAfter: time.Hour}, r.creerCluster(ctx, &demande)
}La date d'expiration est le mécanisme le plus rentable de toute la plateforme. Un cluster de bac à sable créé pour trois jours et qui tourne depuis huit mois représente du gâchis pur.
Tenir la charge à l'échelle
Au-delà de cent clusters, deux limites apparaissent.
L'apiserver de Harvester. Chaque machine virtuelle produit des événements et des mises à jour de statut. Trois mille machines saturent un plan de contrôle mal dimensionné. Surveillez apiserver_request_duration_seconds et prévoyez de séparer l'etcd sur des disques dédiés.
La reconstruction Longhorn. La perte d'un nœud déclenche la reconstruction de tous ses réplicas. Sur 15 Tio, cela représente plusieurs heures de trafic soutenu. Limitez la concurrence de reconstruction, sous peine de transformer une panne d'un nœud en dégradation généralisée.
apiVersion: longhorn.io/v1beta2
kind: Setting
metadata:
name: concurrent-replica-rebuild-per-node-limit
namespace: longhorn-system
value: "2"Le résultat
Une équipe remplit un formulaire. Quinze minutes plus tard, elle reçoit son fichier d'accès à un cluster RKE2 de six nœuds, sauvegardé, audité, conforme aux politiques d'entreprise, et programmé pour disparaître à la date annoncée.
L'exploitation ne gère plus des clusters mais une plateforme. C'est exactement la différence entre subir la demande et l'industrialiser.