technique:old:alice_bob:merge_pv_copy_lv

Déplacer des données de PV à PV ou de LV à LV

Attention:

Cet article nécessite de comprendre les bases de LVM.

Dans certains cas, il est souhaitable de bouger tout ou partie des données d’un volume logique ou physique dans un autre volume logique ou physique.

Prenons un exemple réel qui nous suivra pour cette page. Sur les machines virtuelles, à l’heure de l’écriture de cet article (06 janvier 2020), la mémoire est organisée comme suit :

$ lsblk
NAME          MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda             8:0    0  100G  0 disk
├─sda1          8:1    0  243M  0 part /boot
└─sda5          8:5    0 99,8G  0 part
  ├─vg00-swap 254:1    0  1,9G  0 lvm  [SWAP]
  ├─vg00-root 254:2    0 57,9G  0 lvm  /
sdb             8:16   0   30G  0 disk
└─vg01-data   254:0    0   20G  0 lvm  /DATA

En d’autres termes :

  • Un PV /dev/sda5 et un PV /dev/sdb, dans des VG différents, et tous deux correspondant au SSD,
  • Sur / est monté un LV basé sur le PV /dev/sda5 (on utilise cet abus de langage, car chaque PV a son VG unique),
  • Sur /DATA est monté un LV basé sur le PV /dev/sdb,
  • Un LV basé sur le PV /dev/sda5 est utilisé pour le SWAP.

L’existence de deux disques distincts (/dev/sda, /dev/sdb), bien qu’appartenant tous deux au stockage local (SSD) de Proxmox, vient de l’existence historique de GlusterFS, qui nécessitait un disque à part. Il n’est pas nécessaire de complexifier les choses : un VG par type de stockage suffit, on voudrait donc réunir les deux disques sur un seul VG.

Aussi, le dossier /DATA/docker est historiquement utilisé pour les bind mounts dans les conteneurs Docker. Picasoft a petit à petit fait la transition vers des volumes Docker, qui occupent le dossier /var/lib/docker/volumes. L’espace occupé dans /DATA/docker diminue tandis que celui de /var/lib/docker augmente. Sachant que /var/lib/docker contient aussi les images et les conteneurs, il serait intéressant de le mettre dans un LV séparé, dont on maîtrise la taille, afin qu’une explosion de taille ne paralyse pas l’intégralité du système et que l’on puisse facilement le redimensionner.

Enfin, le dossier /DATA/BACKUP contient les backup des bases de données. Il n’est pas nécessaire que ces backups soient stockés sur le SSD, comme c’est le cas actuellement, car nous disposons d’une place limitée sur le SSD et la vitesse d’accès n’est pas une prioritaire.

L’état attendu (au niveau LVM) des machines virtuelles (du moins pica01 ou pica02) est donc le suivant :

  • Un PV basé sur le SSD et un PV basé sur le HDD, dans des VG différents,
  • Un LV basé sur le PV SSD monté sur la racine (/),
  • Un LV basé sur le PV SSD utilisé comme SWAP,
  • Un LV basé sur le PV SSD monté sur /DATA/docker,
  • Un LV basé sur le PV SSD monté sur /var/lib/docker,
  • Un LV basé sur le PV HDD monté sur /DATA/BACKUP.

Les transformations à effectuer sont les suivantes :

  • Fusionner les deux PV (/dev/sda5 et /dev/sdb) pour n’en garder qu’un (/dev/sda5), i.e. transférer toutes les données des LV de /dev/sdb sur /dev/sda5,
  • Créer un nouveau LV monté sur /DATA/docker (SSD) et y transférer les données existantes,
  • Créer un nouveau LV monté sur /var/lib/docker (SSD) et y transférer les données existantes,
  • Créer un nouveau LV monté sur /DATA/backup (HDD) et y transférer les données existantes.

On veut “déplacer” /dev/sdb dans /dev/sda, c’est-à-dire déplacer tous les LV de /dev/sdb dans /dev/sda5. La première étape est de vérifier que /dev/sda5 contient assez d’espace disque pour absorber /dev/sdb :

snippet.bash
$ pvs
  PV         VG   Fmt  Attr PSize  PFree 
  /dev/sda5  vg00 lvm2 a--  99,76g 40,00g
  /dev/sdb   vg01 lvm2 a--  30,00g     0

On voit dans notre cas que c’est ok. Dans le cas contraire, on suivra ce tutoriel jusqu'à la partie "Augmentation du Physical Volume".

Plusieurs options s’offrent à nous :

La commande pvmove(8) permet de bouger l’ensemble des PE (physical extents) d’un PV ou d’un LV vers un autre PV. Les PE sont des morceaux continus de données qui segmentent les PV. Cette commande est très précautionneuse et permet le transfert même quand le LV source est actif.

Problème, la commande pvmove requiert que les deux PV appartiennent au même VG, ce qui n’est pas le cas ici : sdb et sda5 appartiennent à deux VG différents. Il faudrait donc fusionner ces VG (e.g avec vgmerge), ce qui nécessite de rendre inactif un des deux VG, ce qui nécessite de démonter tous ses LV : impossibles pour le LV monté sur /, et pareil pour le LV monté sur /DATA, qui a des bind mounts à l’intérieur des conteneurs Docker, il faudrait éteindre tous les conteneurs…

Si les deux PV avaient été sur le même VG, on aurait fait :

$ pvmove <pv>
# Enlève le PV du VG
$ vgreduce <vg commun> <pv>
$ pvremove <pv>

Comme on pourrait le vérifier avec la commande pvdisplay avant et après l’opération pvmove, son stockage n’est plus du tout utilisé.

On pourrait tout simplement utiliser dd pour copier les anciens LV vers les nouveaux LV. Mais comme les anciens LV sont utilisés (en particulier par Docker), la consistance des données peut être altérée pendant la copie, ce qu’on veut éviter.

Voici comment on procède :

On crée un snapshot de l’ancien volume, au sens LVM. Le snapshot utilise peu de place (par un mécanisme de COW, il n’écrit dans le snapshot que lorsque l’original a été modifié) et assure la consistance des données. La taille du snapshot doit permettre de contenir uniquement les changements faits pendant la copie (on la déterminera empiriquement).

snippet.bash
$ lvcreate -L 100m -s /dev/vg01/data -n data_snapshot 
$ lvs
  LV            VG   Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  root          vg00 -wi-ao----  57,90g
  swap          vg00 -wi-ao----   1,86g
  data          vg01 owi-aos---  30,00g
  data_snapshot vg01 swi-a-s--- 100,00m      data   0,21

On constate que data_snapshot a bien pour origine data.

On crée un nouveau LV sur le VG vg00 (dont le seul membre est le PV /dev/sda5), avec une taille basée sur l’ancien et on copie le contenu du snapshot dans le nouveau LV.

snippet.bash
$ lvcreate -n data -L 35G vg00
$ dd if=/dev/vg01/data_snapshot of=/dev/vg00/data bs=4M status=progress
7679+0 enregistrements lus
7679+0 enregistrements écrits
32208060416 bytes (32 GB, 30 GiB) copied, 324,287 s, 99,3 MB/s

À ce stade, le principal est copié. Le souci avec Docker est que des bind mounts de /DATA/docker/[...] sont faits dans les conteneurs. Il ne suffit pas de démonter /DATA pour rendre le LV inactif. On stoppe donc le démon Docker et on démonte /DATA.

snippet.bash
$ systemctl stop docker
$ umount /DATA

On utilise l’outil lvmsync pour copier les éventuelles modifications depuis la création du snapshot :

snippet.bash
$ lvmsync /dev/vg01/data_snapshot /dev/vg00/data
Transferred 1568768 bytes in 0.41 seconds
You transferred your changes 20530.80x faster than a full dd!

On constate en effet que le nombre de données à transférer est très faible (1,5M) et la copie est très rapide ; c’est une copie delta “à la rsync”. On peut profiter que le LV ne soit pas monté pour réduire la taille ainsi que celle du système de fichier si nécessaire. On peut immédiatement monter le nouveau LV et redémarrer le démon Docker. Le downtime est très faible.

snippet.bash
# Si besoin... la taille est déterminée par votre estimation
$ lvresize -L 15g --resizefs /dev/vg00/data
$ mount /dev/mapper/vg00-data /DATA
$ systemctl start docker

On peut ensuite tranquillement supprimer le snapshot ainsi que l’ancien LV, puis le VG et enfin le PV (s’il n’y avait qu’un PV concerné comme dans notre cas). On oublie pas de mettre à jour le fstab pour refléter le changement de chemin du nouveau LV (pas dans le même VG).

snippet.bash
$ lvremove /dev/vg01/data_snapshot
$ lvremove /dev/vg01/data
$ vgremove vg01
$ pvremove /dev/sdb

Note:

Il semble exister des cas où remonter dans /DATA montre un dossier vide, alors que monter dans n’importe quel autre dossier fonctionne. Peut-être du à systemd qui gère les points de montage fstab. Dans ce cas un systemctl daemon-reload préalable semble fonctionner.

Voici l’état des périphériques blocs à la fin de la manipulation :

snippet.bash
$ lsblk
NAME          MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda             8:0    0  100G  0 disk
├─sda1          8:1    0  243M  0 part /boot
└─sda5          8:5    0 99,8G  0 part
  ├─vg00-swap 254:1    0  1,9G  0 lvm  [SWAP]
  ├─vg00-root 254:2    0 57,9G  0 lvm  /
  └─vg00-data 254:6    0   35G  0 lvm  /DATA

On voit bien que le LV monté sur /DATA est sur le PV sda5, on peut alors tranquillement supprimer le deuxième disque depuis l’interface Promox.

On se retrouve maintenant dans la situation où un LV est monté sur /DATA, or on voudrait un LV sur /DATA/docker et un LV sur /DATA/BACKUP, chacun sur un PV différent (SSD et HDD). On voudrait aussi /var/lib/docker dans un LV.

Pour le coup je ne vois pas vraiment de moyens d’éviter l’interruption de service car il ne s’agit pas de copier des LV entier mais de copier une partie du système de fichiers dans un LV et l’autre partie dans un autre.

On aura toujours le risque de copie inconsistante, et pour le coup on ne peut pas vraiment exploiter les snapshots LVM (sauf à faire un snapshot, le monter dans un dossier temporaire, rsync les dossiers /DATA/docker… dans le nouveau LV, éteindre Docker, utiliser lvmsync, puis rsync de nouveau les dossiers pour avoir une copie delta au niveau du système de fichiers - ça peut marcher, mais je n’ai pas essayé).

Étant donné que tout ces dossiers ne sont écrits que par le démon Docker, on va procéder de manière très bourrine :

  • Créer les volumes logiques souhaités
  • Éteindre le démon
  • Copier les fichiers des “anciens” LV aux “nouveaux” LV
  • Supprimer les anciens fichiers
  • Monter correctement les nouveaux LV

L’idée est donc essentiellement de faire quelque chose comme (exemple pour les backups)

snippet.bash
# /dev/sdc est le nom du disque correspondant au HDD
$ pvcreate /dev/sdc
# On crée un PV auquel on intègre le HDD
$ vgcreate vm102-hdd /dev/sdc
# On crée un LV qui prend 50% de l'espace disponible
$ lvcreate -n backup -l 50%FREE backup
# On crée le système de fichiers
$ mkfs.ext4 /dev/mapper/vm102--hdd-backup
# On le monte dans un dossier temporaire
$ mkdir /tmp/backup
$ mount /dev/mapper/vm102--hdd-backup /tmp/backup
# On déplace les backups
$ cd /DATA/BACKUP
$ rsync -a --info=progress2 . /tmp/backup
# On supprime les anciens backups et on démonte le LV
$ rm -r BACKUP
$ umount /tmp/backup

Ensuite, on n’oubliera pas d’ajouter la ligne suivante dans /etc/fstab :

/dev/mapper/vm102--hdd-backup	/DATA/BACKUP	ext4	defaults	0	2

On fait de même pour /DATA/docker et /var/lib/docker, simplement on ne crée pas de nouveau PV/VG, juste des nouveaux LV sur le VG sur lequel le SSD (/dev/sda5) est branché.

  • technique/old/alice_bob/merge_pv_copy_lv.txt
  • de qduchemi