Ceph – du « sous-développé » au « production »

Choix de CEPH. Partie 1

Nous avions cinq racks, dix commutateurs optiques, un BGP configuré, une paire de dizaines de SSD et une multitude de disques SAS de toutes les couleurs et tailles, ainsi que proxmox et le désir de mettre toute la statique dans notre propre stockage S3. Ce n'est pas que tout cela était nécessaire pour la virtualisation, mais une fois que j'ai commencé à utiliser l'open source, il fallait aller jusqu'au bout de ma passion. La seule chose qui me préoccupait était le BGP. Il n'y a personne de plus impuissant, irresponsable et immoral dans le monde que la routage interne via BGP. Et je savais que nous allions bientôt plonger dedans.

Ceph – du « sous-développé » au « production »

La tâche était banale : nous avions un CEPH qui ne fonctionnait pas très bien. Il fallait le rendre « bon ».
Le cluster qui m'a été attribué était hétérogène, configuré à la va-vite et pratiquement non optimisé. Il se composait de deux groupes de nœuds différents, avec un réseau commun servant à la fois de réseau de cluster et de réseau public. Les nœuds étaient équipés de quatre types de disques : deux types de SSD, regroupés en deux règles de placement distinctes, et deux types de HDD de tailles différentes, regroupés dans un troisième groupe. Le problème des tailles différentes a été résolu par des poids OSD variés.

La configuration a été divisée en deux parties : l'optimisation du système d'exploitation et l'optimisation de CEPH lui-même et de ses paramètres.

Amélioration de l'OS

Réseau

Une latence élevée se faisait sentir tant lors de l'écriture que lors de l'équilibrage. Lors de l'écriture, parce que le client ne recevra pas de réponse indiquant une écriture réussie tant que les répliques des données dans d'autres groupes de placement n'auront pas confirmé le succès. Étant donné que les règles de répartition des répliques dans la carte CRUSH étaient de conserver une réplique par hôte, le réseau était toujours utilisé.

C'est pourquoi j'ai d'abord décidé d'ajuster un peu le réseau actuel, tout en essayant de convaincre de passer à des réseaux séparés.

Pour commencer, j'ai ajusté les paramètres des cartes réseau. J'ai commencé par configurer les files d'attente :

ce qui était :

ethtool -l ens1f1

root@ceph01:~# ethtool -l ens1f1
Paramètres des canaux pour ens1f1 :
Max préétabli :
RX : 0
TX : 0
Autre : 1
Combinaison : 63
Paramètres matériels actuels :
RX : 0
TX : 0
Autre : 1
Combinaison : 1
root@ceph01:~# ethtool -g ens1f1
Paramètres des anneaux pour ens1f1 :
Max préétabli :
RX : 4096
RX Mini : 0
RX Jumbo : 0
TX : 4096
Paramètres matériels actuels :
RX : 256
RX Mini : 0
RX Jumbo : 0
TX : 256
root@ceph01:~# ethtool -l ens1f1
Paramètres des canaux pour ens1f1 :
Max préétabli :
RX : 0
TX : 0
Autre : 1
Combinaison : 63
Paramètres matériels actuels :
RX : 0
TX : 0
Autre : 1
Combinaison : 1

Il est clair que les paramètres actuels sont loin des maximums. J'ai augmenté :

root@ceph01:~#ethtool -G ens1f0 rx 4096
root@ceph01:~#ethtool -G ens1f0 tx 4096
root@ceph01:~#ethtool -L ens1f0 combined 63

En me basant sur un excellent article

https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data/

j'ai augmenté la longueur de la file d'attente d'envoi txqueuelen de 1000 à 10 000

root@ceph01:~#ip link set ens1f0  txqueuelen 10000

Et en suivant la documentation de ceph lui-même

https://ceph.com/geen-categorie/ceph-loves-jumbo-frames/

j'ai augmenté MTU à 9000.

root@ceph01:~#ip link set dev ens1f0  mtu 9000

J'ai ajouté dans /etc/network/interfaces pour que tout ce qui précède se charge au démarrage

cat /etc/network/interfaces

root@ceph01:~# cat /etc/network/interfaces
auto lo
iface lo inet loopback

auto ens1f0
iface ens1f0 inet manual
post-up /sbin/ethtool -G ens1f0 rx 4096
post-up /sbin/ethtool -G ens1f0 tx 4096
post-up /sbin/ethtool -L ens1f0 combined 63
post-up /sbin/ip link set ens1f0  txqueuelen 10000
mtu 9000

auto ens1f1
iface ens1f1 inet manual
post-up /sbin/ethtool -G ens1f1 rx 4096
post-up /sbin/ethtool -G ens1f1 tx 4096
post-up /sbin/ethtool -L ens1f1 combined 63
post-up /sbin/ip link set ens1f1  txqueuelen 10000
mtu 9000

Après cela, en suivant le même article, j'ai commencé à ajuster avec soin les paramètres du noyau 4.15. Étant donné que les nœuds disposent de 128 Go de RAM, j'ai obtenu un certain fichier de configuration pour sysctl

cat /etc/sysctl.d/50-ceph.conf

net.core.rmem_max = 56623104  
# Taille maximale du tampon de réception des données pour toutes les connexions  54M

net.core.wmem_max = 56623104
# Taille maximale du tampon d'envoi des données pour toutes les connexions 54M

net.core.rmem_default = 56623104
# Taille par défaut du tampon de réception des données pour toutes les connexions. 54M

net.core.wmem_default = 56623104
# Taille par défaut du tampon d'envoi des données pour toutes les connexions 54M  
# pour chaque socket

net.ipv4.tcp_rmem = 4096 87380 56623104
# Variable vectorielle (minimum, par défaut, maximum) dans le fichier tcp_rmem
# contenant 3 entiers définissant la taille du tampon de réception des sockets TCP.
# Minimum : chaque socket TCP a le droit d'utiliser cette mémoire dès
# sa création. L'utilisation de ce tampon est garantie même en cas de pression
# mémoire modérée.
# La taille minimale par défaut du tampon est de 8 Ko (8192).
#Valeur par défaut : quantité de mémoire autorisée pour le tampon
# d'envoi du socket TCP par défaut. Cette valeur s'applique à la place
# du paramètre /proc/sys/net/core/rmem_default utilisé par d'autres protocoles.
# La valeur par défaut utilisée pour le tampon est généralement
# de 87830 octets. Cela détermine la taille de la fenêtre de 65535 avec
# la valeur par défaut de tcp_adv_win_scale et tcp_app_win = 0,
# légèrement inférieure à la valeur par défaut définie par tcp_app_win.
# Maximum : taille maximale du tampon pouvant être automatiquement
# allouée pour la réception du socket TCP. Cette valeur ne remet pas en
# question le maximum spécifié dans le fichier /proc/sys/net/core/rmem_max.
# Lors de l'allocation « statique » de mémoire avec SO_RCVBUF, ce paramètre
# n'a pas d'importance.

net.ipv4.tcp_wmem = 4096 65536 56623104
net.core.somaxconn = 5000    
# Nombre maximal de sockets ouverts en attente de connexions.

net.ipv4.tcp_timestamps=1
# Permet l'utilisation de timestamps, conformément à la RFC 1323.

net.ipv4.tcp_sack=1
# Autoriser les acquittements sélectifs du protocole TCP

net.core.netdev_max_backlog=5000 (par défaut 1000)
# nombre maximal de paquets en attente de traitement, si
# l'interface reçoit des paquets plus rapidement que le noyau ne peut les traiter.

net.ipv4.tcp_max_tw_buckets=262144
# Nombre maximum de sockets dans l'état TIME-WAIT à la fois.
# Si ce seuil est dépassé, le socket « supplémentaire » est détruit et un
# message est écrit dans le journal système.

net.ipv4.tcp_tw_reuse=1
# Permet de réutiliser les sockets TIME-WAIT dans les cas
# où le protocole le juge sûr.

net.core.optmem_max=4194304
# Augmenter la taille maximale du tampon de mémoire ALLOCATABLE
# mesurée en unités de pages (4096 octets)

net.ipv4.tcp_low_latency=1
# Permet à la pile TCP/IP de privilégier un faible temps de latence
# par rapport à une plus grande bande passante.

net.ipv4.tcp_adv_win_scale=1
# Cette variable affecte le calcul de la quantité de mémoire dans le tampon
# de socket allouée pour la taille de la fenêtre TCP et le tampon de l'application.
# Si la valeur de tcp_adv_win_scale est négative, la taille est calculée par
# l'expression suivante :
# Bytes - bytes2 à la puissance -tcp_adv_win_scale
# Où bytes est la taille de la fenêtre en octets. Si la valeur de tcp_adv_win_scale
# est positive, la taille est déterminée par l'expression suivante :
# Bytes - bytes2 à la puissance tcp_adv_win_scale
# La variable accepte une valeur entière. La valeur par défaut est de 2,
# donc ¼ du volume déterminé par la variable tcp_rmem est alloué au tampon de l'application.

net.ipv4.tcp_slow_start_after_idle=0
# Mécanisme de redémarrage du démarrage lent qui réinitialise la valeur de la fenêtre
# de surcharge si la connexion n'a pas été utilisée pendant un certain temps.
# Il est préférable de désactiver SSR sur le serveur pour améliorer la performance
# des connexions à long terme.

net.ipv4.tcp_no_metrics_save=1
# Ne pas enregistrer les résultats des mesures TCP dans le cache lors de la clôture de la connexion.

net.ipv4.tcp_syncookies=0
# Désactiver le mécanisme d'envoi de syncookies

net.ipv4.tcp_ecn=0
# Notification explicite de congestion (Explicit Congestion Notification) dans
# les connexions TCP. Utilisé pour avertir de la création d'un « embouteillage »
# sur le chemin vers un hôte ou un réseau donné. Peut être utilisé pour informer
# l'hôte expéditeur de réduire sa vitesse de transmission de paquets à travers
# un routeur ou un pare-feu spécifique.

net.ipv4.conf.all.send_redirects=0
# désactive l'envoi de redirections ICMP … aux autres hôtes. Cette option doit absolument
# être activée si l'hôte agit en tant que routeur de quelque nature que ce soit.
# Nous n'avons pas de routage.

net.ipv4.ip_forward=0
# Désactivation du forwarding. Nous ne sommes pas une passerelle, Docker sur les machines n'est pas actif,
# nous n’en avons pas besoin.

net.ipv4.icmp_echo_ignore_broadcasts=1
# Ne pas répondre aux requêtes ICMP ECHO envoyées par des paquets de diffusion

net.ipv4.tcp_fin_timeout=10
# définit le temps de maintien du socket dans l'état FIN-WAIT-2 après sa
# fermeture par le côté local. Par défaut 60

net.core.netdev_budget=600 # (par défaut 300)
# Si l'exécution des interruptions logicielles ne dure pas assez longtemps,
# le taux de croissance des données entrantes peut dépasser la capacité du noyau
# à vider le tampon. En conséquence, les tampons NIC seront saturés, et le trafic sera perdu.
# Parfois, il est nécessaire d'augmenter la durée d'exécution des SoftIRQs
# (interruptions logicielles) sur le CPU. Cela est géré par netdev_budget.
# La valeur par défaut est de 300. Ce paramètre obligera le processus SoftIRQ à traiter
# 300 paquets du NIC avant de libérer le CPU

net.ipv4.tcp_fastopen=3
# TFO TCP Fast Open
# si le client et le serveur prennent en charge TFO, signalé par un
# drapeau spécial dans le paquet TCP. Dans notre cas, c'est un placebo, cela
# a juste l'air bien)

Avecréseau de luster a été allouée sur des interfaces réseau distinctes de 10 Gbps dans un réseau plat séparé. Chaque machine était équipée de cartes réseau à deux ports mellanox 10/25 Gbps, branchées sur deux commutateurs distincts de 10 Gbps. L'agrégation était réalisée via OSPF, car le lien avec lacp a montré une bande passante totale maximale de 16 Gbps, alors qu'ospf utilisait complètement les deux dizaines sur chaque machine. Les plans futurs étaient d'utiliser ROCE sur ces mellanox pour réduire la latence. Comment cette partie du réseau était configurée :

  1. Puisque les machines ont des adresses IP externes sur BGP, nous avons besoin du logiciel — ( à ce moment-là, c'était frr=6.0-1 ) qui était déjà installé.
  2. Au total, il y avait deux interfaces réseau sur les machines — soit 4 ports au total. Une carte réseau à deux ports se connectait à la fabrique et avait BGP configuré, l'autre — à deux ports se connectait à deux commutateurs différents et avait OSPF configuré.

Plus de détails sur la configuration d'OSPF : Le principal objectif est d'agréger deux liens et d'avoir une tolérance aux pannes.
les deux interfaces réseau sont configurées dans deux réseaux plats simples — 10.10.10.0/24 et 10.10.20.0/24

1: ens1f0:  mtu 9000 qdisc mq state UP group default qlen 1000
    inet 10.10.10.2/24 brd 10.10.10.255 scope global ens1f0

2: ens1f1:  mtu 9000 qdisc mq state UP group default qlen 1000
    inet 10.10.20.2/24 brd 10.10.20.255 scope global ens1f1

par lesquels les machines se voient mutuellement.

DISQUE

L'étape suivante a été d'optimiser le fonctionnement des disques. Pour les SSD, j'ai changé le planificateur en noop, pour les HDD — deadline. En gros, NOOP fonctionne sur le principe de « le premier arrivé, premier servi », ce qui en anglais se dit « FIFO (First In, First Out) ». Les requêtes se placent dans une file d'attente à mesure de leur arrivée. DEADLINE est davantage optimisé pour la lecture, de plus le processus de la file obtient pratiquement un accès monopolistique au disque au moment de l'opération. Cela convient parfaitement à notre système — car un seul processus — OSD daemon — traite avec chaque disque.
(Ceux qui souhaitent s'immerger dans le planificateur d'E/S peuvent lire à ce sujet ici :
http://www.admin-magazine.com/HPC/Articles/Linux-I-O-Schedulers

Préférant lire en russe : https://www.opennet.ru/base/sys/linux_shedulers.txt.html)

Dans les recommandations pour le tuning de Linux, il est également conseillé d'augmenter nr_request

nr_requests
La valeur de nr_requests détermine le nombre de requêtes I/O qui sont mises en tampon avant que le planificateur I/O n'envoie/reçoive des données vers/le périphérique de stockage. Si vous utilisez une carte RAID ou un périphérique de stockage capable de gérer une file d'attente plus grande que celle définie pour le planificateur I/O, augmenter la valeur de nr_requests peut aider à améliorer le débit et réduire la charge du serveur lorsqu'un grand nombre d'I/O se produit. Si vous utilisez Deadline ou CFQ comme planificateur, il est suggéré de définir la valeur de nr_requests sur 2 fois la valeur de profondeur de la file d'attente.

MAIS ! Les développeurs citoyens de CEPH nous convainquent que leur système de priorisation fonctionne mieux.

Ceph – du « sous-développé » au « production »

WBThrottle et/ou nr_requests

WBThrottle et/ou nr_requests
Le stockage de fichiers utilise des opérations d'entrée/sortie mises en tampon pour l'écriture ; cela apporte tout un ensemble d'avantages si le journal de stockage de fichiers se trouve sur un support plus rapide. Les requêtes des clients reçoivent des notifications dès que les données sont écrites dans le journal, puis sont transférées sur le disque de données principal à un moment ultérieur, en utilisant la fonctionnalité standard de Linux. Cela permet aux disques OSD de fournir une latence d'écriture similaire à celle des SSD lors d'écritures en petits paquets. Cette écriture différée permet également au noyau de réorganiser les requêtes d'opérations d'entrée/sortie vers le disque, espérant soit les fusionner, soit permettre aux têtes de disque existantes de choisir un chemin plus optimisé sur leurs plateaux. L'effet final est que vous pouvez obtenir légèrement plus d'opérations d'entrée/sortie de chaque disque que ce qui serait possible avec des opérations d'entrée/sortie directes ou synchrones.

Cependant, un problème se pose si le volume des écritures arrivant dans ce cluster Ceph dépasse toutes les capacités des disques sous-jacents. Dans ce scénario, le nombre total d'opérations d'entrée/sortie en attente d'écriture sur le disque peut croître de manière incontrôlée, résultant en des files d'attente d'opérations d'entrée/sortie, remplissant tout le disque et les files d'attente de Ceph. Les requêtes de lecture sont particulièrement affectées car elles se retrouvent bloquées entre les requêtes d'écriture qui peuvent prendre plusieurs secondes pour être transférées sur le disque principal.

Pour vaincre ce problème, Ceph dispose d'un mécanisme de limitation d'écriture en cache intégré, nommé WBThrottle. Il est conçu pour restreindre le volume total des opérations d'entrée/sortie en attente qui peuvent être mises en file d'attente et commencer leur processus de vidage plus tôt qu'elles ne le feraient naturellement grâce à l'activation par le noyau lui-même. Malheureusement, les tests montrent que les valeurs par défaut peuvent encore ne pas réduire le comportement existant à un niveau capable d'atténuer cet impact sur la latence des opérations de lecture. Un réglage peut modifier ce comportement et réduire la longueur totale des files d'attente d'écriture, rendant possible un impact moins prononcé. Cependant, il existe un compromis : en réduisant le nombre maximal total de requêtes autorisées en attente, vous pourriez diminuer la capacité du noyau à maximiser son efficacité de queue pour les requêtes entrantes. Il vaut la peine de réfléchir à ce qui est le plus nécessaire pour votre cas d'utilisation spécifique et vos charges de travail et d'ajuster en conséquence.

Pour gérer la profondeur de cette file d'attente d'écriture en attente, vous pouvez soit réduire le nombre maximal total d'opérations d'entrée/sortie non exécutées à l'aide des paramètres de WBThrottle, soit réduire la valeur maximale pour les opérations non exécutées au niveau du bloc de votre noyau. Les deux peuvent efficacement gérer le même comportement et vos préférences seront à la base de la mise en œuvre de ce réglage.
Il convient également de noter que le système de priorités des opérations de Ceph est plus efficace pour les demandes plus courtes au niveau du disque. En réduisant la file d'attente totale pour ce disque, l'emplacement principal dans la file d'attente se déplace vers Ceph, où il a un meilleur contrôle sur la priorité des opérations d'entrée/sortie. Considérons l'exemple suivant :

echo 8 > /sys/block/sda/queue/nr_requests

http://onreader.mdl.ru/MasteringCeph/content/Ch09.html#030202

COMMUN

Et quelques autres réglages du noyau qui permettent de rendre votre machine douce et soyeuse tout en tirant encore un peu plus de performance de votre matériel.

cat /etc/sysctl.d/60-ceph2.conf

 kernel.pid_max = 4194303
# Il y a 25 disques dans chaque machine, donc nous avons estimé qu'il y aurait beaucoup de processus
kernel.threads-max=2097152
# Les threads, bien sûr, également.
vm.max_map_count=524288
# Nous avons augmenté le nombre de régions de la carte mémoire du processus.
# Comme l'indique la documentation sur les variables du noyau
# Les régions de la carte mémoire sont utilisées comme effet secondaire de l'appel
# malloc, directement par mmap, mprotect et madvise, ainsi que lors du chargement
# de bibliothèques partagées.
fs.aio-max-nr=50000000
# Nous optimisons les paramètres d'entrée-sortie
# Le noyau Linux fournit une fonction d'entrée-sortie asynchrone non bloquante (AIO),
# qui permet au processus d'initier plusieurs opérations d'entrée-sortie
# simultanément, sans attendre la fin de l'une d'elles.
# Cela aide à améliorer les performances des applications, 
# qui peuvent chevaucher le traitement et l'entrée-sortie.
# Le paramètre aio-max-nr définit le nombre maximum de
# requêtes simultanées autorisées.
vm.min_free_kbytes=1048576
# taille minimale de mémoire libre à maintenir.
# Réglé à 1 Go, ce qui est largement suffisant pour le fonctionnement du système d'exploitation,
# et permet d'éviter OOM Killer pour les processus OSD. Bien qu'il y ait déjà
# suffisamment de mémoire,
vm.swappiness=10
# Nous disons d'utiliser le swap si 10 % de la mémoire est libre.
# Sur des machines avec 128 Go de RAM, 10 % équivaut à 12 Go. Plus que suffisant pour le fonctionnement.
# Le paramètre par défaut de 60 % faisait ralentir le système, allant dans le swap,
# alors qu'il restait encore beaucoup de mémoire libre
vm.vfs_cache_pressure=1000
# Augmentons le paramètre par défaut de 100. Nous obligeons le noyau à décharger
# plus activement les pages de mémoire non utilisées du cache.
vm.zone_reclaim_mode=0
# Permet d'adopter des approches plus ou moins agressives pour
# la récupération de mémoire lorsque la mémoire dans la zone est épuisée.
# S'il est réglé à zéro, il n'y a pas de récupération de zone.
# Pour les serveurs de fichiers ou les charges de travail
# il est avantageux que leurs données soient mises en cache, zone_reclaim_mode
# doit rester désactivé, car l'effet de mise en cache,
# sera probablement plus important que l'emplacement des données.
vm.dirty_ratio=20
# Pourcentage de mémoire vive alloué aux pages "sales"
# Calculé à partir d'une estimation : 
# Le système a 128 Go de mémoire.
# Environ 20 disques SSD, pour lesquels les paramètres CEPH indiquent
# allouer 3 Go de RAM pour le caching.
# Environ 40 disques HDD, pour lesquels ce paramètre est égal à 1 Go
# 20 % de 128, c'est 25,6 Go. Ainsi, en cas de maximum d'utilisation de la mémoire,
# il restera 2,4 Go de mémoire pour le système. Ce qui devrait lui suffire pour survivre et attendre
# le bruit des sabots de la cavalerie - c'est-à-dire l'arrivée de DevOps qui va tout réparer.
vm.dirty_background_ratio=3
# Pourcentage de la mémoire système pouvant être rempli par des pages sales avant que
# les processus en arrière-plan pdflush/flush/kdmflush ne les écrivent sur le disque.
fs.file-max=524288
# Eh bien, le nombre de fichiers ouverts sera probablement bien plus élevé que celui
# indiqué par défaut. 

Plongée dans CEPH

Paramètres sur lesquels nous souhaitons nous attarder davantage :

cat /etc/ceph/ceph.conf

osd:
    journal_aio: true               # Trois paramètres, y compris
    journal_block_align: true       # E / O direct
    journal_dio: true               # sur le journal
    journal_max_write_bytes: 1073714824 # Élargissons un peu la taille maximale
                                        # de l'opération d'écriture unique dans le journal
    journal_max_write_entries: 10000    # Et le nombre d'enregistrements simultanés
    journal_queue_max_bytes: 10485760000 
    journal_queue_max_ops: 50000
    rocksdb_separate_wal_dir: true      # Nous avons décidé de créer un wal séparé
                                        # Nous avons même essayé d'obtenir un espace
                                        # NVMe
    bluestore_block_db_create: true     # Et un appareil séparé pour le journal
    bluestore_block_db_size: '5368709120 #5G'
    bluestore_block_wal_create: true
    bluestore_block_wal_size: '1073741824   #1G' 
    bluestore_cache_size_hdd: '3221225472   # 3G' 
                                            # un grand volume de mémoire permet de
                                            # stocker des volumes considérables
    bluestore_cache_size_ssd: '9663676416   # 9G' 

    keyring: /var/lib/ceph/osd/ceph-$id/keyring
    osd_client_message_size_cap: '1073741824 #1G'
    osd_disk_thread_ioprio_class: idle
    osd_disk_thread_ioprio_priority: 7
    osd_disk_threads: 2 # nombre de threads du démon par disque
    osd_failsafe_full_ratio: 0.95
    osd_heartbeat_grace: 5
    osd_heartbeat_interval: 3
    osd_map_dedup: true
    osd_max_backfills: 2 # nombre d'opérations de remplissage simultanées par OSD.
    osd_max_write_size: 256
    osd_mon_heartbeat_interval: 5
    osd_op_threads: 16
    osd_op_num_threads_per_shard: 1
    osd_op_num_threads_per_shard_hdd: 2
    osd_op_num_threads_per_shard_ssd: 2
    osd_pool_default_min_size: 1     # Caractéristiques de la gourmandise. L'espace est devenu
    osd_pool_default_size: 2         # rapidement insuffisant, car une solution
                                     # temporaire a été prise de réduire le
                                     # nombre de répliques de données
    osd_recovery_delay_start: 10.000000
    osd_recovery_max_active: 2
    osd_recovery_max_chunk: 1048576
    osd_recovery_max_single_start: 3
    osd_recovery_op_priority: 1
    osd_recovery_priority: 1            # paramètre ajustable au besoin en cours d'exécution
    osd_recovery_sleep: 2
    osd_scrub_chunk_max: 4

Certaines des paramètres testés en QA sur la version 12.2.12 sont absents de la version ceph 12.2.2, par exemple osd_recovery_threads. C'est pourquoi la mise à jour vers 12.2.12 a été incluse dans les projets en production. La pratique a montré la compatibilité dans un cluster des versions 12.2.2 et 12.2.12, ce qui permet de réaliser une mise à jour progressive.

Cluster de test

Bien sûr, pour les tests, il était nécessaire d’avoir la même version que celle de la production, mais au début de mon travail avec le cluster, seul un version plus récente était disponible dans le référentiel. En constatant que les différences dans la version mineure n'étaient pas très significatives,1393 les lignes dans les configurations par rapport à 1436 (la nouvelle version), nous avons décidé de commencer à tester la nouvelle (de toute façon, il fallait mettre à jour, pourquoi continuer avec de l’ancien matériel)

La seule chose que nous avons essayé de garder de l’ancienne version, c’est le paquet ceph-deploy, car certaines utilitaires (et certains employés) étaient adaptés à sa syntaxe. La nouvelle version différait considérablement, mais cela n'affectait en rien le fonctionnement du cluster, et elle a été laissée à la version 1.5.39

Étant donné que l’équipe ceph-disk indique clairement qu’elle est obsolète et qu’il faut utiliser, chers utilisateurs, la commande ceph-volume — nous avons commencé à créer des OSD précisément avec cette commande, sans perdre de temps sur une version dépassée.

Le plan était le suivant : créer un miroir à partir de deux disques SSD, sur lesquels nous placerions les journaux OSD, qui, à leur tour, se trouvent sur des SAS à plateau. Cela nous permettrait de prévenir des problèmes de données en cas de défaillance d'un disque de journal.

La création du cluster a donc eu lieu selon la documentation.

cat /etc/ceph/ceph.conf

root@ceph01-qa:~# cat /etc/ceph/ceph.conf # configuration préparée à l'avance
[client]
rbd_cache = true
rbd_cache_max_dirty = 50331648
rbd_cache_max_dirty_age = 2
rbd_cache_size = 67108864
rbd_cache_target_dirty = 33554432
rbd_cache_writethrough_until_flush = true
rbd_concurrent_management_ops = 10
rbd_default_format = 2
[global]
auth_client_required = cephx
auth_cluster_required = cephx
auth_service_required = cephx
cluster network = 10.10.10.0/24
debug_asok = 0/0
debug_auth = 0/0
debug_buffer = 0/0
debug_client = 0/0
debug_context = 0/0
debug_crush = 0/0
debug_filer = 0/0
debug_filestore = 0/0
debug_finisher = 0/0
debug_heartbeatmap = 0/0
debug_journal = 0/0
debug_journaler = 0/0
debug_lockdep = 0/0
debug_mon = 0/0
debug_monc = 0/0
debug_ms = 0/0
debug_objclass = 0/0
debug_objectcatcher = 0/0
debug_objecter = 0/0
debug_optracker = 0/0
debug_osd = 0/0
debug_paxos = 0/0
debug_perfcounter = 0/0
debug_rados = 0/0
debug_rbd = 0/0
debug_rgw = 0/0
debug_throttle = 0/0
debug_timer = 0/0
debug_tp = 0/0
fsid = d0000000d-4000-4b00-b00b-0123qwe123qwf9
mon_host = ceph01-q, ceph02-q, ceph03-q
mon_initial_members = ceph01-q, ceph02-q, ceph03-q
public network = 8.8.8.8/28 # l'adresse a été modifiée, bien sûr ))
rgw_dns_name = s3-qa.mycompany.ru # et cette adresse a été modifiée
rgw_host = s3-qa.mycompany.ru # et celle-ci aussi
[mon]
mon allow pool delete = true
mon_max_pg_per_osd = 300 # plus de trois cents groupes de placement
                          # sur le disque, nous n'avons pas osé
                     # bien que le paramètre dépende naturellement du nombre de pools,
                     # de leurs tailles et du nombre d'OSD. Avoir peu mais sain de PG
                        # n'est pas non plus le meilleur choix - cela affecte la précision de l'équilibrage
mon_osd_backfillfull_ratio = 0.9
mon_osd_down_out_interval = 5
mon_osd_full_ratio = 0.95 # pour l'instant, pour les disques SSD, l'espace
                          # pour leur journal est le même dispositif que pour l'OSD
                          # nous avons décidé que 5% de l'espace disque (qui mesure 1.2 To)
                          # devrait être largement suffisant, et corrélé avec le paramètre
                          # bluestore_block_db_size plus la variabilité pour de grands groupes de placement
mon_osd_nearfull_ratio = 0.9
mon_pg_warn_max_per_osd = 520
[osd]
bluestore_block_db_create = true
bluestore_block_db_size = 5368709120 #5G
bluestore_block_wal_create = true
bluestore_block_wal_size = 1073741824 #1G
bluestore_cache_size_hdd = 3221225472 # 3G
bluestore_cache_size_ssd = 9663676416 # 9G
journal_aio = true
journal_block_align = true
journal_dio = true
journal_max_write_bytes = 1073714824
journal_max_write_entries = 10000
journal_queue_max_bytes = 10485760000
journal_queue_max_ops = 50000
keyring = /var/lib/ceph/osd/ceph-$id/keyring
osd_client_message_size_cap = 1073741824 #1G
osd_disk_thread_ioprio_class = idle
osd_disk_thread_ioprio_priority = 7
osd_disk_threads = 2
osd_failsafe_full_ratio = 0.95
osd_heartbeat_grace = 5
osd_heartbeat_interval = 3
osd_map_dedup = true
osd_max_backfills = 4
osd_max_write_size = 256
osd_mon_heartbeat_interval = 5
osd_op_num_threads_per_shard = 1
osd_op_num_threads_per_shard_hdd = 2
osd_op_num_threads_per_shard_ssd = 2
osd_op_threads = 16
osd_pool_default_min_size = 1
osd_pool_default_size = 2
osd_recovery_delay_start = 10.0
osd_recovery_max_active = 1
osd_recovery_max_chunk = 1048576
osd_recovery_max_single_start = 3
osd_recovery_op_priority = 1
osd_recovery_priority = 1
osd_recovery_sleep = 2
osd_scrub_chunk_max = 4
osd_scrub_chunk_min = 2
osd_scrub_sleep = 0.1
rocksdb_separate_wal_dir = true

# создаем мониторы
root@ceph01-qa:~#ceph-deploy mon create ceph01-q
# генерируем ключи для аутентификации нод в кластере
root@ceph01-qa:~#ceph-deploy gatherkeys ceph01-q
# Это если поштучно. Если у нас несколько машин доступны - те, которые описаны в конфиге в секции 
# mon_initial_members = ceph01-q, ceph02-q, ceph03-q
# можно запустить эти две команды в виде одной
root@ceph01-qa:~#ceph-deploy mon create-initial
# Положим ключи в указанные в конфиге места
root@ceph01-qa:~#cat ceph.bootstrap-osd.keyring > /var/lib/ceph/bootstrap-osd/ceph.keyring 
root@ceph01-qa:~#cat ceph.bootstrap-mgr.keyring > /var/lib/ceph/bootstrap-mgr/ceph.keyring 
root@ceph01-qa:~#cat ceph.bootstrap-rgw.keyring > /var/lib/ceph/bootstrap-rgw/ceph.keyring
# создадим ключ для управления кластером
root@ceph01-qa:~#ceph-deploy admin ceph01-q
# и менеджер, плагинами управлять
root@ceph01-qa:~#ceph-deploy mgr create ceph01-q

Tout d'abord, ce qui m'a posé problème dans cette version de ceph-deploy avec le cluster version 12.2.12 — c'est une erreur lors de la tentative de création d'un OSD avec db sur un raid logiciel —

root@ceph01-qa:~#ceph-volume lvm create --bluestore --data /dev/sde --block.db /dev/md0
blkid n'a pas pu détecter un PARTUUID pour le périphérique : /dev/md1

En effet, blkid ne montre pas de PARTUUID, j'ai dû créer des partitions manuellement :

root@ceph01-qa:~#parted /dev/md0 mklabel GPT 
# il y aura beaucoup de partitions, 
# sans GPT, il n'est pas possible de les créer
# la taille de la partition est indiquée dans la config ci-dessus = bluestore_block_db_size: '5368709120 #5G'
# J'ai 20 disques pour les OSD, je n'ai pas envie de créer des partitions manuellement
# donc j'ai fait une boucle
root@ceph01-qa:~#for i in {1..20}; do echo -e "nnnn+5Gnw" | fdisk /dev/md0; done

Tout semble prêt, essayons de créer l'OSD encore une fois et nous obtenons l'erreur suivante (qui, soit dit en passant, ne se produisait pas en production)

lors de la création d'un OSD de type bluestore sans spécifier le chemin pour le WAL, mais avec la spécification du db

root@ceph01-qa:~#ceph-volume lvm create --bluestore --data /dev/sde --block.db /dev/md0
 stderr: 2019-04-12 10:39:27.211242 7eff461b6e00 -1 bluestore(/var/lib/ceph/osd/ceph-0/) _read_fsid uuid non parsable
 stderr: 2019-04-12 10:39:27.213185 7eff461b6e00 -1 bdev(0x55824c273680 /var/lib/ceph/osd/ceph-0//block.wal) ouvert a reçu : (22) Argument invalide
 stderr: 2019-04-12 10:39:27.213201 7eff461b6e00 -1 bluestore(/var/lib/ceph/osd/ceph-0/) _open_db ajout du périphérique de bloc (/var/lib/ceph/osd/ceph-0//block.wal) retourné : (22) Argument invalide
 stderr: 2019-04-12 10:39:27.999039 7eff461b6e00 -1 bluestore(/var/lib/ceph/osd/ceph-0/) mkfs a échoué, (22) Argument invalide
 stderr: 2019-04-12 10:39:27.999057 7eff461b6e00 -1 OSD::mkfs: ObjectStore::mkfs a échoué avec l'erreur (22) Argument invalide
 stderr: 2019-04-12 10:39:27.999141 7eff461b6e00 -1 ** ERREUR : erreur lors de la création de l'objet de stockage vide dans /var/lib/ceph/osd/ceph-0/ : (22) Argument invalide

Cependant, si sur le même miroir (ou à un autre endroit, au choix) je crée une autre partition pour le WAL et que je l'indique lors de la création de l'OSD — tout se passera bien (à part l'apparition d'un WAL séparé, que vous ne vouliez peut-être pas).

Mais, étant donné qu'il était de toute façon prévu à long terme d'apporter le WAL sur NVMe, cette pratique s'est avérée utile.

root@ceph01-qa:~#ceph-volume lvm create --bluestore --data /dev/sdf --block.wal /dev/md0p2 --block.db /dev/md1p2

Nous avons créé des moniteurs, des gestionnaires et des OSD. Maintenant, nous voulons les regrouper différemment, car il est prévu d'avoir des disques de différents types — des pools rapides sur SSD et des gros, mais lents sur des disques SAS.

Considérons que les serveurs ont chacun 20 disques, la première dizaine est un type, la seconde — un autre.
La carte initiale et par défaut ressemble à ceci :

ceph osd tree

root@ceph01-q:~# ceph osd tree
ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF
-1 14.54799 root par défaut
-3 9.09200 hôte ceph01-q
0 ssd 1.00000 osd.0 en ligne 1.00000 1.00000
1 ssd 1.00000 osd.1 en ligne 1.00000 1.00000
2 ssd 1.00000 osd.2 en ligne 1.00000 1.00000
3 ssd 1.00000 osd.3 en ligne 1.00000 1.00000
4 hdd 1.00000 osd.4 en ligne 1.00000 1.00000
5 hdd 0.27299 osd.5 en ligne 1.00000 1.00000
6 hdd 0.27299 osd.6 en ligne 1.00000 1.00000
7 hdd 0.27299 osd.7 up 1.00000 1.00000
8 hdd 0.27299 osd.8 up 1.00000 1.00000
9 hdd 0.27299 osd.9 up 1.00000 1.00000
10 hdd 0.27299 osd.10 up 1.00000 1.00000
11 hdd 0.27299 osd.11 up 1.00000 1.00000
12 hdd 0.27299 osd.12 up 1.00000 1.00000
13 hdd 0.27299 osd.13 up 1.00000 1.00000
14 hdd 0.27299 osd.14 up 1.00000 1.00000
15 hdd 0.27299 osd.15 up 1.00000 1.00000
16 hdd 0.27299 osd.16 up 1.00000 1.00000
17 hdd 0.27299 osd.17 up 1.00000 1.00000
18 hdd 0.27299 osd.18 up 1.00000 1.00000
19 hdd 0.27299 osd.19 up 1.00000 1.00000
-5 5.45599 host ceph02-q
20 ssd 0.27299 osd.20 up 1.00000 1.00000
21 ssd 0.27299 osd.21 up 1.00000 1.00000
22 ssd 0.27299 osd.22 up 1.00000 1.00000
23 ssd 0.27299 osd.23 up 1.00000 1.00000
24 hdd 0.27299 osd.24 up 1.00000 1.00000
25 hdd 0.27299 osd.25 up 1.00000 1.00000
26 hdd 0.27299 osd.26 up 1.00000 1.00000
27 hdd 0.27299 osd.27 up 1.00000 1.00000
28 hdd 0.27299 osd.28 up 1.00000 1.00000
29 hdd 0.27299 osd.29 up 1.00000 1.00000
30 hdd 0.27299 osd.30 up 1.00000 1.00000
31 hdd 0.27299 osd.31 up 1.00000 1.00000
32 hdd 0.27299 osd.32 up 1.00000 1.00000
33 hdd 0.27299 osd.33 up 1.00000 1.00000
34 hdd 0.27299 osd.34 up 1.00000 1.00000
35 hdd 0.27299 osd.35 up 1.00000 1.00000
36 hdd 0.27299 osd.36 up 1.00000 1.00000
37 hdd 0.27299 osd.37 up 1.00000 1.00000
38 hdd 0.27299 osd.38 up 1.00000 1.00000
39 hdd 0.27299 osd.39 up 1.00000 1.00000
-7 6.08690 host ceph03-q
40 ssd 0.27299 osd.40 up 1.00000 1.00000
41 ssd 0.27299 osd.41 up 1.00000 1.00000
42 ssd 0.27299 osd.42 up 1.00000 1.00000
43 ssd 0.27299 osd.43 up 1.00000 1.00000
44 hdd 0.27299 osd.44 up 1.00000 1.00000
45 hdd 0.27299 osd.45 up 1.00000 1.00000
46 hdd 0.27299 osd.46 up 1.00000 1.00000
47 hdd 0.27299 osd.47 up 1.00000 1.00000
48 hdd 0.27299 osd.48 up 1.00000 1.00000
49 hdd 0.27299 osd.49 up 1.00000 1.00000
50 hdd 0.27299 osd.50 up 1.00000 1.00000
51 hdd 0.27299 osd.51 up 1.00000 1.00000
52 hdd 0.27299 osd.52 up 1.00000 1.00000
53 hdd 0.27299 osd.53 up 1.00000 1.00000
54 hdd 0.27299 osd.54 up 1.00000 1.00000
55 hdd 0.27299 osd.55 up 1.00000 1.00000
56 hdd 0.27299 osd.56 up 1.00000 1.00000
57 hdd 0.27299 osd.57 up 1.00000 1.00000
58 hdd 0.27299 osd.58 up 1.00000 1.00000
59 hdd 0.89999 osd.59 up 1.00000 1.00000

Créons nos propres racks et serveurs, avec du blackjack et tout le reste :

root@ceph01-q:~#ceph osd crush add-bucket rack01 root #créé un nouveau root
root@ceph01-q:~#ceph osd crush add-bucket ceph01-q host #créé un nouvel hôte
root@ceph01-q:~#ceph osd crush move ceph01-q root=rack01 #déplacé le serveur vers un autre rack
root@ceph01-q:~#osd crush add 28 1.0 host=ceph02-q # Ajouter l'OSD au serveur

# Si mal créé, on peut supprimer
root@ceph01-q:~# ceph osd crush remove osd.4
root@ceph01-q:~# ceph osd crush remove rack01

Les problèmes auxquels nous avons été confrontés dans le combat cluster, en essayant de créer un nouvel hôte et de le déplacer dans un rack existant — la commande ceph osd crush move ceph01-host root=rack01 se bloquait, et les moniteurs commençaient à tomber un par un. L'interruption de la commande avec CTRL+C ramenait le cluster dans le monde vivant.

Une recherche a révélé un tel problème : https://tracker.ceph.com/issues/23386

La solution a été de dumper le crushmap et de supprimer la section rule replicated_ruleset

root@ceph01-prod:~#ceph osd getcrushmap -o crushmap.row #Exportez la carte sous forme brute
root@ceph01-prod:~#crushtool -d crushmap.row -o crushmap.txt #Convertissez-la en format lisible
root@ceph01-prod:~#vim  crushmap.txt #Modifiez en supprimant la règle replicated_ruleset
root@ceph01-prod:~#crushtool -c crushmap.txt  -o new_crushmap.row #Compilez-la à nouveau
root@ceph01-prod:~#ceph osd setcrushmap -i  new_crushmap.row #Chargez-la dans le cluster

Avertissement : cette opération peut provoquer un rééquilibrage des groupes de placement entre les OSD. Cela s'est produit chez nous, mais de manière très limitée.

Une étrange situation à laquelle nous avons été confrontés dans le cluster de test est que, après le redémarrage du serveur, les OSD oubliaient qu'ils avaient été déplacés vers de nouveaux serveurs et racks, et retournaient au root par défaut.
Au final, en créant un schéma final où nous avons séparé les racines pour les disques SSD et pour les disques rotatifs, nous avons réparti tous les OSD dans les racks et avons simplement supprimé la racine par défaut. Après le redémarrage, les OSD sont restés à leur place.
En fouillant plus tard dans la documentation, nous avons trouvé un paramètre qui régit ce comportement. Il en sera question dans la deuxième partie.

Comment nous avons constitué différents groupes par type de disque.

Pour commencer, nous avons créé deux racines — une pour les SSD et une pour les HDD.

root@ceph01-q:~#ceph osd crush add-bucket ssd-root root
root@ceph01-q:~#ceph osd crush add-bucket hdd-root root

Étant donné que les serveurs sont physiquement dans différents racks, pour plus de commodité, nous avons créé des racks et y avons placé les serveurs.

# Стойки:
root@ceph01-q:~#ceph osd crush add-bucket ssd-rack01 rack
root@ceph01-q:~#ceph osd crush add-bucket ssd-rack02 rack
root@ceph01-q:~#ceph osd crush add-bucket ssd-rack03 rack

root@ceph01-q:~#ceph osd crush add-bucket hdd-rack01 rack
root@ceph01-q:~#ceph osd crush add-bucket hdd-rack01 rack
root@ceph01-q:~#ceph osd crush add-bucket hdd-rack01 rack

# Сервера
root@ceph01-q:~#ceph osd crush add-bucket ssd-ceph01-q host
root@ceph01-q:~#ceph osd crush add-bucket ssd-ceph02-q host
root@ceph01-q:~#ceph osd crush add-bucket ssd-ceph03-q host

root@ceph01-q:~#ceph osd crush add-bucket hdd-ceph01-q host
root@ceph01-q:~#ceph osd crush add-bucket hdd-ceph02-q host
root@ceph01-q:~#ceph osd crush add-bucket hdd-ceph02-q host

et avons réparti les disques par type sur différents serveurs.

root@ceph01-q:~# Les disques 0 à 3 sont des SSD, situés dans ceph01-q, nous les plaçons dans le serveur 
root@ceph01-q:~#  ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 0 1 host=ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 1 1 host=ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 2 1 host=ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 3 1 host=ssd-ceph01-q
root-ceph01-q:~# de même pour les autres serveurs.

Après avoir réparti les disques entre les racines ssd-root et hdd-root, nous avons laissé le root par défaut vide, donc nous pouvons le supprimer.

root-ceph01-q:~#ceph osd crush remove default

Ensuite, nous devons créer des règles de distribution que nous lierons aux pools que nous créons — dans les règles, nous indiquerons dans quelles racines nous pouvons placer les données de notre pool et le niveau d'unicité des répliques — par exemple, les répliques doivent nécessairement être sur différents serveurs, ou dans différents racks (même dans différentes racines, si nous avons une telle distribution).

Avant de choisir un type, il est préférable de lire la documentation :
http://docs.ceph.com/docs/jewel/rados/operations/crush-map/#crushmaprules

root-ceph01-q:~#ceph osd crush rule create-simple rule-ssd ssd-root host firstn
root-ceph01-q:~#ceph osd crush rule create-simple rule-hdd hdd-root host firstn
root-ceph01-q:~# Nous avons défini deux règles selon lesquelles les données sont répliquées
root-ceph01-q:~# entre les hôtes - c'est-à-dire qu'une réplique doit se trouver sur un autre hôte,
root-ceph01-q:~# même s'ils sont dans le même rack
root-ceph01-q:~# En production, si possible, il est préférable de répartir les hôtes
root-ceph01-q:~# par racks et de spécifier de répartir les répliques par racks:
root-ceph01-q:~# ##ceph osd crush rule create-simple rule-ssd ssd-root rack firstn

Nous créons donc des pools dans lesquels nous souhaitons à l'avenir stocker les images de disque de notre virtualisation — PROXMOX :

    root-ceph01-q:~# #ceph osd pool create {NAME} {pg_num}  {pgp_num}
    root-ceph01-q:~# ceph osd pool create ssd_pool 1024 1024 
    root-ceph01-q:~# ceph osd pool create hdd_pool 1024 1024

Et nous indiquons à ces pools quelles règles de placement utiliser

 root-ceph01-q:~#ceph osd crush rule ls # regardons la liste des règles
    root-ceph01-q:~#ceph osd crush rule dump rule-ssd | grep rule_id #sélectionnons l'ID souhaité
    root-ceph01-q:~#ceph osd pool set ssd_pool crush_rule 2

Le choix du nombre de groupes de placement doit être fait avec une vision préalable de votre cluster — combien d'OSD il y aura à peu près, quel pourcentage de données (en pourcentage du volume total) sera dans le pool, combien de données au total.

Idéalement, il ne devrait pas y avoir plus de 300 groupes de placement par disque, et il sera plus facile de balancer avec de petits groupes de placement — c'est-à-dire que si votre pool entier représente 10 To et qu'il contient 10 PG — ce sera problématique de balancer en déplaçant des blocs d'un téraoctet (pg) — il est plus simple et plus uniforme de transférer du sable de petite taille avec des seaux.

Mais il faut se rappeler que plus il y a de PG, plus les ressources dépensées pour calculer leur emplacement augmentent — la mémoire et le CPU commencent à être sollicités.

Une compréhension approximative peut être obtenue grâce à un calculateur, fourni par les développeurs de la documentation CEPH.

Liste des matériaux :

https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data
http://www.admin-magazine.com/HPC/Articles/Linux-I-O-Schedulers
http://onreader.mdl.ru/MasteringCeph/content/Ch09.html#030202
https://tracker.ceph.com/issues/23386
https://ceph.com/pgcalc/

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster