Comment simuler des fautes E/S à l’exécution
Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →

Dans un environnement de production, des défaillances du système de fichiers peuvent survenir en raison de divers incidents tels que des pannes de disque ou des erreurs d'administration. En tant que plateforme d'ingénierie du chaos, Chaos Mesh prend en charge la simulation de fautes E/S dans un système de fichiers depuis ses premières versions. En ajoutant simplement une définition de ressource personnalisée (CRD) IOChaos, nous pouvons observer comment le système de fichiers échoue et renvoie des erreurs.
Cependant, avant Chaos Mesh 1.0, cette expérience n'était pas aisée et pouvait consommer beaucoup de ressources. Nous devions injecter des conteneurs sidecar dans le Pod via les webhooks d'admission mutatifs et réécrire la commande ENTRYPOINT. Même lorsqu'aucune faille n'était injectée, le conteneur sidecar injecté générait une surcharge substantielle.
Chaos Mesh 1.0 a changé cette situation. Désormais, nous pouvons utiliser IOChaos pour injecter des fautes dans un système de fichiers à l'exécution. Cela simplifie le processus et réduit considérablement la surcharge système. Cet article présente comment nous implémentons l'expérience IOChaos sans utiliser de sidecar.
Injection de fautes E/S
Pour simuler des fautes E/S à l'exécution, nous devons injecter des erreurs dans un système de fichiers après que le programme a initié des appels système (comme des lectures ou écritures), mais avant que les requêtes n'atteignent le système de fichiers cible. Nous pouvons le faire de deux manières :
-
Utiliser le Berkeley Packet Filter (BPF) ; cependant, il ne peut pas injecter de délai.
-
Ajouter une couche de système de fichiers appelée ChaosFS devant le système de fichiers cible. ChaosFS utilise le système de fichiers cible comme backend et reçoit les requêtes du système d'exploitation. La chaîne d'appel complète est appel système du programme cible -> noyau Linux -> ChaosFS -> système de fichiers cible. Comme ChaosFS est personnalisable, nous pouvons injecter des délais et des erreurs à volonté. ChaosFS est donc notre choix.
Mais ChaosFS présente plusieurs problèmes :
-
Si ChaosFS lit et écrit des fichiers dans le système de fichiers cible, nous devons le monter sur un chemin différent de celui spécifié dans la configuration du Pod. ChaosFS ne peut pas être monté sur le chemin du répertoire cible.
-
Nous devons monter ChaosFS avant le démarrage du programme cible. En effet, le ChaosFS nouvellement monté ne prend effet que sur les fichiers nouvellement ouverts par le programme dans le système de fichiers cible.
-
Nous devons monter ChaosFS dans l'espace de noms
mntdu conteneur cible. Pour plus de détails, voir mount_namespaces(7) — page de manuel Linux.
Avant Chaos Mesh 1.0, nous utilisions le webhook d'admission mutatif pour implémenter IOChaos. Cette technique résolvait les trois problèmes mentionnés ci-dessus et nous permettait de :
-
Exécuter des scripts dans le conteneur cible. Cette action modifiait le répertoire cible du système de fichiers backend de ChaosFS (par exemple, de
/mnt/aà/mnt/a_bak) pour pouvoir monter ChaosFS sur le chemin cible (/mnt/a). Modifier la commande de démarrage du Pod. Par exemple, nous pouvions remplacer la commande originale/apppar/waitfs.sh /app. -
Le script
waitfs.shvérifiait continuellement si le système de fichiers était correctement monté. Une fois monté, il lançait/app. -
Ajouter un nouveau conteneur dans le Pod pour exécuter ChaosFS. Ce conteneur devait partager un volume avec le conteneur cible (par exemple,
/mnt), puis nous montions ce volume sur le répertoire cible (par exemple,/mnt/a). Nous activions également correctement la propagation de montage pour que le montage de ce volume pénètre l'hôte puis transmette en mode slave vers la cible.
Ces trois approches nous permettaient d'injecter des fautes I/O pendant l'exécution du programme. Cependant, l'injection était loin d'être pratique :
-
Nous ne pouvions injecter des fautes que dans un sous-répertoire de volume, pas dans le volume entier. Le contournement consistait à remplacer
mv(renommer) parmount movepour déplacer le point de montage du volume cible. -
Nous devions explicitement écrire les commandes dans le Pod plutôt que d'utiliser implicitement les commandes de l'image. Sinon, le script
/waitfs.shne pouvait pas démarrer correctement le programme après le montage du système de fichiers. -
Le conteneur correspondant devait avoir une configuration appropriée pour la propagation de montage. En raison de problèmes potentiels de confidentialité et de sécurité, nous ne pouvions pas modifier cette configuration via le webhook de mutation d'admission.
-
La configuration d'injection était laborieuse. Pire encore, nous devions créer un nouveau Pod après que la configuration puisse injecter des fautes.
-
Nous ne pouvions pas retirer ChaosFS pendant l'exécution du programme. Même si aucune faute ou erreur n'était injectée, les performances étaient grandement affectées.
Injecter des fautes I/O sans le webhook de mutation d'admission
Et si nous résolvions ces problèmes épineux sans le webhook de mutation d'admission ? Revenons en arrière et réfléchissons à la raison pour laquelle nous utilisions ce webhook pour ajouter un conteneur exécutant ChaosFS : c'était pour monter le système de fichiers dans le conteneur cible.
En réalité, il existe une autre solution. Au lieu d'ajouter des conteneurs au Pod, nous pouvons d'abord utiliser l'appel système Linux setns pour modifier l'espace de noms du processus courant, puis utiliser l'appel mount pour monter ChaosFS dans le conteneur cible. Supposons que le système de fichiers à injecter soit /mnt. Le nouveau processus d'injection est le suivant :
-
Utiliser
setnspour que le processus courant entre dans l'espace de noms mnt du conteneur cible. -
Exécuter
mount --movepour déplacer/mntvers/mnt_bak. -
Monter ChaosFS sur
/mnten utilisant/mnt_bakcomme backend.
Une fois ce processus terminé, le conteneur cible ouvrira, lira et écrira les fichiers dans /mnt via ChaosFS. Ainsi, les délais ou fautes sont injectés beaucoup plus facilement. Cependant, deux questions subsistent :
-
Comment gérer les fichiers déjà ouverts par le processus cible ?
-
Comment restaurer le processus étant donné que nous ne pouvons pas démonter le système de fichiers tant que des fichiers sont ouverts ?
Remplacer dynamiquement les descripteurs de fichiers
ptrace résout ces deux problèmes. Nous pouvons utiliser ptrace pour remplacer les descripteurs de fichiers (FD) ouverts à l'exécution, ainsi que le répertoire de travail courant (CWD) et les mmap.
Utiliser ptrace pour exécuter un programme binaire dans un tracee
ptrace est un outil puissant qui permet au processus cible (tracee) d'exécuter n'importe quel appel système ou programme binaire. Pour qu'un tracee exécute le programme, ptrace modifie l'adresse pointée par RIP et ajoute une instruction int3 pour déclencher un point d'arrêt. Lorsque le programme binaire s'arrête, nous devons restaurer les registres et la mémoire.
Note :
Dans l'architecture x86_64, le registre RIP (pointeur d'instruction) pointe toujours vers l'adresse mémoire de la prochaine instruction à exécuter. Pour charger le programme dans les espaces mémoire du processus cible :
-
Utiliser ptrace pour appeler mmap dans le programme cible et allouer la mémoire nécessaire.
-
Écrire le programme binaire dans la mémoire nouvellement allouée et pointer le registre RIP vers celui-ci.
-
Après l'arrêt du programme binaire, appeler munmap pour nettoyer la section mémoire.
En tant que meilleure pratique, nous remplaçons souvent les écritures ptrace POKE_TEXT par process_vm_writev car, pour de gros volumes de données, process_vm_writev offre de meilleures performances.
Grâce à ptrace, nous pouvons faire remplacer son propre FD par un processus. Il nous manque seulement une méthode pour déclencher ce remplacement : l'appel système dup2.
Utiliser dup2 pour remplacer un descripteur de fichier
La signature de la fonction dup2 est int dup2(int oldfd, int newfd);. Elle crée une copie de l'ancien descripteur (oldfd) avec le nouveau numéro newfd. Si newfd correspond déjà à un fichier ouvert, ce dernier est automatiquement fermé.
Par exemple, si le processus courant ouvre /var/run/__chaosfs__test__/a avec le FD 1, le remplacement par /var/run/test/a nécessite :
-
Utiliser l'appel système
fcntlpour obtenir lesOFlags(le paramètre utilisé par l'appel systèmeopen, tel queO_WRONLY) de/var/run/__chaosfs__test__/a. -
Utiliser l'appel système
Iseekpour obtenir la position courante duseek. -
Ouvrir
/var/run/test/aviaopenavec les mêmesOFlags, en supposant que le FD obtenu soit2. -
Utiliser
Iseekpour positionner leseekdu nouveau FD2. -
Utilise
dup2(2, 1)pour remplacer le FD1de/var/run/__chaosfs__test__/apar le nouveau FD2. -
Fermer le FD
2.
Après ces opérations, le FD 1 pointe désormais vers /var/run/test/a. Ainsi, pour injecter des fautes, les opérations ultérieures sur le fichier transitent par FUSE (Filesystem in Userspace) - une interface logicielle permettant aux utilisateurs non privilégiés de créer des systèmes de fichiers sans modifier le code du noyau.
Écrire un programme pour remplacer le descripteur de fichier
La combinaison de ptrace et dup2 permet au traceur de faire remplacer le FD ouvert par le tracé lui-même. Il nous reste à écrire un programme binaire que le processus cible exécutera :
Note :
Cette implémentation suppose que :
- Les threads du processus cible sont des threads POSIX partageant les fichiers ouverts
- Lors de la création de threads via
clone, le paramètreCLONE_FILESest passéAinsi, Chaos Mesh ne remplace que le FD du premier thread du groupe.
-
Écrire un code assembleur basé sur les sections précédentes et l'utilisation des directives syscall. Exemple de code assembleur.
-
Traduire le code en programme binaire via un assembleur comme dynasm-rs.
-
Utiliser ptrace pour exécuter ce programme dans le processus cible, remplaçant le FD à l'exécution.
Processus global d'injection de fautes
Le diagramme suivant illustre le processus global d'injection de fautes d'E/S :

Dans ce diagramme, chaque ligne horizontale correspond à un thread s'exécutant dans le sens des flèches. Les tâches Mount/Umount Filesystem et Replace FD sont soigneusement orchestrées en séquence. Compte tenu du processus décrit, cette organisation est parfaitement logique.
Prochaines étapes
J'ai expliqué comment nous implémentons l'injection de fautes pour simuler des erreurs d'E/S au runtime (voir chaos-mesh/toda). Cependant, l'implémentation actuelle est encore perfectible :
-
Les numéros de génération ne sont pas pris en charge.
-
ioctl n'est pas supporté.
-
Chaos Mesh ne détermine pas immédiatement si un système de fichiers est correctement monté. Cette vérification n'intervient qu'après une seconde.
Si Chaos Mesh vous intéresse et que vous souhaitez contribuer, rejoignez notre canal Slack ou soumettez vos pull requests/issues sur notre dépôt GitHub.
Ceci est le premier article d'une série sur l'implémentation de Chaos Mesh. Si vous souhaitez découvrir comment d'autres types d'injection de fautes sont implémentés, restez à l'écoute.