~/textes/cephplayground
Un cluster Ceph que je jette à chaque redémarrage
Je voulais un vrai point d'accès Ceph (S3, CephFS, RBD) pour tester du code applicatif, sans cluster k8s ni un seul octet qui touche mon SSD. Un conteneur, ~30 secondes, tout en RAM. C'est le balayage des versions qui est devenu intéressant.
La plupart du temps, je ne veux pas vraiment d'un cluster Ceph. Je veux le point d'accès. Une URL S3 avec des identifiants, ou un montage CephFS, ou un périphérique RBD, pour pointer du code applicatif vers du vrai RADOS et le regarder faire ce qu'il fera en production. Le cluster, c'est le coût que j'endure pour me rendre là.
La façon normale d'obtenir ça en local, c'est Rook : monter un cluster k8s avec minikube ou kind, installer le chart de l'opérateur, écrire un CR CephCluster, découper des PVC pour les OSD, attendre quelques minutes que ça converge. C'est le bon outil quand on veut vraiment quelque chose en forme de cluster Ceph, avec réplication, injection de pannes et tout le cycle de vie de l'opérateur. Ça fait beaucoup de machinerie quand tout ce qu'on voulait, c'était un point d'accès S3 vers lequel pointer une suite de tests.
Alors j'ai bâti la petite version. Un conteneur Docker, un verbe en ligne de commande, prêt en une trentaine de secondes, chaque octet qu'il écrit vivant en RAM. launch, lance tes tests, destroy. Redémarre la machine et il n'y a aucune trace de son existence, parce que rien n'a jamais été sur le disque pour en laisser une.
Tout dans tmpfs, exprès
La contrainte qui a façonné l'ensemble : aucun état persistant. Pas « nettoyé à la sortie », je veux dire jamais écrit sur le disque en premier lieu. Mon /tmp est en tmpfs, 62,8 Gio, donc c'est là que le cluster vit.
L'OSD est la partie amusante. BlueStore veut un périphérique en mode bloc et je n'en ai pas un de rechange qui traîne, donc le terrain de jeu crée un fichier creux dans tmpfs, l'enveloppe dans un périphérique loop, et donne ça à BlueStore.
# Sparse file in tmpfs becomes the OSD block device via a loopback.
truncate -s 8G /tmp/cephplayground/<name>/osd0.img
losetup --find --show /tmp/cephplayground/<name>/osd0.img
# Every S3 object, CephFS file, and RBD block ends up in RAM
# through that loop device into that file.À l'intérieur du conteneur, les répertoires d'exécution des démons sont eux aussi en tmpfs : /var/lib/ceph (db du mon, db du mgr, journal du MDS, comptabilité de l'OSD), plus /etc/ceph, /run/ceph, /var/log/ceph, /tmp. La seule chose qui touche mon SSD, c'est la couche d'image quay.io/ceph/ceph en lecture seule que Docker a déjà mise en cache. Redémarre, tout est parti, et le disque n'a pas pris une seule écriture pour quoi que ce soit là-dedans.
L'état contre les données
Je me suis surpris à répétition à appeler la comptabilité du mon et du MDS de l'« état » et les objets des « données », comme si seul l'un des deux avait besoin d'être jetable. Même exigence. Si la db du mon survit à un redémarrage mais pas l'OSD, on se retrouve avec un cluster qui se souvient d'une carte d'un stockage qui n'existe plus. Les deux vivent dans tmpfs, ou bien ni l'un ni l'autre.
Pas de cephadm, pas de conteneurs imbriqués
L'autre décision a été de sauter cephadm complètement. C'est comme ça qu'on est censé amorcer un Ceph moderne, mais ça fonctionne en orchestrant un conteneur par démon. Faire ça à l'intérieur d'un conteneur, et voilà qu'on a un environnement d'exécution de conteneurs à l'intérieur d'un conteneur, ce qui est une couche de douleur à laquelle je ne voulais pas m'inscrire.
Alors le point d'entrée démarre simplement les démons directement : un mon, un mgr, un OSD, puis optionnellement un MDS pour CephFS et une passerelle RADOS pour S3, tous des processus ordinaires dans l'unique conteneur. Un drapeau --services (par défaut rgw,cephfs,rbd) choisit lesquels des optionnels montent, et ils coexistent sans problème. Quand CephFS ou RBD est activé, le conteneur bascule en réseau hôte pour qu'un client sur l'hôte puisse joindre directement le mon, le MDS et l'OSD ; le mode S3 seul garde la redirection de port plus simple.
Restait le seul travail que cephadm fait normalement pour toi : créer l'OSD. Ce qui est exactement là où le balayage des versions a déraillé.
Le lancement de Quincy qui se figeait
Je voulais que ça fonctionne sur plus d'une version de Ceph, alors j'ai lancé un balayage : lancer chaque version majeure, la sonder, la détruire. La façon manuelle de monter un OSD, c'est ceph-volume raw prepare pointé sur le périphérique loop. Correct sur les versions plus récentes. Sur la v17, Quincy, le lancement se figeait, point.
Le journal du conteneur n'aidait pas beaucoup au début. Il faisait défiler en boucle une erreur à propos de « no LV », ce qui m'a envoyé patauger dans LVM. Cul-de-sac : le terrain de jeu n'utilise pas LVM du tout, il donne à BlueStore un périphérique loop brut.
Une fois que j'ai arrêté de poursuivre la piste LVM, la vraie cause a fait surface. ceph-volume raw prepare est bogué sur Quincy quand la cible est un périphérique loop. La ligne « no LV » est du bruit de rollback qu'il imprime en déroulant un prepare qui n'aurait jamais dû commencer. Pas un problème LVM, pas un problème de config. L'outil, sur cette version, contre ce genre de périphérique.
[cephplayground] preparing OSD on /dev/cephplay-osd0
[cephplayground] raw prepare with explicit OSD id failed; retrying fresh-cluster prepare
# "no LV found" scrolls here. There is no LV. There was never going
# to be an LV. The message is describing the rollback, not the cause.Le correctif a été d'arrêter d'utiliser ceph-volume pour ça complètement. L'amorçage de l'OSD n'en a pas besoin. On peut enregistrer un nouvel OSD et poser un système de fichiers BlueStore à la main, et ce chemin se comporte pareil de la v16 à la v20.
# Register the OSD and mkfs BlueStore directly onto the loop device.
osd_uuid=$(uuidgen)
osd_id=$(ceph osd new "$osd_uuid")
ceph-osd -i "$osd_id" --mkkey
ceph-osd -i "$osd_id" --mkfs --osd-uuid "$osd_uuid"
# No ceph-volume, no LVM, no per-release surprises.Ce seul changement, laisser tomber ceph-volume raw prepare pour un mkfs BlueStore manuel, c'est ce qui a fait passer tout le balayage.
Un plus petit accroc que j'ai contourné plutôt que corrigé : l'amorçage du realm RGW. Les versions plus anciennes veulent qu'on crée le realm, le zonegroup et la zone soi-même ; la v19 et plus créent automatiquement les valeurs par défaut, donc l'appel explicite se transforme en erreur. Alors c'est conditionné derrière une sonde radosgw-admin zonegroup get : ça lance la configuration manuelle là où elle est nécessaire et ne fait rien là où elle ne l'est pas. Le bouton de version reste un simple --image quay.io/ceph/ceph:v18 plutôt qu'un drapeau dédié, parce qu'un drapeau --ceph-version ne serait que du sucre par-dessus la même chaîne et je passerais mon temps à courir après les renommages de tags en amont.
Le côté client était la partie délicate
Le côté cluster était facile. RGW n'est que du HTTP, donc remettre à quelqu'un un point d'accès S3, c'est imprimer AWS_ENDPOINT_URL et les clés d'accès/secrète d'un utilisateur préfabriqué. CephFS et RBD ne sont pas aussi polis. Un client a besoin d'un trousseau de clés, d'un ceph.conf, puis d'un vrai montage ou mapping, et les montages noyau veulent root et des modules noyau correspondants.
Alors env imprime tout ce dont un client a besoin : les chemins du conf et du trousseau, plus une ligne de montage prête à coller, et tu fais le montage toi-même avec les outils en espace utilisateur plus aimables. Il écrit des trousseaux à portée limitée par service au lieu de distribuer client.admin : client.cephplay-fs peut toucher le système de fichiers, client.cephplay-rbd peut toucher le pool, et ni l'un ni l'autre ne peut administrer le cluster.
# CephFS over FUSE, no kernel module required:
ceph-fuse --id cephplay-fs --conf $CEPHPLAY_CONF /mnt/play
# RBD over NBD, same idea:
sudo rbd-nbd map rbd/play --id cephplay-rbd --conf $CEPHPLAY_CONFLe test de bout en bout est la partie que je ne sauterais pas, parce que « les démons ont monté » n'est pas la même chose que « un client peut s'en servir ». RGW a répondu 200 et un bucket a fait l'aller-retour. CephFS s'est monté par-dessus ceph-fuse avec le trousseau à portée limitée, et un fichier écrit à travers s'est relu. RBD était le plus strict : mapper par-dessus rbd-nbd, mkfs.ext4, monter, écrire un fichier, démapper, remapper, vérifier que le fichier est toujours là. Ce démappage-remappage est ce qui prouve que le bloc a réellement atterri dans le stockage de fond et qu'il n'était pas juste posé dans une cache client.
Les périphériques qui ressemblent à des restes mais n'en sont pas
Après un test RBD, /dev/nbd0 jusqu'à /dev/nbd15 restent là, tous de taille zéro, comme si le terrain de jeu avait échoué à nettoyer. Ce n'est pas le cas. Ces nœuds sont ce que le module noyau nbd crée quand il se charge (nbds_max vaut 16 par défaut), et rbd-nbd map le charge automatiquement. Les emplacements restent inactifs jusqu'au prochain redémarrage ou un modprobe -r nbd. Même histoire que les nœuds /dev/loop* préalloués. Savoir quels restes sont les tiens et lesquels le noyau crée toujours, c'est la moitié du travail pour ne pas courir après des fantômes.
À quoi ça sert
Ce n'est pas un déploiement de production et ça ne prétend pas l'être. Un OSD, pas de réplication, pas de domaines de panne, le cluster entier un point de défaillance unique par conception, le tout en mémoire volatile. C'est le but. C'est un vrai point d'accès RADOS avec les vraies surfaces RGW, MDS et RBD, assez bon marché pour le monter et le démonter à l'intérieur d'un test, et qui ne laisse rien derrière.
Rook reste la bonne réponse quand on veut un cluster qu'on peut casser et regarder se réparer. Quand on veut une URL S3 en trente secondes et son SSD intact, voici l'outil plus petit et plus affûté.
Le code est sur GitHub.