Przejdź do głównej zawartości

Testowanie z wstrzykiwaniem błędów i awarii

Twój serwis płatności po cichu degraduje się, gdy PostgreSQL zwalnia pod obciążeniem. Pula połączeń się zapełnia, żądania ustawiają się w kolejce, timeouty, których istnienie zakładałeś, nigdy się nie odpalają, a pierwszy sygnał, jaki dostajesz, to wściekłe tweety klientów dwadzieścia minut po tym, jak zaczął się skok opóźnień. Wszystkie testy happy-path przechodzą. Bug pojawia się tylko wtedy, gdy zależność się psuje — czyli dokładnie w warunku, którego nigdy nie testujesz.

Wstrzykiwanie błędów (zwane też wstrzykiwaniem awarii, ang. fault injection) rozwiązuje to przez celowe psucie zależności w kontrolowany sposób: spowolnij zapytanie do bazy danych, zabij poda po stronie zależności, porzucaj pakiety między dwoma serwisami albo spraw, by zewnętrzne API zwracało błędy 500 na żądanie. Inżynieria chaosu to rozszerzenie tej samej idei na poziom systemu — uruchamianie tych awarii na działającym systemie (zwykle na stagingu), aby zweryfikować, że się trzyma. Ten przewodnik pokazuje, jak używać AI do generowania eksperymentów, runbooków i kodu odporności, na konkretnym stacku, zamiast pisać YAML z pamięci.

  • Powtarzalny workflow do wstrzykiwania opóźnień, awarii zależności i presji na zasoby z Cursor, Claude Code i Codex
  • Prawdziwe, uruchamialne artefakty: konfigurację Toxiproxy do lokalnego wstrzykiwania awarii i manifest Chaos Mesh dla Kubernetes
  • Prompty do skopiowania, które nazywają Twój stack i produkują eksperymenty plus runbooki uwzględniające wycofywanie, a nie generyczne porady
  • Test, który dowodzi, że Twoje timeouty, ponawianie prób i circuit breakery faktycznie się odpalają pod awarią
  • Checklistę “Gdy coś się zepsuje” dla trybów awarii samych eksperymentów chaosu

Wstrzykiwanie awarii dzieje się na dwóch warstwach i na obu przyda Ci się pomoc AI.

Wstrzykiwanie aplikacyjne / lokalne

Wstrzykuj awarie w procesie lub na lokalnym proxy (Toxiproxy, nock, klienty HTTP wstrzykujące awarie). Szybkie, deterministyczne, działa w CI. Najlepsze do udowodnienia logiki timeoutów, ponawiania prób i fallbacków pojedynczego serwisu.

Wstrzykiwanie systemowe / chaos

Wstrzykuj awarie na warstwie infrastruktury (Chaos Mesh, LitmusChaos) na działającym klastrze: zabijanie podów, opóźnienia sieciowe, partycje, presja na zasoby. Najlepsze do weryfikacji odzyskiwania, promienia wybuchu i zachowania w stanie ustalonym między serwisami.

Zacznij od warstwy aplikacyjnej, bo jest tania i deterministyczna, a potem awansuj do chaosu na poziomie systemu, gdy pojedyncze serwisy są już udowodnione jako odporne.

Workflow 1: lokalne wstrzykiwanie opóźnień zależności z Toxiproxy

Dział zatytułowany „Workflow 1: lokalne wstrzykiwanie opóźnień zależności z Toxiproxy”

Najszybszy sposób, by udowodnić, że serwis radzi sobie z wolną zależnością, to postawić przed nim sterowalne proxy. Toxiproxy siedzi między Twoją aplikacją a PostgreSQL (albo Redisem, albo dowolnym serwisem TCP) i pozwala na żądanie dodawać opóźnienia, ograniczać przepustowość albo zrywać połączenie.

Minimalna, realna konfiguracja dla serwisu Node/Express rozmawiającego z PostgreSQL:

test/resilience/db-latency.test.js
import { Toxiproxy } from 'toxiproxy-node-client';
import { Pool } from 'pg';
const toxiproxy = new Toxiproxy('http://localhost:8474');
// App connects to PG *through* the proxy on 5433, not directly on 5432.
const pool = new Pool({ host: 'localhost', port: 5433, database: 'app' });
test('order creation fails fast when the DB is slow, instead of hanging', async () => {
const proxy = await toxiproxy.get('postgres');
// Inject 4s of latency on every query through the proxy.
await proxy.addToxic({ type: 'latency', attributes: { latency: 4000 } });
const start = Date.now();
// statement_timeout / pool timeout should trip well before 4s.
await expect(createOrder(pool, { sku: 'ABC', qty: 1 })).rejects.toThrow(/timeout/i);
expect(Date.now() - start).toBeLessThan(3000);
await proxy.removeToxic('latency'); // always clean up
});

Sednem nie jest hydraulika proxy — chodzi o to, że masz teraz deterministyczny sposób, by stwierdzić “ten kod szybko zawodzi”. Pozwól AI wygenerować macierz eksperymentów i asercje dla Twojego prawdziwego serwisu.

Otwórz testowany serwis plus jego warstwę dostępu do danych, a następnie użyj trybu Agent, aby Cursor widział Twoją prawdziwą konfigurację timeoutów i ustawienia puli.

Gdy pojedyncze serwisy już szybko zawodzą, zweryfikuj, że klaster się regeneruje. Chaos Mesh to natywna dla Kubernetes platforma chaosu, której eksperymenty to zwykłe CRD — co oznacza, że AI może je generować dokładnie, a Ty możesz je przeglądać jak każdy inny manifest. Pojedynczy NetworkChaos, który opóźnia ruch do Twojej bazy danych na dziesięć minut:

chaos/postgres-latency.yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: postgres-latency
namespace: staging
spec:
action: delay
mode: all
selector:
namespaces:
- staging
labelSelectors:
app: postgresql
delay:
latency: '200ms'
jitter: '50ms'
duration: '10m'

Zastosuj go poleceniem kubectl apply -f chaos/postgres-latency.yaml, obserwuj swoje dashboardy SLO i usuń go poleceniem kubectl delete -f (albo pozwól wygasnąć duration). Użyj AI, by rozwinąć pojedynczą awarię w etapowy eksperyment z kontrolami bezpieczeństwa.

Odwołaj się do swoich istniejących manifestów Kubernetes, aby Cursor używał Twoich prawdziwych namespace’ów, etykiet i nazw serwisów zamiast placeholderów.

Wstrzyknięcie awarii to tylko połowa testu. Druga połowa to potwierdzenie, że Twoja logika timeoutów, ponawiania prób i circuit breakera faktycznie zadziałała. Prawdziwy test circuit breakera z mockiem wstrzykującym awarie dla zewnętrznego API:

test/resilience/circuit-breaker.test.js
import nock from 'nock';
import { getInventory } from '../../src/clients/inventory.client.js';
test('circuit opens after 5 consecutive failures, then serves the fallback', async () => {
// Make the external API fail every call.
nock('https://inventory.internal').get(/.*/).times(10).reply(500);
// Drive enough failures to trip the breaker (threshold = 5).
for (let i = 0; i < 5; i++) {
await expect(getInventory('SKU-1')).rejects.toThrow();
}
// 6th call should short-circuit and return the cached fallback,
// NOT hit the network again.
const result = await getInventory('SKU-1');
expect(result.source).toBe('fallback-cache');
expect(nock.isDone()).toBe(false); // breaker prevented the 6th network call
});

Uruchamiaj tanie testy awarii na warstwie aplikacyjnej przy każdym PR, a cięższe eksperymenty chaosu na stagingu planuj z określoną częstotliwością. Zaplanowany workflow, który uruchamia zestaw testów odporności i może też na żądanie odpalić pojedynczy nazwany eksperyment chaosu:

.github/workflows/continuous-chaos.yml
name: Continuous Fault Injection
on:
schedule:
- cron: '0 */6 * * *' # every 6 hours
workflow_dispatch:
inputs:
experiment:
description: 'Chaos Mesh manifest to apply (staging only)'
required: false
default: 'postgres-latency'
jobs:
resilience-tests:
runs-on: ubuntu-latest
services:
toxiproxy:
image: ghcr.io/shopify/toxiproxy
ports:
- 8474:8474
- 5433:5433
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- name: Run fault-injection resilience suite
run: npx jest test/resilience/
staging-chaos:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Pre-flight - abort if staging is unhealthy
run: ./scripts/preflight.sh --slo-status green --active-incidents none
- name: Apply chaos experiment (auto-expires via spec.duration)
run: kubectl apply -n staging -f chaos/${{ github.event.inputs.experiment }}.yaml

Eksperymenty chaosu mają własne tryby awarii. Oto te, które najmocniej dają się we znaki zespołom.

  1. Zmapuj zależności i zdefiniuj stan ustalony. Wypisz każde zewnętrzne wywołanie (baza danych, cache, kolejka, zewnętrzne API) oraz SLO, jakie każde musi utrzymać pod obciążeniem.

  2. Najpierw wstrzykuj na warstwie aplikacyjnej. Użyj Toxiproxy i nock, aby deterministycznie udowodnić w CI, że timeouty, ponawianie prób i fallbacki każdego serwisu się odpalają.

  3. Awansuj do chaosu na poziomie systemu. Gdy serwisy już szybko zawodzą, uruchamiaj eksperymenty Chaos Mesh na stagingu, aby zweryfikować odzyskiwanie i promień wybuchu.

  4. Skodyfikuj bezpieczeństwo. Ogranicz każdy eksperyment przez duration, zawężaj selektory do stagingu i trzymaj polecenia przerwania w runbooku.

  5. Zautomatyzuj częstotliwość. Uruchamiaj tanie testy awarii przy każdym PR; planuj cięższy chaos na stagingu i blokuj go za kontrolami zdrowia przed lotem.