KubeVirt - Partie 6: Création de la VM et usages

Introduction

Il est maintenant temps de terminer cette suite d’articles par l’exécution de notre VM OPNsense sous Kubevirt.

Pour ceux qui arriveraient ici sans avoir parcouru le début du tutoriel, je vous invite à reprendre du début via ce lien.

Rappel des composants en place

Élément pour le stockage

Précédemment nous avons créé les DataVolume (DV) nécessaires à la VM dans le namespace dev-opnsenselantoweb-inf spécifiquement mis en œuvre pour notre besoin.

Deux disques de données (DataVolume) ont été montés: dv-opnsenselantoweb-iso, qui héberge l’image ISO d’installation d’OPNsense, et dv-opnsenselantoweb-root, qui servira de disque pour la machine virtuelle et l’installation du système d’exploitation.

Ces disques ont été importés via Containerized Data Importer (CDI). Cet espace est géré via une StorageClass sc-longhorn-nvme, elle-même sous gestion de la solution de stockage distribué Longhorn.

Élément pour le réseau

Dans un article dédié, on s’est occupé des besoins réseau via la création de deux NetworkAttachmentDefinition :

  • nad-lan-default : Connexion de la VM au LAN par défaut.
  • nad-vlan-web : Connexion de la VM au VLAN WEB.

Pour rappel, l’objectif est de pouvoir utiliser OPNsense comme un firewall entre deux zones réseau, il faut donc à la VM deux interfaces.

Schéma final

On peut résumer notre VM aux besoins suivants :

Schéma final VM OPNsense

Cliquez sur l'image pour l'agrandir.

Il ne reste maintenant plus qu’à créer l’objet Kubernetes qui va regrouper tout cela, à savoir un objet VirtualMachine issu de la branche API kubevirt.io/v1.

Déploiement de la VM

Fichier YAML

Voici le contenu du fichier YAML 03-vm-opnsenselantoweb-inf.yml :

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: vm-opnsenselantoweb-inf
  namespace: dev-opnsenselantoweb-inf
  labels:
    app: opnsense
spec:
  running: false
  template:
    metadata:
      labels:
        kubevirt.io/domain: vm-opnsenselantoweb-inf
    spec:
      domain:
        cpu:
          cores: 2
        resources:
          requests:
            memory: 2Gi
        devices:
          disks:
            - name: cdrom-iso
              bootOrder: 1
              cdrom:
                bus: sata
            - name: harddrive
              bootOrder: 2
              disk:
                bus: virtio
          interfaces:
            - name: lan
              bridge: {}
            - name: wan
              bridge: {}
      networks:
        - name: lan
          multus:
            networkName: dev-opnsenselantoweb-inf/nad-lan-default
        - name: wan
          multus:
            networkName: dev-opnsenselantoweb-inf/nad-vlan-web
      volumes:
        - name: cdrom-iso
          dataVolume:
            name: dv-opnsenselantoweb-iso
        - name: harddrive
          dataVolume:
            name: dv-opnsenselantoweb-root

L’objet VirtualMachine (VM) hérite de toute la logique classique des objets Kubernetes. C’est d’ailleurs un des avantages de KubeVirt, puisqu'une VM ne devient ni plus ni moins qu’un simple objet complémentaire au cluster qu’il va nous être possible de manipuler comme on en a l’habitude pour nos applications conteneurisées.

On retrouve donc la notion de namespace, ici dev-opnsenselantoweb-inf, puis la capacité à intégrer des labels pour aider à l’identification de la VM.

Dans la section spec, on va retrouver les points spécifiques à l’objet VM, comme le nombre de CPU et la quantité de mémoire.

Puis, on va s’appliquer à définir les disques, nos deux DV, qu’on pourra ensuite référencer dans la section Volume, de manière à pouvoir y faire référence dans la section dédiée au disque de la VM :

  • Le datavolume dv-opnsenselantoweb-iso référencé sous cdrom-iso qu’on va rattacher à l’émulation d’un bus SATA en mode readonly.
  • Le datavolume dv-opnsenselantoweb-root référencé sous harddrive qu’on va rattacher à l’émulation d’un bus virtio. (Virtio est le standard de l'industrie pour l'I/O (entrées/sorties) "paravirtualisées". Dans l'écosystème KVM et KubeVirt, c'est ce qui permet à une machine virtuelle (VM) de communiquer avec l'hôte de manière ultra-rapide, sans le ralentissement lié à l'émulation matérielle classique).

Notez qu’il est possible de fixer l’ordre du démarrage.


De la même manière, on va appliquer la conf réseau en associant notre NetworkAttachmentDefinition nad-lan-default à l’interface virtuelle « lan » et notre NetworkAttachmentDefinition nad-vlan-web à l’interface virtuelle « wan ».

Étant donné que les NetworkAttachmentDefinition ne sont pas dans le même namespace que la VM, il faut indiquer leur chemin complet dans la section network.


Les plus attentifs d’entre vous auront remarqué que j’ai fixé les adresses mac des interfaces virtuelles.

C’est effectivement une assurance de ma part de conserver des MAC identiques si, par la suite je venais à déplacer ma VM sur un autre node. Par contre cela peut vite tourner aux problèmes quand je vais multipler les VMs, il faut donc bien choisir une convention de nommage stricte et logique pour éviter le risque de doublon.


À noter que le préfixe MAC 02:00:00 ne correspond pas à un constructeur, mais est spécifique au réseau virtuel et utilisé par d’autres solutions, comme Proxmox ou OpenStack.


Il ne reste plus qu’à exécuter la VM via la classique commande kubectl apply :

kubectl apply -f 03-vm-opnsenselantoweb-inf.yml
Application du yaml de la VM

Cliquez sur l'image pour l'agrandir.

On peut vérifier son statut via :

kubectl get vm -n dev-opnsenselantoweb-inf
Controle du statut de la VM

Cliquez sur l'image pour l'agrandir.

Accès à la console

Le problème maintenant va être d’accéder à l’interface d’installation. À priori, la VM est démarrée. Si la configuration est correcte, elle devrait avoir booté à partir de l’ISO d’OPNsense.


Dans un scénario habituel, on devrait voir l’écran de démarrage ou d’installation d’OPNsense. Mais, dans notre cas, il n’y a pas de console, donc nous ne voyons pas ce qui se passe.


Heureusement, il est possible d’y ’accéder via le bon vieux protocole VNC.


Il va d’abord falloir ouvrir un port sur notre worker pour qu’il puisse rendre accessible le flux VNC. On peut prendre le port de son choix dans la liste des ports disponibles, en l’occurrence pour l’exemple ici le port 5900.

Étant donné qu’on est sur Rocky Linux 10, on fait appel aux commandes:

sudo firewall-cmd --add-port=5900/tcp --permanent
sudo firewall-cmd --reload
Ouverture des ports pour le flux VNC

Cliquez sur l'image pour l'agrandir.

Attention, ce flux est à ouvrir sur tout worker susceptible d’exécuter la VM. Dans mon lab, pour l’instant, je n’ai qu’un seul serveur jouant le rôle de control plane et de worker. Cependant, dans des situations réelles, il faut bien penser à traiter tous les nœuds d'exécution.

Ensuite, on va utiliser notre CLI virtctl, qui a été déployée dans un article précédent, pour indiquer qu’on souhaite rediriger notre connexion VNC vers la VM

virtctl vnc vm-opnsenselantoweb-inf \
  -n dev-opnsenselantoweb-inf \
  --proxy-only=true \
  --address=0.0.0.0 \
  --port=5900
Commande pour proxy du flux VNC

Cliquez sur l'image pour l'agrandir.

De là, il suffit d’avoir un client VNC sous la main pour faire une connexion.

Personnellement, j’utilise TigerVNC sous Windows, mais chacun est libre de faire le choix du client qu’il souhaite.

Depuis mon poste, en configurant ce dernier pour qu’il se connecte à l’adresse IP de notre serveur sur le port 5900, j’ai accès à la console de la VM.

Accès console VNC

Cliquez sur l'image pour l'agrandir.

Accès console VNC

Cliquez sur l'image pour l'agrandir.

De là, je peux piloter l’installation et faire la configuration de OPNsense.

Configuration OPNsense

Cliquez sur l'image pour l'agrandir.

Je ne vais pas détailler spécifiquement ce point, mais je vais demander à inscrire les binaires sur le disque root de la VM et associer les bonnes interfaces virtuelles aux interfaces OPNsense tout en renseignant leur IP.

Configuration OPNsense

Cliquez sur l'image pour l'agrandir.

Configuration OPNsense

Cliquez sur l'image pour l'agrandir.

Configuration OPNsense

Cliquez sur l'image pour l'agrandir.

Configuration OPNsense

Cliquez sur l'image pour l'agrandir.

Premier reboot

Une fois l’installation terminée, je peux redémarrer la VM.

Mais je vais d’abord l’éteindre pour modifier sa configuration et retirer le support du lecteur de CD-ROM émulé afin de forcer le démarrage sur le disque associé au DV dv-opnsenselantoweb-root.

Pour cela, on recourt également à la ligne de commande virtctl:

virtctl stop vm-opnsenselantoweb-inf -n dev-opnsenselantoweb-inf

J’édite le yaml, je commente ce qui n'est plus nécessaire, puis je le réapplique

Modification conf VM

Cliquez sur l'image pour l'agrandir.

Modification conf VM

Cliquez sur l'image pour l'agrandir.

kubectl apply -f 03-vm-opnsenselantoweb-inf.yml

Je lance le démarrage de la VM:

virtctl start vm-opnsenselantoweb-inf -n dev-opnsenselantoweb-inf
Demarrage VM

Cliquez sur l'image pour l'agrandir.

Je pourrais relancer la commande virtctl vnc vm-opnsenselantoweb-inf -n dev-opnsenselantoweb-inf --proxy-only=true --address=0.0.0.0 --port=5900 pour regagner l’accès à la console.

Mais je vais plutôt attendre quelques minutes. Si tout fonctionne correctement, je pourrais me connecter à l’interface d’OPNsense directement via mon navigateur, en spécifiant non pas l’adresse IP de mon nœud, mais l’adresse IP attribuée à l’interface LAN de ma VM, qui est reliée à mon NetworkAttachmentDefinition nad-lan-default.

Accès GUI OPNsense

Cliquez sur l'image pour l'agrandir.

Test fonctions OPNsense

Cliquez sur l'image pour l'agrandir.

C’est chose faite, ma VM est opérationnelle. On peut tester différentes commandes au sein de OPENsense ou via d’autres machines présentes sur mes différents réseaux pour contrôler que les deux interfaces sont bien ups et joignables.

GUI additionnelle

Idéalement, il serait intéressant d’avoir une GUI à KubeVirt pour avoir une vue sur les VMs directement à partir d’une interface WEB. Pour ce faire, vous pouvez utiliser KubeVirt Manager.

On peut trouver d’autres options, mais Kubevirt-Manager se distingue comme étant la solution la plus simple et complète pour ce domaine.

Son déploiement passe par plusieurs objets K8S que je vais quelque peu adapter à mon besoin.

Nous allons d’abord affecter un namespace dédié, inf-kubevirtmanager-lan.

Kubectl create ns inf-kubevirtmanager-lan
Création Namespace pour Kubevirt Manager

Cliquez sur l'image pour l'agrandir.

Ensuite j’aimerais rendre l’interface accessible à travers un login/mot de passe.

Pour ça je définis un fichier 01-middelware-kubevirtmanager-auth.yml chargé de créer un middelware Traefik:


apiVersion: v1
kind: Secret
metadata:
  name: sec-kubevirtmanager-auth
  namespace: inf-kubevirtmanager-lan
type: Opaque
stringData:
  users: bvivi57:xxxxxxxxxxxxxxxxxxxxxxxxx
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: middelware-kubevirtmanager-auth
  namespace: inf-kubevirtmanager-lan
spec:
  basicAuth:
    secret: sec-kubevirtmanager-auth

Je ne vais pas revenir en détail sur ce point, je vais refaire ce que j’ai fait pour sécuriser la GUI de Longhorn, à savoir définir un secret en charge de stocker mon login et mon mot de passe, puis passer ce secret au middleware Traefik.

Ensuite, on va exploiter un fichier 02-right-kubevirtmanager.yml qui va porter sur les accès de kubevirt-manager à Kubevirt:


apiVersion: v1
kind: Namespace
metadata:
  name: inf-kubevirtmanager-lan
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-kubevirt-manager
  namespace: inf-kubevirtmanager-lan
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: crb-kubevirt-manager
subjects:
  - kind: ServiceAccount
    name: sa-kubevirt-manager
    namespace: inf-kubevirtmanager-lan
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

En effet, la GUI va devoir pouvoir accéder à l’API kubernetes pour lire les objets kubernetes et les manipuler.

On passe donc par un compte de service sa-kubevirt-manager disposant des droits sur le cluster via le cluster role cluster-admin.

Pour ceux qui ne seraient pas à l’aise avec la notion de droit et notamment de RBAC de Kubernetes, je les invite à lire mon article dédié au sujet.

On pourrait me faire le reproche (justifié) que le rôle de cluster-admin est trop important pour les besoins de kubevirt-manager. Cependant, j’ai pris ce raccourcis en utilisant ce rôle par défaut qui donne accès à tous les namespaces et objets K8S.

On enchaine avec le fichier 03-deployment-kubevirtmanager.yml, soit le classique Deployment:


---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dpl-kubevirt-manager
  namespace: inf-kubevirtmanager-lan
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kubevirt-manager
  template:
    metadata:
      labels:
        app: kubevirt-manager
    spec:
      serviceAccountName: sa-kubevirt-manager
      containers:
        - name: kubevirt-manager
          image: kubevirtmanager/kubevirt-manager:latest
          ports:
            - containerPort: 8080
          env:
            - name: KUBERNETES_Auth_Type
              value: "ServiceAccount"

Qui va de pair avec le fichier 04-svc-kubevirtmanager.yml pour la création du Service:


---
apiVersion: v1
kind: Service
metadata:
  name: svc-kubevirt-manager
  namespace: inf-kubevirtmanager-lan
spec:
  selector:
    app: kubevirt-manager
  ports:
    - port: 80
      targetPort: 8080[

Enfin, s’appuyant sur tout ce qui a été fait dans la troisième partie de ce cookbook consacré à Kubevirt, on termine avec le fichier 05-http-kubevirtmanager.yml pour la création de la HTTPRoute afin d’exposer à l’extérieur l’URL « kubevirt-manager.coolcorp.priv » que j’ai choisie pour l’accès à kubevirt-manager:


---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-traefik-to-manager
  namespace: inf-kubevirtmanager-lan
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: Gateway
    namespace: inf-traefik-lan
  to:
  - group: ""
    kind: Service
    name: svc-kubevirt-manager
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httproute-kubevirt-manager
  namespace: inf-kubevirtmanager-lan
spec:
  parentRefs:
  - name: traefik-gateway-lan
    namespace: inf-traefik-lan
    sectionName: websecure
  hostnames:
  - kubevirt-manager.coolcorp.priv
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      filters:
        # On active l'authentification située dans le namespace Traefik
        - type: ExtensionRef
          extensionRef:
            group: traefik.io
            kind: Middleware
            name: middelware-kubevirtmanager-auth
      backendRefs:
        - name: svc-kubevirt-manager
          port: 80

Je ne vais pas m’étendre sur ce fichier. Toutes les informations nécessaires sont déjà couvertes dans l’article dédié, ainsi que dans l’article sur l’exposition de la GUI Longhorn avec l’API Gateway et Traefik.

À noter simplement que j’associe mon Middleware créé juste avant pour protéger mon url via demande de login et de mot de passe grâce à Traefik.

De la on applique le tout (kubectl apply -f nom_dossier_qui_contient_les_yamls), pour accéder ensuite à l’URL https://kubevirt-manager.coolcorp.priv.

Application de la conf pour kubevirtmanager

Cliquez sur l'image pour l'agrandir.

(On oublie pas de labéliser le namespace pour qu'il soit pris en compte par notre Gateway (voir article dédié))

Labélisation du namespace

Cliquez sur l'image pour l'agrandir.

On a l’invite de login/mot de passe que l’on complète avec le contenu du secret qu’on a retenu, puis on peut accéder à la GUI pour piloter ses VMs.

GUI de kubevirt manager

Cliquez sur l'image pour l'agrandir.

Kubevirt-manager possède toutes les fonctionnalités vues précédemment, telles que l’arrêt et la mise en marche de la VM, ainsi que l’accès à la console de la VM, et ce, sans dépendre d’un client VNC. Cet accès se fait directement depuis un navigateur.

Détails de la VM via kubevirt manager

Cliquez sur l'image pour l'agrandir.

Accès à la console via kubevirt manager

Cliquez sur l'image pour l'agrandir.

Conclusion

Nous voici arrivés au bout de cette série de tutoriels destinés à appréhender KubeVirt.

L’écosystème associé est en constante évolution, mais ne cesse de grandir.

Si aujourd’hui, KubeVirt s’utilise principalement à travers des solutions packagées comme OpenShift, on voit bien qu’avec un peu de travail, on peut arriver à construire son cluster KubeVirt à partir des briques de bases.

Qu’on ait besoin de plusieurs interfaces réseau, de stockage spécifique ou d’un accès console, tout est adressable à partir des paquets de bases, et ceci de plusieurs manières possibles.

Grâce à sa base KVM et à son intégration Kubernetes, KubeVirt est certainement une solution d’avenir et une alternative plus que crédible à d’autres solutions de virtualisation.

Reste maintenant à poursuivre l’aventure, avec un véritable cluster multinode et l’évaluation de compétences supplémentaires, comme le déplacement de VMs d’un node à un autre, ou la reprise automatique sur incident.

Ceci arrivera, je l’espère, plus tard dans l’année, grâce à la mise en place d’un nouveau homelab, ce qui me permettra de proposer un cookbook plus complet.

D’ici là, n’hésitez pas à expérimenter par vous-même et vous à inspirer, si nécessaire, de ce qui a été présenté jusque-là ici.