Wie man I/O-Fehler zur Laufzeit simuliert
Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →

In Produktionsumgebungen können Dateisystemfehler durch verschiedene Vorfälle wie Festplattenausfälle oder Administrationsfehler auftreten. Als Chaos-Engineering-Plattform unterstützt Chaos Mesh seit seinen frühen Versionen die Simulation von I/O-Fehlern in Dateisystemen. Durch einfaches Hinzufügen einer IOChaos CustomResourceDefinition (CRD) können wir beobachten, wie das Dateisystem Fehler verursacht und Fehlermeldungen zurückgibt.
Vor Chaos Mesh 1.0 war dieses Experiment jedoch nicht einfach und konnte viele Ressourcen verbrauchen. Wir mussten Sidecar-Container über mutierende Admission-Webhooks in den Pod injecten und den ENTRYPOINT-Befehl umschreiben. Selbst wenn kein Fehler injiziert wurde, verursachte der injizierte Sidecar-Container erheblichen Overhead.
Chaos Mesh 1.0 hat dies geändert. Jetzt können wir mit IOChaos Fehler in ein Dateisystem zur Laufzeit injizieren. Dies vereinfacht den Prozess und reduziert den Systemoverhead erheblich. Dieser Blogbeitrag erläutert, wie wir das IOChaos-Experiment ohne Sidecar implementieren.
I/O-Fehlerinjektion
Um I/O-Fehler zur Laufzeit zu simulieren, müssen wir Fehler in ein Dateisystem injizieren, nachdem das Programm Systemaufrufe (wie Lese- oder Schreiboperationen) gestartet hat, aber bevor die Aufrufe beim Ziel-Dateisystem ankommen. Dafür gibt es zwei Ansätze:
-
Berkeley Packet Filter (BPF) verwenden; allerdings kann es keine Verzögerungen injizieren.
-
Eine Dateisystemebene namens ChaosFS vor dem Ziel-Dateisystem einfügen. ChaosFS nutzt das Ziel-Dateisystem als Backend und empfängt Anfragen vom Betriebssystem. Die gesamte Aufrufkette ist Zielprogramm-Systemaufruf → Linux-Kernel → ChaosFS → Ziel-Dateisystem. Da ChaosFS anpassbar ist, können wir Verzögerungen und Fehler nach Bedarf injizieren. Daher fiel die Wahl auf ChaosFS.
ChaosFS bringt jedoch mehrere Herausforderungen mit sich:
-
Wenn ChaosFS Dateien im Ziel-Dateisystem liest und schreibt, müssen wir ChaosFS auf einen anderen Pfad mounten als den in der Pod-Konfiguration angegebenen Zielpfad. ChaosFS kann nicht auf den Pfad des Zielverzeichnisses gemountet werden.
-
Wir müssen ChaosFS vor dem Start des Zielprogramms mounten. Denn das neu gemountete ChaosFS wirkt sich nur auf Dateien aus, die neu vom Programm im Ziel-Dateisystem geöffnet werden.
-
Wir müssen ChaosFS im
mnt-Namespace des Ziel-Containers mounten. Details finden Sie unter mount_namespaces(7) — Linux-Handbuchseite.
Vor Chaos Mesh 1.0 verwendeten wir den mutierenden Admission-Webhook zur Implementierung von IOChaos. Diese Technik löste die drei genannten Probleme und ermöglichte uns:
-
Skripte im Ziel-Container auszuführen. Diese Aktion änderte das Zielverzeichnis des ChaosFS-Backend-Dateisystems (z.B. von
/mnt/azu/mnt/a_bak), sodass wir ChaosFS auf den Zielpfad (/mnt/a) mounten konnten. Den Pod-Startbefehl wurde modifiziert, z.B. von/appzu/waitfs.sh /app. -
Das Skript
waitfs.shüberprüfte kontinuierlich, ob das Dateisystem erfolgreich gemountet wurde. Nach erfolgreichem Mount wurde/appgestartet. -
Einen zusätzlichen Container im Pod hinzufügen, der ChaosFS ausführt. Dieser Container musste ein Volume mit dem Zielcontainer teilen (z.B.
/mnt), und wir haben dann dieses Volume im Zielverzeichnis gemountet (z.B./mnt/a). Zudem mussten wir die Mount Propagation für dieses Volume korrekt konfigurieren, damit der Mount den Host erreicht und dann als Slave an das Ziel weitergegeben wird.
Diese drei Ansätze ermöglichten die Injektion von I/O-Fehlern während des Programmbetriebs. Die Implementierung war jedoch wenig praktikabel:
-
Fehler konnten nur in Unterverzeichnissen eines Volumes injiziert werden, nicht im gesamten Volume. Als Workaround musste
mv(Umbenennen) durchmount moveersetzt werden, um den Mount-Point des Zielvolumes zu verschieben. -
Befehle im Pod mussten explizit geschrieben werden statt implizit Image-Befehle zu nutzen. Andernfalls konnte das
/waitfs.sh-Skript das Programm nicht korrekt starten, nachdem das Dateisystem gemountet war. -
Der entsprechende Container benötigte eine korrekte Konfiguration für Mount Propagation. Aufgrund potenzieller Datenschutz- und Sicherheitsbedenken durften wir die Konfiguration nicht über den Mutating Admission Webhook anpassen.
-
Die Injektionskonfiguration war umständlich. Noch problematischer: Ein neuer Pod musste erstellt werden, nachdem die Konfiguration für die Fehlerinjektion bereitstand.
-
ChaosFS konnte während des Programmbetriebs nicht zurückgezogen werden. Selbst ohne injizierte Fehler war die Performance erheblich beeinträchtigt.
I/O-Fehler ohne Mutating Admission Webhook injizieren
Wie lassen sich diese Herausforderungen ohne Mutating Admission Webhook bewältigen? Betrachten wir zunächst den Grund, warum wir überhaupt einen zusätzlichen Container für ChaosFS benötigten: Um das Dateisystem im Zielcontainer zu mounten.
Tatsächlich gibt es eine alternative Lösung: Statt Container im Pod hinzuzufügen, können wir mit dem Linux-Systemaufruf setns den Namespace des aktuellen Prozesses ändern und anschließend ChaosFS via mount im Zielcontainer einbinden. Für ein Dateisystem unter /mnt sieht der neue Injektionsprozess wie folgt aus:
-
setnsverwenden, um in den mnt-Namespace des Zielcontainers zu wechseln. -
mount --moveausführen, um/mntnach/mnt_bakzu verschieben. -
ChaosFS unter
/mnteinbinden und/mnt_bakals Backend verwenden.
Nach diesem Prozess öffnet, liest und schreibt der Zielcontainer Dateien in /mnt über ChaosFS. Verzögerungen oder Fehler lassen sich so wesentlich einfacher injizieren. Dennoch bleiben zwei Fragen:
-
Wie werden bereits geöffnete Dateien durch den Zielprozess behandelt?
-
Wie erfolgt die Wiederherstellung, wenn das Dateisystem bei geöffneten Dateien nicht unmounted werden kann?
Dynamisches Ersetzen von Dateideskriptoren
ptrace löst beide Probleme. Mit ptrace können wir geöffnete Dateideskriptoren (FDs) zur Laufzeit ersetzen, ebenso das aktuelle Arbeitsverzeichnis (CWD) und mmap-Bereiche.
Nutzung von ptrace zum Ausführen von Binärprogrammen
ptrace ermöglicht es, Zielprozesse (Tracees) beliebige Systemaufrufe oder Binärprogramme ausführen zu lassen. Dazu wird die RIP-registeradresse des Zielprozesses modifiziert und eine int3-Instruktion eingefügt, um einen Breakpoint auszulösen. Nach Programmstopp müssen Register und Speicher wiederhergestellt werden.
Hinweis:
In der x86_64-Architektur zeigt das RIP-Register (Instruction Pointer) stets auf die Speicheradresse der nächsten auszuführenden Anweisung. Zum Laden des Programms in den Prozessspeicher:
-
ptrace verwenden, um mmap im Zielprozess aufzurufen und benötigten Speicher zu allokieren.
-
Schreibe das Binärprogramm in den neu zugewiesenen Speicher und richte den RIP-Register darauf aus.
-
Nach Beendigung des Binärprogramms rufe munmap auf, um den Speicherbereich zu bereinigen.
Als Best Practice ersetzen wir oft ptrace-POKE_TEXT-Schreibvorgänge durch process_vm_writev, da bei großen Datenmengen process_vm_writev effizienter arbeitet.
Mit ptrace können wir einen Prozess dazu bringen, seinen eigenen Dateideskriptor (FD) auszutauschen. Jetzt benötigen wir nur noch eine Methode, um diesen Austausch durchzuführen: den dup2-Systemaufruf.
Verwendung von dup2 zum Austauschen von Dateideskriptoren
Die Signatur der dup2-Funktion lautet int dup2(int oldfd, int newfd);. Sie erstellt eine Kopie des alten Dateideskriptors (oldfd) mit der neuen Deskriptornummer newfd. Falls newfd bereits einem geöffneten Dateideskriptor entspricht, wird dieser automatisch geschlossen.
Beispiel: Der aktuelle Prozess öffnet /var/run/__chaosfs__test__/a mit dem Dateideskriptor 1. Um diese geöffnete Datei durch /var/run/test/a zu ersetzen, führt der Prozess folgende Schritte aus:
-
Verwendet den
fcntl-Systemaufruf, um dieOFlags(Parameter desopen-Aufrufs, z.B.O_WRONLY) von/var/run/__chaosfs__test__/aabzurufen. -
Verwendet den
Iseek-Systemaufruf, um die aktuelle Position vonseekzu ermitteln. -
Öffnet
/var/run/test/amit denselbenOFlagsüber denopen-Systemaufruf. Angenommener Dateideskriptor:2. -
Verwendet
Iseek, um dieseek-Position des neuen Dateideskriptors2anzupassen. -
Führt
dup2(2, 1)aus, um den Dateideskriptor1von/var/run/__chaosfs__test__/adurch den neuen Deskriptor2zu ersetzen. -
Schließt den Dateideskriptor
2.
Nach Abschluss zeigt Dateideskriptor 1 des Prozesses nun auf /var/run/test/a. Um Fehler zu injizieren, durchlaufen alle weiteren Dateioperationen das Filesystem in Userspace (FUSE). FUSE ermöglicht nicht-privilegierten Benutzern, eigene Dateisysteme ohne Kernel-Modifikationen zu erstellen.
Programmierung des Dateideskriptor-Austauschs
Die Kombination von ptrace und dup2 ermöglicht es dem Tracer, den Tracee seinen eigenen Dateideskriptor austauschen zu lassen. Jetzt müssen wir ein Binärprogramm schreiben und im Zielprozess ausführen:
Hinweis:
In unserer Implementierung gehen wir davon aus, dass:
- Die Threads des Zielprozesses POSIX-Threads sind und geöffnete Dateien gemeinsam nutzen.
- Beim Thread-Erstellen via
clonewird der ParameterCLONE_FILESübergeben.Daher ersetzt Chaos Mesh nur den Dateideskriptor des ersten Threads in der Thread-Gruppe.
-
Schreiben Sie Assembler-Code gemäß den oben beschriebenen Prinzipien und Syscall-Anweisungen. Ein Beispiel finden Sie hier.
-
Übersetzen Sie den Code mit einem Assembler in ein Binärprogramm. Wir verwenden dynasm-rs.
-
Verwenden Sie ptrace, um das Programm im Zielprozess auszuführen. Während der Ausführung wird der Dateideskriptor zur Laufzeit ersetzt.
Gesamtprozess der Fehlerinjektion
Das folgende Diagramm veranschaulicht den Gesamtprozess der I/O-Fehlerinjektion:

Jede horizontale Linie im Diagramm entspricht einem Thread, der in Richtung der Pfeile abläuft. Die Aufgaben Mount/Umount Filesystem und Replace FD sind sequenziell angeordnet. Angesichts des beschriebenen Prozesses ergibt diese Anordnung vollkommen Sinn.
Was kommt als Nächstes?
Ich habe erläutert, wie wir die Fehlerinjektion zur Simulation von I/O-Fehlern zur Laufzeit implementieren (siehe chaos-mesh/toda). Die aktuelle Implementierung ist jedoch noch nicht perfekt:
-
Generierungsnummern werden nicht unterstützt.
-
ioctl wird nicht unterstützt.
-
Chaos Mesh erkennt nicht sofort, ob ein Dateisystem erfolgreich gemountet wurde. Die Überprüfung erfolgt erst nach einer Sekunde.
Falls Sie Chaos Mesh interessant finden und verbessern möchten, treten Sie gerne unserem Slack-Kanal bei oder reichen Sie Pull Requests bzw. Issues im GitHub-Repository ein.
Dies ist der erste Beitrag einer Serie zur Implementierung von Chaos Mesh. Wenn Sie erfahren möchten, wie andere Arten der Fehlerinjektion umgesetzt werden, bleiben Sie dran.