Chaos ist kein Bug, sondern ein Feature: Einführung in Chaos Engineering mit C++
\nStellen Sie sich vor: Ihr C++-Dienst lief monatelang ohne Ausfälle. Und dann – zack! – Plattenausfall, Paketverlust oder ein plötzlicher SIGKILL. Und alles ist vorbei. Das System stürzt ab, die Benutzer sind wütend, und Sie suchen fieberhaft nach dem Bug in den Logs. Kommt Ihnen das bekannt vor?
Wir sind es gewohnt, Software unter idealen Bedingungen zu testen: stabiles Netzwerk, schnelle Festplatte, unbegrenzter Speicher. Aber die Realität ist grausam. Genau hier kommt Chaos Engineering ins Spiel – eine Disziplin, die uns lehrt, das System absichtlich zu zerstören, um es stärker zu machen. Und heute sprechen wir darüber, wie man diesen Ansatz auf C++-Anwendungen anwendet.
\nChaos Engineering für C++ bedeutet nicht, "einen Chaos Monkey zu starten und zu beten". Es geht um kontrollierte Experimente, die Schwachstellen aufdecken, bevor die Benutzer sie finden. Wir lernen, Speicherfehler zu injizieren, langsamen I/O zu emulieren und Netzwerkverbindungen zu unterbrechen – und das alles mit reinem C++-Code.
\n\nWarum C++ besonders Chaos-Engineering braucht?
\nDie Sprache C++ gibt dem Entwickler enorme Macht: manuelle Speicherverwaltung, Low-Level-Zugriff auf Ressourcen, keine Garbage Collection. Aber diese Stärke ist ein zweischneidiges Schwert. Ein Fehler in delete[], ein Leck in einem RAII-Wrapper oder ein falscher reinterpret_cast können ein stabiles System in ein fragiles Kartenhaus verwandeln.
Traditionelle Unit-Tests prüfen selten, wie sich der Code verhält bei:
\n- \n
- Speicherknappheit (
std::bad_alloc) \n - Plötzlichem Schließen eines Dateideskriptors \n
- Netzwerkverzögerungen (Latenzspitzen) \n
- Datenkorruption im Shared Memory \n
Chaos Engineering für C++ schließt diese Lücken. Wir schreiben nicht nur Tests – wir provozieren das System zu Fehlern und beobachten, ob es überlebt.
\n\nWerkzeugkasten: Wie man Chaos in C++-Code bringt
\nFür die Durchführung von Experimenten benötigen wir Fault Injection-Werkzeuge (Fehlerinjektion). In C++ können wir auf verschiedenen Ebenen agieren:
\n- \n
- Auf Code-Ebene: Abfangen von Systemaufrufen via LD_PRELOAD oder
dlsym\n - Auf Bibliotheksebene: Austausch von Standard-Allokatoren und Sockets \n
- Auf Betriebssystemebene: Verwendung von
ptraceoder eBPF, um in den Prozess einzugreifen \n
Wir gehen den praktischsten Weg – wir schreiben unsere eigene Bibliothek zur Fehlerinjektion, die sich leicht in jedes C++-Projekt integrieren lässt.
\n\nBeispiel 1: Simulation eines Speicherzuweisungsfehlers
\nEines der häufigsten Probleme in C++ ist ein unerwarteter std::bad_alloc. Wie verhält sich Ihr Code, wenn new eine Ausnahme auslöst? Lassen Sie es uns überprüfen.
// chaos_memory.h\n#pragma once\n#include <new>\n#include <cstdlib>\n#include <atomic>\n#include <cstddef>\n\nclass ChaosAllocator {\npublic:\n // Legt die Ausfallwahrscheinlichkeit fest (0.0 - 1.0)\n static void set_failure_probability(double prob) {\n failure_probability_ = prob;\n }\n\n static void* allocate(std::size_t size) {\n if (should_fail()) {\n throw std::bad_alloc();\n }\n return std::malloc(size);\n }\n\n static void deallocate(void* ptr) {\n std::free(ptr);\n }\n\nprivate:\n static bool should_fail() {\n // Einfachster Zufallszahlengenerator zur Demonstration\n static std::atomic<unsigned int> seed = 42;\n seed = seed * 1103515245 + 12345;\n return (seed / 65536) % 100 < failure_probability_ * 100;\n }\n\n inline static double failure_probability_ = 0.0;\n};\nJetzt können wir den Standard-Allokator ersetzen und prüfen, wie unser Code mit Speicherausfällen umgeht:
\n// main.cpp\n#include <iostream>\n#include <vector>\n#include "chaos_memory.h"\n\n// Benutzerdefinierter Allokator