KubeVirt - Partie 5: Containerized Data Importer (CDI)

Introduction

Lors du précédent article, nous avons déployé la solution de stockage distribué LongHorn.

L’objectif était de pouvoir disposer d’un stockage performant et résilient destiné à l’hébergement des données des machines virtuelles exécutées par KubeVirt.

Ce n’est cependant pas suffisant pour couvrir tous nos besoins. En effet, dans le cadre du maintien en condition opérationnel sur les VMs, peu importe l’écosystème, nous avons souvent besoin d’importer des disques ou des ISOs.

Dans l’univers de KubeVirt, on utilise pour cela Containerized Data Importer (CDI).

CDI est un projet open source essentiel et largement mis en œuvre.

Pour rappel, dans Kubernetes, les données sont stockées dans des Persistent Volume (PV). Habituellement, un PV est vide au démarrage. CDI offre la possibilité de créer une image de disque à partir d’une source existante, telle qu’un fichier .qcow2, .iso ou .raw.

CDI supporte de nombreux inputs :

  • URL HTTP/S : Télécharger une image depuis un serveur web.
  • S3 Compatible : Importer depuis un bucket AWS S3 ou MinIO.
  • Registry de conteneurs : Utiliser une image Docker qui contient un disque de VM.
  • Upload direct : envoyer un fichier depuis votre poste local via une API.
  • Clone : Copier un PVC existant vers un nouveau.

Principe de fonctionnement

Lorsqu’on déploie CDI sur un cluster Kubernetes, on introduit une nouvelle ressource personnalisée (CRD - Custom Ressource Definition) appelée DataVolume (DV).

C’est une couche d’abstraction supplémentaire dans laquelle on va définir la source d’une image disque qu’on va souhaiter utiliser.

CDI utilise ce DataVolume pour créer une demande de volume persistant ((PersistentVolumeClaim) (PVC)). Cette dernière entrainera à son tour la création d’un volume persistent (PV) sur lequel sera téléchargée, décompressée (le cas échéant) et écrit l’image.

Une fois terminé, le DV est marqué comme « Ready » et disponible pour un usage dans une VM KubeVirt qui va pouvoir bénéficier du PV rattaché.

Avantage

L’importation d’une image devient une opération déclarative exploitant un simple YAML comme pour tout objet K8S. CDI automatise les opérations et s’appuie intelligemment sur les configurations disque en place.

Objectif

Dans notre cas, nous allons exploiter CDI pour injecter l’image ISO de nos OS ainsi que pour préparer les disques racines de nos VMs.

Le premier cas d’usage va être la récupération de l’ISO de OPNsense.

Voici un schéma de ce qui va être construit.

Schéma CDI

Cliquez sur l'image pour l'agrandir.

Installation

Déploiement de l’operator

CDI peut s’installer directement via les YAMLS disponibles dans le repos github du projet. On récupère pour cela d’abord la dernière release disponible :

export VERSION=$(curl -s https://api.github.com/repos/kubevirt/containerized-data-importer/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')

Puis on applique les CRD (Custom Ressource Definition) pour créer l’opérator associé.

kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/$VERSION/cdi-operator.yaml
kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/$VERSION/cdi-cr.yaml
Deploiement de CDI

Cliquez sur l'image pour l'agrandir.

Par défaut toutes les ressources se déploient dans un namespace cdi. Je vous conseille de laisser le nom par défaut et de ne pas chercher à modifier ce paramétrage. Cela faciliterait les opérations d’update futur.

On peut vérifier que tout est correct via la commande :

kubectl get pods -n cdi
Controle des pods cdi

Cliquez sur l'image pour l'agrandir.

Déploiement de la HTTPRoute

Comme expliquer plus haut, CDI « injecte » la donnée issue de la source qu’on lui demande de récupérer. Pour que cela fonctionne, même au sein du cluster, il est essentiel d’exposer le service d'upload de CDI de manière à ce qu’il puisse recevoir les données.

Pour cela on va s’appuyer sur tout ce que nous avons déployé dans la partie 3 de ce cookbook dédiée à KubeVirt, à savoir la GatewayAPI et Traefik. Je ne vais pas revenir sur le sujet, et je vous invite à parcourir l’article dédié si vous souhaitez en savoir plus.

On va déjà autoriser le namespace CDI à être en mesure d’être vu par notre Gateway via l’ajout d’un label :

kubectl label namespace cdi network=lan

Puis on crée l’objet HTTPRoute à travers le fichier 01-httproute-cdi-upload.yml dont voici le contenu.

        
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-traefik-to-cdi
  namespace: cdi
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: Gateway
    namespace: inf-traefik-lan
  to:
  - group: ""
    kind: Service
    name: cdi-uploadproxy
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httproute-cdi-upload
  namespace: cdi
spec:
  parentRefs:
  - name: traefik-gateway-lan
    namespace: inf-traefik-lan
    sectionName: websecure
  hostnames:
  - cdi.coolcorp.priv
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: cdi-uploadproxy      

Comme déjà vu, on ajoute dans le fichier également l’objet ReferenceGrant pour permettre à la Gateway d’utiliser le certificat wildcard qu’on a précédemment généré (voir l'article dédiée). Ceci va nous permettre d’exposer le point d’upload de CDI sur l’URL cdi.coolcorp.priv.

La requete sera redirigée vers le service cdi-uploadproxy déployé lors de l'installation de CDI.

Il suffit d'appliquer le fichier.

Création de la httproute

Cliquez sur l'image pour l'agrandir.

Injection de l’ISO

Création du DV

À ce stade tout est prêt, il ne nous reste plus qu’à récupérer notre ISO sur le site de OPNsense. On peut dédier un dossier sur le serveur pour récupérer cette dernière :

sudo mkdir -p /mnt/local/isos
sudo chown -R bvivi57:bvivi57 /mnt/local/isos
Création du dossier pour les ISOs

Cliquez sur l'image pour l'agrandir.

Comme celle-ci est au format bz2, il faudra s’assurer d’avoir le package bzip2 pour accéder au format iso directement.

sudo dnf install -y bzip2
bunzip2 OPNsense-24.7-dvd-amd64.iso.bz2
Déchiffrement de l'ISO

Cliquez sur l'image pour l'agrandir.

Notre source ISO est prête et disponible. Nous allons pouvoir créer notre objet DataVolume (DV). Juste avant, on va définir un namespace dev-opnsenselantoweb-inf destiné à recevoir notre futur VM OPNSense. Étant donné que notre ISO et donc notre DV va être exploité par cette dernière, autant créer notre DV dans le namespace associée.

kubectl create namespace dev-opnsenselantoweb-inf

Le DV est représenté par le fichier 01-dv-opnsenselantoweb-iso.yml dont voici le contenu :


---
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: dv-opnsenselantoweb-iso
  namespace: dev-opnsenselantoweb-inf
  labels:
    environment: dev
    application: opnsenselantoweb
spec:
  source:
    upload: {}
    #http:
    #   url: "https://pkg.opnsense.org/releases/24.7/OPNsense-24.7-dvd-amd64.iso.bz2"
  pvc:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 4Gi
    storageClassName: sc-longhorn-sata

Comme vous pouvez le constater, j’avais prévu au départ d’intégrer la récupération automatique de l’ISO directement depuis le site de OPNsense avec comme type de source « upload ». Malheureusement, cela ne fonctionnera pas en raison du format bz2, car le CDI attend un fichier ISO non compressé. J’ai néanmoins laissé l’exemple en commentaire.

Comme nous l’avons vu plus haut, notre DV va donner lieu à un PVC et donc à un PV, car il va bien falloir stocker le contenu de notre ISO dans un volume persistant. On s’appuie donc sur notre classe de storage sc-longhorn-sata créée lors de l'article précédent et associée aux disques SSD sata présent dans le serveur, utilisés par longhorn.

Attention à la taille du volume retenu, il faut qu’il soit suffisant pour héberger l’ISO. On applique le fichier :

kubectl apply -f 01-dv-opnsenselantoweb-iso.yml

Upload de l’ISO

Comme je n’ai pas pu nativement récupérer l’ISO dans la création du DV, celui-ci est actuellement vide et non exploitable. On va donc devoir injecter manuellement notre ISO récupéré localement en passant par notre HTTPRoute déployée juste avant. Pour cela, on passe par la CLI virtctl, qu’on a déployée en même temps que KubeVirt.

virtctl image-upload dv dv-opnsenselantoweb-iso \
  --namespace dev-opnsenselantoweb-inf \
  --image-path=/mnt/local/isos/OPNsense-24.7-dvd-amd64.iso \
  --size=4Gi \
  --storage-class=sc-longhorn-sata \
  --access-mode=ReadWriteOnce \
  --insecure \
  --uploadproxy-url=https://cdi.coolcorp.priv
  --force-bind

Comme vous pouvez le constater, on fournit à l’argument image-upload un ensemble de paramètres, à commencer par le nom et le namespace de notre DV. On indique ou se trouve la source, notre ISO local, et comment l’uploader au sein du DV soit via notre URL rendu accessible par notre HTTProute.

Lancement de l'upload

Cliquez sur l'image pour l'agrandir.

On peut suivre l’avancement de l’upload et s’assurer de la réussite de l’opération via la commande:

kubectl get datavolume dv-opnsenselantoweb-iso -n dev-opnsenselantoweb-inf
Controle de l'upload de l'ISO

Cliquez sur l'image pour l'agrandir.

Il faut qu’on termine avec un indicateur de progression à 100 %. On peut découvrir toute la logique en remontant la filiation des objets créés.

Si on liste les PVC dans le namespace via la commande :

kubectl get pvc -n dev-opnsenselantoweb-inf
Controle des PVC

Cliquez sur l'image pour l'agrandir.

On a bien notre PVC dv-opnsenselantoweb-iso (du même nom que le DV) rattaché à un PV pvc-08511331-e6c2-4149-a18c-993cea4dc563 dans la StorageClass sc-longhorn-sata. PV qu'on peut retrouver via :

kubectl get pv pvc-08511331-e6c2-4149-a18c-993cea4dc563
Controle des PV

Cliquez sur l'image pour l'agrandir.

DV « root » supplémentaire

Avant de terminer cette partie du cookbook, il reste une dernière chose à faire dont on n’a pas parlé encore, mais visible dans mon schéma: le DV pour le disque root de la VM.

En effet, les DV ne sont pas réservés aux ISO, mais servent à toutes images de disque, même vides, rattachables à une VM. On va donc utiliser un second fichier yaml 02-dv-opnsenselantoweb-root.yml dont voici le contenu.


---
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: dv-opnsenselantoweb-root
  namespace: dev-opnsenselantoweb-inf
  labels:
    environment: dev
    application: opnsenselantoweb
spec:
  source:
    blank: {}
  pvc:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 40Gi 

La syntaxe est la même que pour le DV dédié à l’ISO, à la différence près qu’on laisse une source vide et qu’on réfère à la SC sc-longhorn-nvme pour exploiter le disque NVME haute performance qu’on a configuré avec LongHorn. On applique le fichier:

kubectl apply -f 02-dv-opnsenselantoweb-root.yml

Qu’allons-nous en faire maintenant ? Nous verrons ça dans le prochain article à venir où nous allons enfin pouvoir créer notre première VM.