Pour tester les performances de lecture et d'écriture d'une charge de travail Google Kubernetes Engine (GKE) à partir de plusieurs clients GKE, utilisez l'outil de benchmark IOR. Les instructions suivantes vous expliquent comment automatiser la configuration de votre client et utiliser IOR avec mpirun sur SSH sans mot de passe entre les pods Kubernetes pour tester les E/S agrégées.
Prérequis
Une instance Managed Lustre déjà provisionnée.
Un environnement Docker local configuré et authentifié pour l'envoi à Google Artifact Registry ou Container Registry (voir Méthodes d'authentification).
Assurez-vous que la valeur
mtude votre réseau est définie sur8896.
Créer un cluster GKE
Pour tester les performances, vous avez besoin d'un cluster GKE sur lequel le pilote CSI Lustre géré est activé. Pour les charges de travail de stockage hautes performances, configurez vos pools de nœuds GKE avec des familles de machines optimisées pour le calcul (par exemple, c2 ou c3) et la mise en réseau TIER_1.
Exécutez la commande suivante pour créer un cluster GKE Standard optimisé pour les tests de performances :
gcloud container clusters create CLUSTER_NAME \
--zone=ZONE \
--machine-type=MACHINE_TYPE \
--addons=LustreCsiDriver \
--network-performance-configs=total-egress-bandwidth-tier=TIER_1 \
--network=NETWORK \
--num-nodes=NUM_NODES
Remplacez ZONE et NETWORK par les valeurs spécifiques à votre déploiement. Le cluster doit résider dans le même réseau VPC que votre instance Managed Lustre.
Choisissez un MACHINE_TYPE. Pour savoir comment choisir des types de machines afin d'obtenir le meilleur débit, consultez Considérations relatives aux performances.
Si votre type de machine n'est pas compatible avec le réseau TIER_1, supprimez la ligne
--network-performance-configsde la commande.Spécifiez le NUM_NODES. Pour saturer votre système de fichiers, la capacité réseau globale de votre cluster doit dépasser le débit provisionné de votre système de fichiers d'environ 20%.
Pour les machines sur lesquelles la mise en réseau Tier 1 est activée, un seul nœud peut transférer entre 25 et 200 Gbit/s (environ 3 000 à 25 000 Mbit/s), en fonction de la famille de VM et du nombre de processeurs. Pour les instances standards, le trafic de sortie est généralement limité à environ 2 Gbit/s par vCPU.
Par exemple, si la capacité de votre instance Managed Lustre génère un débit théorique de 100 000 Mo/s, vous avez besoin d'un trafic de sortie client global de 120 000 Mo/s (
100,000 * 1.2) pour la saturer :- Avec des instances standards : si chaque nœud a une sortie publiée de 2 000 Mbit/s, vous devez provisionner au moins 60 nœuds (
120,000 / 2,000). - Avec le réseau de niveau 1 : si chaque nœud dispose d'une sortie publiée de 10 000 Mbit/s (environ 80 Gbit/s), vous devez provisionner au moins 12 nœuds (
120,000 / 10,000).
- Avec des instances standards : si chaque nœud a une sortie publiée de 2 000 Mbit/s, vous devez provisionner au moins 60 nœuds (
Créer l'image Docker IOR
Créez une image de conteneur avec OpenMPI et IOR installés. Compilez l'IOR avec la prise en charge des E/S asynchrones (AIO) pour de meilleures performances.
Créez un fichier nommé
Dockerfileen local :FROM ubuntu:22.04 # Prevent interactive prompts during installation ENV DEBIAN_FRONTEND=noninteractive # Install dependencies, SSH, and required Autotools packages RUN apt-get update && apt-get install -y \ openssh-server \ openmpi-bin \ libopenmpi-dev \ wget \ git \ make \ gcc \ g++ \ automake \ autoconf \ libtool \ pkg-config \ libaio-dev \ sudo \ && rm -rf /var/lib/apt/lists/* # Build IOR from source (version 4.0.0) with Asynchronous I/O (AIO) support RUN git clone -b 4.0.0 https://github.com/hpc/ior /tmp/ior \ && cd /tmp/ior \ && ./bootstrap \ && ./configure --disable-dependency-tracking --with-aio \ && make -j"$(nproc)" \ && make install \ && rm -rf /tmp/ior # Configure SSH for OpenMPI passwordless communication RUN mkdir /var/run/sshd RUN echo 'root:root' | chpasswd RUN sed -i 's/^#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config # SSH login fix so user isn't kicked out after container initialization RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd EXPOSE 22 CMD ["/usr/sbin/sshd", "-D"]Créez et transférez cette image vers le registre de conteneurs de votre choix. Les instructions de ce document utilisent Artifact Registry.
export IMAGE_TAG="gcr.io/PROJECT_ID/lustre-ior-benchmark:latest" docker build -t $IMAGE_TAG . docker push $IMAGE_TAG
Générer des clés SSH sans mot de passe pour MPI
OpenMPI nécessite une communication entre les nœuds à l'aide de SSH sans mot de passe. Créez une clé SSH et stockez-la dans un secret Kubernetes.
Générez les clés RSA :
ssh-keygen -t rsa -b 4096 -C "mpi-user" -N '' -f "./id_rsa"Créez le secret Kubernetes :
kubectl create secret generic mpi-ssh-secret \ --from-file=id_rsa=./id_rsa \ --from-file=id_rsa.pub=./id_rsa.pub \ --from-file=authorized_keys=./id_rsa.pub
Créer un volume persistant et une revendication
Connectez vos pods GKE à l'instance Managed Lustre à l'aide du provisionnement statique.
Créez un fichier nommé
lustre-pv.yaml. Remplacez les éléments suivants :- CAPACITY avec la capacité de stockage de votre instance en Gio.
- EXTENDED_LUSTRE_ID avec votre identifiant Managed Lustre, au format PROJECT_ID/ZONE/INSTANCE_NAME.
Par exemple,
project-123/us-west1-a/my-lustre-instance. - LUSTRE_IP par l'adresse IP de montage de votre instance.
- FS_NAME par le nom du système de fichiers de l'instance.
Vous pouvez récupérer ces valeurs à l'aide de la commande
gcloud lustre instances describe.apiVersion: v1 kind: PersistentVolume metadata: name: my-lustre-pv spec: storageClassName: "" claimRef: name: my-lustre-pvc namespace: default accessModes: - ReadWriteMany capacity: storage: CAPACITYGi # retain `Gi` suffix persistentVolumeReclaimPolicy: Retain volumeMode: Filesystem csi: driver: lustre.csi.storage.gke.io volumeHandle: EXTENDED_LUSTRE_ID # project-name/zone/instance-name volumeAttributes: ip: LUSTRE_IP filesystem: FS_NAME --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-lustre-pvc spec: storageClassName: "" volumeName: my-lustre-pv accessModes: - ReadWriteMany resources: requests: storage: CAPACITYGiAppliquez le fichier manifeste :
kubectl apply -f lustre-pv.yaml
Déployer les nœuds de calcul MPI
Pour mettre à l'échelle les tâches d'IOR sur plusieurs nœuds, déployez un StatefulSet avec votre image de benchmark.
Créez un fichier nommé
mpi-workers.yaml. Spécifiez votre PROJECT_ID et définissez NUM_NODES sur le nombre de nœuds de votre cluster.apiVersion: v1 kind: Service metadata: name: mpi-workers labels: app: mpi-worker spec: clusterIP: None selector: app: mpi-worker ports: - port: 22 name: ssh --- apiVersion: apps/v1 kind: StatefulSet metadata: name: mpi-worker spec: serviceName: "mpi-workers" replicas: NUM_NODES selector: matchLabels: app: mpi-worker template: metadata: labels: app: mpi-worker spec: tolerations: - operator: "Exists" containers: - name: mpi-worker image: gcr.io/PROJECT_ID/lustre-ior-benchmark:latest command: ["/bin/sh", "-c"] args: - >- mkdir -p /var/run/sshd && ssh-keygen -A && mkdir -p /root/.ssh && echo "Host *" > /root/.ssh/config && echo " StrictHostKeyChecking no" >> /root/.ssh/config && echo " UserKnownHostsFile=/dev/null" >> /root/.ssh/config && cp /mnt/mpi-ssh-keys/id_rsa /root/.ssh/id_rsa && cp /mnt/mpi-ssh-keys/id_rsa.pub /root/.ssh/id_rsa.pub && cp /mnt/mpi-ssh-keys/authorized_keys /root/.ssh/authorized_keys && chmod 700 /root/.ssh && chmod 600 /root/.ssh/* && exec /usr/sbin/sshd -D ports: - containerPort: 22 volumeMounts: - name: lustre-mount mountPath: /lustre - name: ssh-key-secret mountPath: /mnt/mpi-ssh-keys readOnly: true volumes: - name: lustre-mount persistentVolumeClaim: claimName: my-lustre-pvc - name: ssh-key-secret secret: secretName: mpi-ssh-secretAppliquez le fichier manifeste :
kubectl apply -f mpi-workers.yaml
Exécuter le benchmark IOR
Lancez le benchmark à partir du premier pod (mpi-worker-0), en le traitant comme le nœud principal.
Générez un fichier d'hôte contenant les adresses IP internes des nœuds de calcul et copiez-le sur le nœud principal :
kubectl get pods -l app=mpi-worker -o jsonpath='{range .items[*]}{.status.podIP}{"\n"}{end}' > hosts.txt kubectl cp hosts.txt mpi-worker-0:/root/hostfileOuvrez une session bash dans votre pod principal :
kubectl exec -it mpi-worker-0 -- /bin/bashDans le pod, créez un répertoire de test :
mkdir -p /lustre/testDéfinissez les variables de test :
export NUM_NODES="NUM_NODES" export PROCESSES_PER_NODE="PROCESSES_PER_NODE" export NUM_PROCESSES=$(( NUM_NODES * PROCESSES_PER_NODE ))Où :
NUM_NODES : nombre total de pods de nœuds de calcul participant au test.
PROCESSES_PER_NODE : nombre de rangs MPI à exécuter sur chaque conteneur. Nous vous recommandons de commencer par définir ce paramètre sur le nombre de cœurs physiques (ou la moitié du nombre de processeurs virtuels) de vos machines clientes. Pour les types de machines hautes performances, définir cette valeur entre
8et16permet généralement d'obtenir le meilleur débit réseau.
Exécutez les commandes de benchmark :
Débit en écriture
Cette commande écrit en continu pendant 60 secondes pour tester le débit maximal en régime permanent. La taille de fichier par tâche est définie sur un plafond arbitrairement élevé de 50 Tio pour que les tâches continuent d'écrire jusqu'à l'expiration du minuteur de 60 secondes.
mpirun \ --allow-run-as-root \ --mca plm_rsh_no_tree_spawn 1 \ --mca opal_set_max_sys_limits 1 \ --mca plm_rsh_num_concurrent ${NUM_NODES} \ --mca plm_rsh_args "-o StrictHostKeyChecking=no" \ --npernode ${PROCESSES_PER_NODE} \ --np ${NUM_PROCESSES} \ --hostfile ~/hostfile \ /usr/local/bin/ior \ -a AIO \ --posix.odirect \ -F -g -v -w -k \ -t 4m -b 50t \ -D 60 \ -O stoneWallingWearOut=1 \ -O stoneWallingStatusFile=/lustre/test/ior-easy.stonewall \ -o /lustre/test/ior_file
Débit en lecture
Cette phase relit exactement la quantité de données qui a été écrite avec succès lors du test de débit d'écriture de 60 secondes. Bien que la taille de fichier par tâche (
-b) soit définie sur 50 Tio pour correspondre à la géométrie de la phase d'écriture, l'indicateur-O stoneWallingWearOut=1indique à IOR d'arrêter la lecture dès qu'il atteint la limite exacte des données enregistrée dans le fichier d'état stonewall.mpirun \ --allow-run-as-root \ --mca plm_rsh_no_tree_spawn 1 \ --mca opal_set_max_sys_limits 1 \ --mca plm_rsh_num_concurrent ${NUM_NODES} \ --mca plm_rsh_args "-o StrictHostKeyChecking=no" \ --npernode ${PROCESSES_PER_NODE} \ --np ${NUM_PROCESSES} \ --hostfile ~/hostfile \ /usr/local/bin/ior \ -a AIO \ --posix.odirect \ -F -g -v -r -k \ -t 4m -b 50t \ -D 60 \ -O stoneWallingWearOut=1 \ -O stoneWallingStatusFile=/lustre/test/ior-easy.stonewall \ -o /lustre/test/ior_file
IOPS en écriture
Ce test utilise de petites tailles de transfert de 4 Kio et des tailles de fichier de 8 Gio par tâche pour mesurer le nombre maximal d'opérations d'entrée/sortie par seconde (IOPS) que le système de fichiers peut gérer.
mpirun \ --allow-run-as-root \ --mca plm_rsh_no_tree_spawn 1 \ --mca opal_set_max_sys_limits 1 \ --mca plm_rsh_num_concurrent ${NUM_NODES} \ --mca plm_rsh_args "-o StrictHostKeyChecking=no" \ --np ${NUM_PROCESSES} \ --oversubscribe \ --map-by node \ --bind-to socket \ --hostfile ~/hostfile \ /usr/local/bin/ior \ -e \ -t 4k \ -b 8g \ -s 1 \ -a AIO \ --posix.odirect \ --aio.max-pending=256 \ -w \ -F \ -z \ -Q 1 \ -G 1745405099 \ -D 45 \ -O stoneWallingWearOut=1 \ -o /lustre/test/ior-random
IOPS en lecture
Pour éviter la lecture à partir d'un fichier clairsemé, ce test utilise deux commandes : une écriture pour créer un fichier solide avec des tailles de transfert de 4 Mio et des tailles de fichier de 8 Gio par tâche, suivie du test IOPS de lecture aléatoire de 4 Kio.
Créez le fichier à lire :
mpirun \ --allow-run-as-root \ --mca plm_rsh_no_tree_spawn 1 \ --mca opal_set_max_sys_limits 1 \ --mca plm_rsh_num_concurrent ${NUM_NODES} \ --mca plm_rsh_args "-o StrictHostKeyChecking=no" \ --npernode ${PROCESSES_PER_NODE} \ --np ${NUM_PROCESSES} \ --oversubscribe \ --map-by node \ --bind-to socket \ --hostfile ~/hostfile \ /usr/local/bin/ior \ -a AIO \ --posix.odirect \ -w \ -F \ -k \ -t 4m \ -b 8g \ -s 1 \ -Q 1 \ -G 1745405099 \ -o /lustre/test/ior_rand_read
Exécutez le test de lecture IOR :
mpirun \ --allow-run-as-root \ --mca plm_rsh_no_tree_spawn 1 \ --mca opal_set_max_sys_limits 1 \ --mca plm_rsh_num_concurrent ${NUM_NODES} \ --mca plm_rsh_args "-o StrictHostKeyChecking=no" \ --oversubscribe \ --map-by node \ --bind-to socket \ --npernode ${PROCESSES_PER_NODE} \ --np ${NUM_PROCESSES} \ --hostfile ~/hostfile \ /usr/local/bin/ior \ -a AIO \ --posix.odirect \ --aio.max-pending 256 \ -r \ -F \ -z \ -t 4k \ -b 8g \ -s 1 \ -Q 1 \ -G 1745405099 \ -D 45 \ -O stoneWallingWearOut=1 \ -o /lustre/test/ior_rand_read
Les options
mpirunsont les suivantes :--mca plm_rsh_no_tree_spawn 1: désactive la création de daemons basés sur un arbre pour améliorer la fiabilité du lancement sur les nœuds.--mca opal_set_max_sys_limits 1: tente automatiquement de définir les limites du système (comme le nombre maximal de fichiers ouverts) sur leurs valeurs maximales autorisées.--mca plm_rsh_num_concurrent: définit le nombre maximal de connexions SSH simultanées quempirunutilisera lors du lancement des daemons de nœud de calcul.--mca plm_rsh_args ...: contourne la vérification stricte des clés d'hôte pour empêcher les invites SSH interactives de bloquer le lancement du processus MPI.--prefix ...: définit explicitement le chemin d'installation d'OpenMPI pour Rocky Linux et RHEL afin que les nœuds de calcul puissent trouver le démon requis (orted).--allow-run-as-root: permet àmpirunde s'exécuter en tant qu'utilisateur racine.--oversubscribe: permet à MPI de planifier plus de processus sur un nœud qu'il n'y a de cœurs physiques disponibles.--map-by node: distribue les processus MPI de manière séquentielle et uniforme sur les nœuds disponibles.--bind-to socket: lie les processus MPI aux sockets de processeur physiques pour optimiser l'accès à la mémoire et les performances du cache.--npernode: nombre de processus par nœud.--np: nombre total de processus MPI à lancer.--hostfile: spécifie le fichier contenant la liste des hôtes sur lesquels exécuter le test.
Les options
iorsont les suivantes :-a AIO --posix.odirect: utilise le moteur d'E/S asynchrones (AIO) combiné aux E/S directes POSIX. Cela contourne le cache de pages RAM côté client et force les écritures simultanées non bloquantes directement sur les serveurs de stockage, ce qui garantit que le benchmark mesure les performances réelles du stockage réseau plutôt que les tampons de mémoire.--aio.max-pending=256: détermine le nombre maximal d'opérations d'E/S asynchrones simultanées en cours par processus.-C: réorganisez les tâches pour optimiser les performances de lecture.-F: mode "un fichier par processus".-g: utilisez des barrières pour séparer les phases d'écriture et de lecture du test.-v: affiche la journalisation détaillée.-w/-r: indique à IOR d'exécuter le test de performances d'écriture (-w) ou de lecture (-r).-k: empêche IOR de supprimer le fichier de test après l'écriture, ce qui le rend disponible pour le test de lecture.-e: effectue unfsyncaprès la phase d'écriture pour garantir que les données sont validées sur les lecteurs de stockage.-z: indique à IOR d'effectuer des E/S à accès aléatoire plutôt qu'à accès séquentiel.-s 1: définit le nombre de segments sur 1.-Q 1: définit le décalage des tâches par nœud, en alignant les tâches afin que le benchmark se coordonne correctement sur tous les nœuds.-G 1745405099: code en dur le code temporel de la graine aléatoire afin que la phase de lecture génère exactement les mêmes décalages de fichier aléatoires que ceux utilisés par la phase d'écriture.-t: définit la taille du transfert pour chaque opération d'E/S (par exemple,4mpour le débit,4kpour les IOPS).-b: définit la taille de bloc cible par processus (par exemple,50tpour le débit,8gpour les IOPS) afin de s'assurer que le test ne manque pas de données utiles pendant l'exécution chronométrée.-D: limite la durée d'exécution du test à un nombre spécifique de secondes (par exemple,60ou45). Cette "stonewalling" met fin au benchmark de manière uniforme pour capturer une véritable mesure à l'état stable.-O stoneWallingWearOut=1: force tous les threads simultanés à continuer à générer une charge d'écriture continue pendant toute la durée, ce qui empêche les threads plus rapides de se terminer plus tôt et de réduire la pression globale sur le réseau.-O stoneWallingStatusFile=<path>: écrit un fichier de validation de l'état à la fin de la phase d'écriture. La phase de lecture suivante utilise ce fichier pour lire uniquement les blocs qui ont été validés avec succès, ce qui évite les erreurs de pointeur null lors des lectures.-o: chemin d'accès au fichier de test sur le système de fichiers Managed Lustre.
Afficher les résultats
Une fois le benchmark terminé, il affiche les métriques de performances globales de toutes les VM ou de tous les pods clients directement dans votre terminal. Recherchez le tableau Résultats en bas de la sortie pour trouver votre débit ou vos IOPS maximums.
Métriques clés
aggregate filesize: quantité totale de données écrites ou lues pendant le test sur tous les clients participants.bw(MiB/s)/Max Write/Max Read: métrique la plus importante pour les tests séquentiels. Elle indique la bande passante globale obtenue par le système de fichiers Lustre géré.IOPS: métrique la plus importante pour les tests d'E/S aléatoires. Il indique le nombre maximal d'opérations d'entrée/sortie par seconde.
Exporter les résultats vers un fichier
Si vous souhaitez analyser vos résultats de manière programmatique, les insérer dans une base de données ou les enregistrer pour une analyse ultérieure, vous pouvez demander à IOR d'exporter les données récapitulatives aux formats JSON et CSV au lieu de simplement les afficher à l'écran.
Pour ce faire, ajoutez les indicateurs -O à la fin de la chaîne de commande ior :
-O summaryFormat=JSON \
-O summaryFile=/lustre/test/perf-results/summary.json \
-O saveRankPerformanceDetailsCSV=/lustre/test/perf-results/details.csv
Le répertoire de sortie doit exister sur le système de fichiers avant l'exécution du benchmark.
Performances attendues par rapport aux performances réelles
Vous pouvez calculer le débit maximal mathématique de votre système de fichiers en fonction de sa capacité provisionnée et de son niveau de performances. Étant donné que la capacité de stockage est provisionnée en gibioctets (Gio) et que les niveaux sont évalués en tébioctets (Tio), vous devez d'abord convertir votre capacité :
(Capacity GiB/1 024) * Tier MBps = Mbit/s théorique max
Par exemple, une instance de 216 000 Gio dans le niveau 500 Mbit/s par Tio fournit mathématiquement un débit de 105 469 Mbit/s ((216 000/1 024) * 500).
Le débit maximal observé sera toujours limité par la capacité de débit provisionnée de votre système de fichiers ou par les limites de sortie réseau combinées de vos machines clientes, selon la valeur la plus basse.
Voici quelques raisons courantes pour lesquelles vos chiffres de référence peuvent ne pas atteindre les vitesses théoriques :
Surcharge TCP/IP : l'encapsulation réseau standard et les en-têtes de paquets consomment environ 5 à 10% de la bande passante brute. Votre maximum mathématique inclut ce coût, mais le benchmark IOR ne mesure que la charge utile brute écrite sur le disque.
Limites du réseau client : les machines clientes sont soumises à des limites strictes de bande passante de sortie. Si vous utilisez un petit nombre de clients ou de nœuds, ou des types de machines sans réseau de niveau 1 activé, les clients limiteront le benchmark avant que le système de fichiers Managed Lustre n'atteigne sa limite.
Changement de contexte MPI : si
PROCESSES_PER_NODEest défini sur une valeur supérieure au nombre de cœurs physiques de vos machines clientes, la contention du processeur et la surcharge de changement de contexte dégraderont artificiellement les performances d'E/S du benchmark.E/S directes manquantes : si l'indicateur
--posix.odirectest omis, les données transitent par le cache de pages RAM du client. Cela introduit des goulots d'étranglement de la mémoire et une surcharge du processeur qui masquent les véritables performances de stockage réseau.
Effectuer un nettoyage
Pour éviter que les ressources utilisées sur cette page ne soient facturées sur votre compte Google Cloud , procédez comme suit :
Supprimez les fichiers de test du volume Managed Lustre :
kubectl exec mpi-worker-0 -- rm -rf /lustre/testSupprimez le cluster GKE :
gcloud container clusters delete CLUSTER_NAME --zone=ZONELa suppression du cluster entraîne également la suppression des pods GKE, du code secret Kubernetes et de la revendication de volume persistant.
Si vous avez transféré l'image Docker de référence et que vous n'en avez plus besoin, supprimez-la de votre dépôt :
gcloud container images delete gcr.io/PROJECT_ID/lustre-ior-benchmark:latest --force-delete-tagsSi vous avez créé l'instance Managed Lustre spécifiquement pour ce test et que vous n'en avez plus besoin, supprimez-la :
gcloud lustre instances delete INSTANCE_ID --location=LOCATION
Résoudre les goulots d'étranglement courants liés aux tests de performances
Si les résultats de votre benchmark sont nettement inférieurs au niveau de performances de stockage attendu, consultez Résoudre les problèmes de goulots d'étranglement courants.