Contexte

J’utilise beaucoup Talos en ce moment, et je suis confronté à un besoin spécifique : je souhaite utiliser des disques externes pour créer des PVC pour mes applications. C’est un cas d’utilisation assez spécifique, mais je pense que cela peut être utile pour d’autres personnes, voici le résultat de mes recherches.

Talos

Petit rappel sur ce qu’est Talos : c’est une distribution immuable dédiée à Kubernetes. Celle-ci est réputée pour être minimaliste (pas de shell, pas de systemd, pas de gestionnaire de paquet). Ainsi lorsqu’on souhaite modifier un paramètre dans le système, il faut passer par des patchs yaml qui seront appliqués par talosctl.

La problématique dans le fait de monter des disques dans Talos est que nous devons nous plier à ce que Talos demande et qu’il n’est pas possible d’utiliser des outils comme fdisk ou mkfs pour formater les disques.

L’objectif est alors le suivant : Créer un PV par disque. Pour cela, nous allons utiliser le provisionneur local-static-provisioner.

Installation of local-static-provisioner

Local Static Provisioner est un provisionneur de stockage qui permet de créer des PV à partir de disques locaux. Dans les faits, nous pouvons lui donner un dossier qu’il va surveiller. Dès qu’un point de montage est créé dans ce dossier, il va créer un PV associé à ce point de montage.

Celui-ci s’installe via un Helm Chart :

helm repo add sig-storage-local-static-provisioner https://kubernetes-sigs.github.io/sig-storage-local-static-provisioner/
helm install local-static-provisioner sig-storage-local-static-provisioner/local-static-provisioner -n local-static-provisioner --create-namespace

Dans le fichier valeur, nous pouvons définir les classes de stockage (StorageClass) que nous souhaitons créer. Pour chaque classe, nous devons définir un dossier qui sera surveillé par le provisionneur.

classes:
- name: fast-storage
  hostDir: /var/lib/disks/ssd/
  volumeMode: Filesystem
  storageClass: true
  fsType: xfs
- name: slow-storage
  hostDir: /var/lib/disks/hdd/
  volumeMode: Filesystem
  fsType: xfs
  storageClass: true
- name: block-storage
  hostDir: /var/lib/disks/block/
  blockCleanerCommand:
  - "/scripts/quick_reset.sh"
  volumeMode: Block
  storageClass: true

Remarque

Comme montré dans le dernier dossier (block-storage), il est aussi possible de créer des volumes en “Block” (qui seront formatés au scheduling). Dans ce cas, plutôt que de créer des points de montage, nous devons créer des liens symboliques vers les disques.

ln -s /dev/sdb /var/lib/disks/block/sdb

À savoir que Talos ne permet (pas encore?) la création de ce lien symbolique directement dans sa configuration. Il faudra passer par un pod avec 2 volumes HostPath pour créer ce lien.

Nous reparlerons des volumes en Block un plus bas.

Voici les 3 classes de stockage que nous avons créées :

  • fast-storage pour les disques SSD
  • slow-storage pour les disques HDD
  • block-storage pour tous les disques qui seront en mode bloc.

Cependant, ces SC ne peuvent actuellement pas provisionner de PVC. Nous devons encore ajouter les disques aux dossiers configurés dans le fichier valeur.

Configuration des disques

Considérons que nous avons 5 disques par machine, voici notre organisation :

  • vda - Disque système (ne sera pas utilisé)
  • vdb - SSD
  • vdc - SSD
  • vdd - HDD
  • vde - HDD
  • vdf - Block
  • vdg - Block

Pour créer ces points de montage, nous devons les ajouter dans le fichier de configuration de Talos. Celui-ci va créer les dossiers demandés lors de l’ajout de cette configuration.

machine:
  disks:
      - device: /dev/vdb
        partitions:
          - mountpoint: /var/lib/disks/ssd/vdb
      - device: /dev/vdc
        partitions:
          - mountpoint:  /var/lib/disks/ssd/vdb
      - device: /dev/vdd
        partitions:
          - mountpoint: /var/lib/disks/hdd/vdd
      - device: /dev/vde
        partitions:
          - mountpoint: /var/lib/disks/hdd/vde

Après avoir appliqué ce patch à mes workers via talosctl patch mc --nodes w-1,w-2,w-3 --patch @patch.yaml Local Static Provisioner devrait avoir créé des PV liés à ces disques.

$ kubectl get pv
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM              STORAGECLASS   REASON   AGE
local-pv-1f3185eb   60Gi       RWO            Delete           Available                      fast-storage            4h54m
local-pv-50be5869   60Gi       RWO            Delete           Available                      fast-storage            4h54m
local-pv-5ec0ef44   60Gi       RWO            Delete           Available                      slow-storage            4h54m
local-pv-9119c1d6   60Gi       RWO            Delete           Available                      slow-storage            4h54m

Nous pouvons maintenant créer un PVC et un pod pour tester si le stockage est bien monté.

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
  - name: nginx
    image: nginx:latest
    volumeMounts:
    - mountPath: "/usr/share/nginx/html"
      name: nginx-pvc
  volumes:
  - name: nginx-pvc
    persistentVolumeClaim:
      claimName: nginx-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: slow-storage

Malheureusement, le pod échoue avec une erreur FailedMount provenant de kubelet :

$ kubectl describe pod nginx-pod
# ...
Events:
  Type     Reason            Age              From               Message
  ----     ------            ----             ----               -------
  Warning  FailedScheduling  5s               default-scheduler  0/6 nodes are available: persistentvolumeclaim "nginx-pvc" not found. preemption: 0/6 nodes are available: 6 Preemption is not helpful for scheduling..
  Normal   Scheduled         3s               default-scheduler  Successfully assigned default/nginx-pod to worker-4
  Warning  FailedMount       0s (x4 over 3s)  kubelet            MountVolume.NewMounter initialization failed for volume "local-pv-485eb69c" : path "/var/lib/disks/hdd/vde" does not exist

La raison à ça est que le kubelet n’a pas accès aux dossiers créés par Talos (c’est le cas dès lors que Kubelet est lui-même un conteneur), celui-ci doit pourtant pouvoir y accéder pour monter les volumes dans les pods sous forme de PV Local (cette limitation n’existe pas pour les volumes en mode HostPath). Nous devons alors patcher la configuration de Talos pour que le kubelet puisse accéder à ces dossiers.

Voici notre nouveau patch :

machine:
  kubelet:
    extraMounts:
      - destination: /var/lib/disks/
        type: bind
        source: /var/lib/disks/
        options:
          - rbind
          - rshared
          - rw

Plutôt que de créer un extraMounts pour chaque catégorie de disque, nous pouvons en utiliser un seul pour tous les disques.

Volume en block

Comme dit en début d’article, pour créer des volumes en mode bloc, nous devons créer des liens symboliques vers les disques. Talos ne peut pas créer ces liens directement, nous devons alors passer par un pod qui va créer ces liens.

Plutôt que d’en déployer un à la main, nous pouvons utiliser le daemonset de local-static-provisioner pour créer ce lien.

En admettant que les disques vdf et vdg sont des disques en mode bloc sur toutes les machines, nous pouvons lancer ce script Bash :

pods=$(kubectl get pods -n local-pv -l app.kubernetes.io/name=local-static-provisioner -o jsonpath='{.items[*].metadata.name}')
disks=("vdf" "vdg")
for pod in $pods; do
  for disk in "${disks[@]}"; do
    echo "Linking /dev/$disk to /var/lib/disks/block/$disk in pod $pod"
    kubectl exec $pod -- /bin/sh -c "mkdir /var/lib/disks/block"
    kubectl exec $pod -- /bin/sh -c "ln -s /dev/$disk /var/lib/disks/block/$disk"
  done
done

*Bien sûr, cette commande fonctionne uniquement parce que les pods possèdent un HostPath vers /var/lib/disks/block.

Maintenant, nous devrions avoir nos nouveaux PV sur la classe de stockage block-storage.

$ kubectl get pv
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM              STORAGECLASS   REASON   AGE
local-pv-1f3185eb   60Gi       RWO            Delete           Available                      fast-storage            4h54m
local-pv-50be5869   60Gi       RWO            Delete           Available                      fast-storage            4h54m
local-pv-5ec0ef44   60Gi       RWO            Delete           Available                      slow-storage            4h54m
local-pv-9119c1d6   60Gi       RWO            Delete           Available                      slow-storage            4h54m
local-pv-e13d6c91   60Gi       RWO            Delete           Available                      block-storage           13m
local-pv-699a79e5   60Gi       RWO            Delete           Available                      block-storage           13m

Nous pouvons à présent créer un pod et monter le PVC pour tester si le stockage block est bien disponible.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: block-pvc
spec:
  storageClassName: block-storage
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  volumeMode: Block
---
apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-pod
spec:
  containers:
  - name: ubuntu-container
    image: ubuntu:latest
    command: ["/bin/bash", "-c", "while true; do sleep 3600; done"]
    volumeDevices: 
    - name: my-block-storage
      devicePath: /dev/sdo
  volumes:
  - name: my-block-storage
    persistentVolumeClaim:
      claimName: block-pvc

On peut maintenant vérifier si le sdo est bien mappé dans le container :

kubectl exec pods/ubuntu-pod -- ls -lah /dev/sdo
brw------- 1 root root 253, 48 Aug 20 19:35 /dev/sdo