PI-GROUPE
KubernetesInfrastructureVirtualisationIaCDevOpsOpen SourceCloud

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

text
┌──────────────────────────────────────────────┐
│  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ôleQuantitéConfiguration
Nœuds de gestion316 cœurs, 64 Gio, 2 × 480 Gio NVMe
Nœuds de calcul12 à 4064 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.

yaml
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.

yaml
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.

yaml
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.

go
// 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.

yaml
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.