Kubelet conteneurisé : elles sont où mes données ?
Dans mon précédent expresso, j’ai présenté l’usage de local-static-provisioner
. Je voulais également vous parler d’une mésaventure qui m’est arrivée durant mes expérimentations, mais afin de ne pas alourdir le mini-article, j’ai préféré vous en parler dans une page dédiée.
Lorsque j’ai commencé à bricoler avec local-static-provisionner
, je me suis beaucoup inspiré à propos de la documentation Talos à propos de local-path-provisioner
. Sur cette page, Sidero recommande un patch dans la configuration Talos pour que le kubelet puisse accéder aux dossiers utilisés pour créer des PVCs :
machine:
kubelet:
extraMounts:
- destination: /var/mnt
type: bind
source: /var/mnt
options:
- bind
- rshared
- rw
Pour rappel : nous créons ce point de montage entre le kubelet et l’hôte pour qu’il puisse vérifier que les dossiers utilisés existent et soient accessibles pour créer des PVCs. C’est une étape obligatoire lorsqu’on utilise des PV de type local
.
L’option bind
permet de monter un répertoire ou un fichier à un autre emplacement, sans altérer le contenu du système de fichiers. C’est l’option qu’on utilise dans la majorité des cas.
Ne vous souciez pas de rshared
et rw
, ces options sont essentielles, mais ne sont pas intéressantes pour notre étude du jour.
Jusque-là, tout a l’air d’aller. Nous pouvons ajouter notre patch pour que Talos puisse monter notre disque additionnel dans le dossier /var/mnt/sdb
:
machine:
disks:
- device: /dev/sdb
partitions:
- mountpoint: /var/mnt/sdb
J’installe local-static-provisionner
et je crée un petit pod avec ma storageClass slow-storage
(celle liée au PV de mon disque).
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx:latest
securityContext:
privileged: true
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
Logiquement, nous avons bel et bien notre conteneur. Vérifions que notre PVC est bien monté, et créons un fichier à l’intérieur de ce volume :
$ kubectl exec pods/nginx-pod -c nginx -- mount | grep nginx
/dev/mapper/sda6-encrypted on /usr/share/nginx/html type xfs (rw,noatime,attr2,inode64,logbufs=8,logbsize=32k,prjquota)
$ kubectl exec pods/nginx-pod -c nginx -- touch /usr/share/nginx/html/coffee
Supprimons le pod et le PVC et recréons-les avant de vérifier le contenu du volume :
$ kubectl exec pods/nginx-pod -c nginx -- ls /usr/share/nginx/html
coffee
Vous ne rêvez pas : nous retrouvons bien le fichier que nous avons créé même après avoir supprimé ce volume. En y réfléchissant bien, ce n’est pas totalement anormal : comme nous utilisons un PV de type local
il reflète le contenu du filesystem de notre nœud (en l’occurrence, ici, c’est le point de montage du disque sdb
).
Maintenant, si je vous annonce que local-static-provisionner
se charge de nettoyer les fichiers des PVCs supprimés, nous sommes bien faces à un problème. Allons voir le responsable de cette tâche :
$ kubectl get pods -n local-static-provisioner
NAME READY STATUS RESTARTS AGE
local-static-provisioner-r2h4g 1/1 Running 0 58m
$ kubectl describe -n local-static-provisioner pods local-static-provisioner-r2h4g
# [...]
Volumes:
provisioner-config:
Type: ConfigMap (a volume populated by a ConfigMap)
Name: local-pv-local-static-provisioner-config
Optional: false
provisioner-dev:
Type: HostPath (bare host directory volume)
Path: /dev
HostPathType:
slow-storage:
Type: HostPath (bare host directory volume)
Path: /var/mnt/
HostPathType:
# [...]
Il possède un volume de type HostPath
monté sur /var/mnt/
. C’est bien ce que nous avons configuré dans notre fichier Talos. Il devrait alors pouvoir supprimer ce fameux fichier coffee
présent dans le volume, non ?
D’ailleurs, dans les logs de celui-ci, nous pouvons voir qu’il a bien appliqué une procédure de nettoyage :
I1001 19:34:48.940953 1 deleter.go:195] Start cleanup for pv local-pv-b6950bf1
I1001 19:34:48.941379 1 deleter.go:266] Deleting PV file volume "local-pv-b6950bf1" contents at hostpath "/var/mnt/sdb", mountpath "/var/mnt/sdb"
Allons-y étape par étape. Est-ce qu’il détecte bien le disque sdb
?
$ kubectl exec -n local-static-provisioner pods/local-pv-local-static-provisioner-r2h4g -c provisioner -- lsblk
# [...]
sdb 253:16 0 60G 0 disk
`-sdb1 253:17 0 60G 0 part /var/mnt/sdb
Il détecte même le point de montage /var/mnt/sdb
. Cohérent puisque nous avons l’hostPath sur /var/mnt/
(donc nous pouvons logiquement accéder à /var/mnt/sdb
).
Finalement, ce n’était pas si difficile. Notre fichier coffee
n’a plus qu’à disparaître.
$ kubectl exec -n local-static-provisioner pods/local-pv-local-static-provisioner-r2h4g -c provisioner -- rm /var/mnt/sdb/coffee
rm: cannot remove '/var/mnt/sdb/coffee': No such file or directory
command terminated with exit code 1
Oh. Ai-je fait une erreur dans le chemin ?
$ kubectl exec -n local-static-provisioner pods/local-pv-local-static-provisioner-r2h4g -c provisioner -- find /var/mnt/sdb/coffee
/var/mnt
/var/mnt/sdb
Le fichier n’est pas là. Pourtant, nous avons bien vu qu’il était présent dans le volume. Cherchons un peu plus loin en listant les points de montage dans un pod privilégié (autre que celui de local-static-provisioner
car il ne possède pas la commande mount
) :
$ kubectl exec -n local-static-provisioner pods/my-privilegied-pod -- mount | grep local-pv
/dev/mapper/sda6-encrypted on /var/lib/kubelet/pods/98453aa1-2e8c-4275-ba03-460872b7d4ee/volumes/kubernetes.io~local-volume/local-pv-b6950bf1 type xfs (rw,noatime,attr2,inode64,logbufs=8,logbsize=32k,prjquota)
Nous découvrons un point de montage dans /var/lib/kubelet
correspondant à notre PV. C’est la procédure habituelle de kubelet pour mapper un PVC à un pod : il monte le volume dans un dossier temporaire (/var/lib/kubelet/pods/...
) avant de faire hériter le pod de ce point de montage.
Ainsi, lorsque nginx-pod
a été créé, le kubelet a monté le volume dans son dossier temporaire, puis nginx-pod
à récupérer ce point de montage dans le dossier /usr/share/nginx/html
.
En d’autres termes, notre disque sdb
est monté dans /var/mnt/sdb
(nœud talos). Le répertoire /var/mnt
(nœud talos) est monté dans /var/mnt
(kubelet). En créant un PV local avec la source /var/mnt/sdb
, le kubelet monte ce répertoire dans /var/lib/kubelet/pods/...
(ce même dossier est utilisé dans le volume du PVC, et donc du pod).
En image, c’est peut-être plus clair :
Mais en regardant le résultat de la commande mount
, le point de montage dans /var/lib/kubelet/pods/...
est monté sur sda
et non sdb
!
C’est là que le bât blesse, d’autant plus qu’étant donné que sda
est le disque utilisé par Talos, celui-ci peut être formaté à tout moment (dû à la nature de Talos comme OS immuable).
Notre mission initiale était bien de nettoyer ce fichier, mais cela veut dire qu’il n’y a aucune persistance dans les données stockées dans mes PVs (les fichiers ne sont pas sur les disques additionnels, mais sur le disque principal de mon nœud), je voulais juste supprimer un fichier, mais je me retrouve avec un problème plus profond.
Mais alors, pourquoi est-ce que le kubelet monte le volume sur sda
et non sdb
? La réponse est dans une option de montage que nous avons utilisée : bind
.
Je trouve la réponse dans la documentation du kernel Linux :
Bind replicates the specified mount. Rbind replicates all the mounts in the tree belonging to the specified mount.
Laissez-moi reformuler : bind
monte un répertoire ou un fichier à un autre emplacement. rbind
monte un répertoire ou un fichier à un autre emplacement, en répliquant tous les montages de ce répertoire.
Le souci est qu’en créant un montage via bind
du dossier /var/mnt
(nœud talos) vers /var/mnt
(kubelet), il ne verra pas le montage de /dev/sdb
dans /var/mnt/sdb
. Il se contente uniquement de voir le dossier accueillant le montage, et non le montage en lui-même (ce qui explique pourquoi il peut voir le dossier /var/mnt/sdb
, mais pas le fichier /var/mnt/sdb/coffee
).
Une seconde preuve de ce comportement est que Talos peut voir le fichier coffee
dans /var/lib/kubelet/pods/...
mais pas dans /var/mnt/sdb
.
Talos peut ainsi voir le fichier présent dans le répertoire du kubelet mais pas dans le point de montage initial :
$ talosctl ls -n node-disk /var/lib/kubelet/pods/98453aa1-2e8c-4275-ba03-460872b7d4ee/volumes/kubernetes.io~local-volume
NODE NAME
node-disk .
node-disk local-pv-b6950bf1
node-disk local-pv-b6950bf1/coffee
$ talosctl ls -n node-disk /var/mnt/sdb/ -r
NODE NAME
node-disk .
Pour résoudre ce problème, il suffit de modifier le patch Talos pour utiliser rbind
à la place de bind
(et oui, c’est aussi simple que ça) :
machine:
kubelet:
extraMounts:
- destination: /var/mnt
type: bind
source: /var/mnt
options:
- rbind # <--- Ici
- rshared
- rw
Après un redémarrage du nœud, on relance le pod nginx-pod
et le fichier coffee
n’est plus présent (logique, puisque nous utilisons à présent le disque sdb
).
Je vous propose de créer son alter-ego, tea
:
$ kubectl exec pods/nginx-pod -c nginx -- touch /usr/share/nginx/html/tea
Vérifions dans le pod de local-static-provisioner
que le fichier est bien présent dans le disque sdb
:
$ kubectl exec -n local-static-provisioner pods/local-pv-local-static-provisioner-zb27w -c provisioner ls /var/mnt/sdb
tea
C’est une belle avancée ! Qu’en est-il du résultat de la commande mount
?
$ kubectl exec -n local-static-provisioner pods/my-privilegied-pod -- mount | grep local-pv
/dev/sdb1 on /var/lib/kubelet/pods/b61f8420-5652-4d86-bdbb-10869389b242/volumes/kubernetes.io~local-volume/local-pv-b6950bf1 type xfs (rw,noatime,attr2,inode64,logbufs=8,logbsize=32k,prjquota)
Le point de montage est bien sur sdb
, nos données sont à présent persistantes.
Coté Talos, nous pouvons voir le fichier tea
dans le point de montage initial et dans le répertoire du kubelet :
$ talosctl ls -n node-disk /var/mnt/sdb
NODE NAME
node-disk .
node-disk tea
$ talosctl ls -n node-disk /var/lib/kubelet/pods/b61f8420-5652-4d86-bdbb-10869389b242/volumes/kubernetes.io~local-volume/local-pv-b6950bf1
NODE NAME
node-disk .
node-disk tea
Comme ultime et dernière vérification, supprimons le pod nginx-pod
et le PVC nginx-pvc
avant de recréer le pod et vérifier si le fichier tea
est bien absent :
kubectl exec pods/nginx-pod -c nginx -- ls -l /usr/share/nginx/html/
total 0
Victoire 😎 ! Je crois que je n’ai jamais été aussi content de voir un répertoire vide.
Malgré la simplicité de la solution, il m’a fallu un certain temps pour comprendre pourquoi mes données n’étaient pas persistantes et pourquoi le nettoyage des PVs ne fonctionnait pas. C’est une erreur que je ne referai pas de sitôt et totalement évitable si j’avais pris le temps de comprendre de lire la documentation des paramètres utilisés dans mon patch Talos.