Mert Özen Her Satırda Daha İleri
DevOps SRE ve Production Incident'ları — #10

Game Day ve Chaos Engineering: Incident'ı Beklemeden Prova Etmek

Mert Özen 15 Kas 2026 15 dk 11 görüntülenme
Game Day ve Chaos Engineering: Incident'ı Beklemeden Prova Etmek

Game day, steady state, hipotez, küçük blast radius ve önceden yazılmış abort koşuluyla yapılan kontrollü bir arıza provasıdır; chaos engineering bunu ölçülebilir bir deneye çevirir. Bookstore'un ödeme client'ına Polly'nin chaos strategy'leriyle hata enjekte ediyor, iki hipotezin çürüdüğünü (page 46 dakika gecikiyor, runbook sebebi göstermiyor) ölçüyor ve sonuçları runbook'a ve bir retry'a geri besleyerek checkout hatasını yüzde 9,3'ten yüzde 0,9'a indiriyoruz.

Sürüm notu: Bu yazıdaki örnekler .NET 10, Microsoft.Extensions.Http.Resilience 11.0.0 (Polly.Core 8.4.2 ile) ve Prometheus 3.16.0 ile laboratuvarda denenmiştir. Game day bir laboratuvar provasıdır; production'da ya da gerçek bir ödeme sağlayıcısında hiçbir şey çalıştırılmadı.

Önce Tahmin Et

Bookstore'un (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) ödeme sağlayıcısı bir gün isteklerin yüzde 10'una 503 dönmeye başlarsa ne olur? Ekipte üç görüş var. Emre: "Checkout availability SLO'muz (Service Level Objective; servisin hedeflenen güvenilirlik seviyesi) yüzde 99,5; yüzde 10 hata bütçeyi 20 kat hızlı yakar, 1 saatlik page birkaç dakikada çalar." Ayşe: "Çalar ama runbook'un 3. adımı sağlayıcının 503'lerini gösterir, beş dakikada sebebi buluruz." Selin: "Bunu gerçek bir arızada mı öğreneceğiz?" Emre'nin hesabını, Ayşe'nin iddiasını ve Selin'in sorusunu bu yazıda sınıyoruz. Bir ipucu: hatayı nereye enjekte ettiğin, neyin görünür olacağını belirliyor. Ayşe'nin iddiası bu yazının deneyinde tutmadı; neden tutmadığını tahmin et.

Chaos Engineering: Hipotezle Başlayan Deney

Chaos engineering, bir sistemin production'daki beklenmedik koşullara dayanabileceğine dair güveni deneyle kazanmak. principlesofchaos.org'da yayımlanan Principles of Chaos Engineering metni bunu bir bilimsel deney gibi kuruyor: önce sistemin normal davranışını ölçülebilir bir çıktıyla tanımla (steady state; örneğin checkout başarı oranı ve latency'si), bu davranışın bir arıza sırasında da süreceğine dair bir hipotez kur, gerçek hayatta olabilecek bir olayı (sunucu kaybı, bağımlılığın yavaşlaması, ağ kopması) kontrollü biçimde enjekte et ve hipotezi çürütmeye çalış. İleri prensipler de var: hipotezi iç metriklere değil kullanıcının gördüğü çıktıya kur, deneyleri gerçek trafikte ve otomatik çalıştır, blast radius'u (deneyin etkileyebileceği kullanıcı ve sistem alanı) küçük tut.

Chaos engineering'in "rastgele bir şeyleri kırmak" olmadığı burada görülüyor: deneyin bir hipotezi, bir ölçüsü ve bir durdurma koşulu var. Hipotez doğrulanırsa sistemin o arızaya dayandığını artık biliyorsun; çürütülürse bir zayıflığı müşteri değil sen buldun. Game day ise bu deneylerin ekipçe, takvime konmuş bir oturumda yapılması: bir senaryo, rollerine yerleşmiş kişiler (bu serinin altıncı yazısındaki IC (incident'ı yöneten kişi), ops lead, comms lead, scribe) ve gerçek araçlar. Game day yalnız sistemi değil, insanları ve süreci de sınıyor: alert çalıyor mu, runbook doğru yere götürüyor mu, kollar çalışıyor mu, iletişim akıyor mu?

Game Day'i Planlamak

Bir game day'in en önemli kısmı, başlamadan önce yazılan bir sayfa. Bookstore'un ilk game day'i ödeme sağlayıcısının hata vermesini prova ediyor ve planı repoda docs/gamedays/2026-11-payment-provider-errors.md:

## Steady state (measured before the start)
- Checkout availability (5m error ratio, `slo:checkout_availability:error_ratio_rate5m`) below 0.5 %.
- Checkout latency: at least 99 % under 800 ms.

## Hypotheses
1. With 10 % of payment calls answered 503 and 5 % delayed by 1 s, checkout fails about 10 % of the time and the
   1h CheckoutErrorBudgetBurn page fires within 5 minutes.
2. The on-call finds the cause with runbook step 3 within 5 minutes.
3. Turning chaos off (Chaos:Enabled=false in switches.json) stops the failures within a minute.

## Injection
`switches.json`: `"Chaos": { "Enabled": true, "FaultRate": 0.1, "LatencyRate": 0.05, "LatencyMs": 1000 }` for 6 minutes,
4 user journeys per second of synthetic traffic. Blast radius: the lab only; real customers never.

## Abort conditions (anyone may call them; the script also checks the first one every 15 s)
- 5m checkout error ratio above 20 %.
- Any page other than CheckoutErrorBudgetBurn.
- Anyone in the room is unsure what is happening.
Abort = `"Chaos": { "Enabled": false }` in switches.json; no restart needed.

Planın dört parçası var ve her biri bir soruya cevap veriyor. Steady state: "normal" neye benziyor, sayıyla? Hipotezler: neyin olacağını düşünüyoruz, yine sayıyla ve süreyle? Enjeksiyon: tam olarak ne yapacağız ve kimi etkileyebilir? Abort koşulları: ne olursa duruyoruz ve durmak ne kadar sürüyor? Abort koşulunun önceden yazılması ve herkesin onu çağırabilmesi kritik; deney sırasında "biraz daha bekleyelim" demek, bir provayı gerçek bir incident'a çevirmenin en kısa yolu. Roller serinin altıncı yazısındakilerle aynı: game day aynı zamanda incident sürecinin provası.

.NET'te Fault Injection: Polly Chaos Strategy'leri

Hatayı nereye enjekte edeceğin, neyi sınayabileceğini belirliyor. Sahte ödeme sağlayıcısının hata ayarları (serinin önceki yazılarında kullandığımız /admin/faults) sağlayıcı tarafını taklit ediyor; production'daki gerçek sağlayıcıya ise böyle bir düğme yok. Polly v8'in chaos strategy'leri (eski adıyla Simmy) hatayı uygulamanın kendi HTTP client'ında üretiyor: AddChaosLatency gecikme, AddChaosFault exception, AddChaosOutcome sahte bir sonuç (ör. 503), AddChaosBehavior ise rastgele bir davranış ekliyor. Bunlar Polly.Core'un içinde, 8.3.0'dan beri var; Bookstore'a Microsoft.Extensions.Http.Resilience 11.0.0 paketini ekledik (Polly.Core 8.4.2'yi getiriyor). Strategy'lerin ayrıntılı mekaniğini ve testlerde kullanımını External Service Integration ve Resilience serisinin resilience'ı test etmek yazısı anlatıyor; burada game day için gereken üç özelliğe odaklanıyoruz:

// Game day'ler için fault injection. Production'da hiç kaydedilmez; diğer ortamlarda Chaos:Enabled açılana
// kadar kapalıdır (switches.json'da; böylece game day restart olmadan başlar ve durdurulur).
bool chaosAllowed = !environment.IsProduction();
if (chaosAllowed)
{
    services.AddOptions<ChaosOptions>()
        .BindConfiguration(ChaosOptions.SectionName)
        .ValidateDataAnnotations()
        .ValidateOnStart();
    services.AddSingleton<LastKnownGood<ChaosOptions>>();
}

client.AddResilienceHandler("payments", (pipeline, context) =>
{
    pipeline.AddRetry(...);   // aşağıda

    if (!chaosAllowed)
    {
        return;
    }
    // Chaos en sonda (en içte): yukarıdaki retry, enjekte edilen hataları gerçekleri gibi görür.
    LastKnownGood<ChaosOptions> chaos = context.ServiceProvider.GetRequiredService<LastKnownGood<ChaosOptions>>();
    pipeline
        .AddChaosLatency(new ChaosLatencyStrategyOptions
        {
            EnabledGenerator = _ => ValueTask.FromResult(chaos.Value.Enabled),
            InjectionRateGenerator = _ => ValueTask.FromResult(chaos.Value.LatencyRate),
            LatencyGenerator = _ => ValueTask.FromResult(TimeSpan.FromMilliseconds(chaos.Value.LatencyMs))
        })
        .AddChaosOutcome(new ChaosOutcomeStrategyOptions<HttpResponseMessage>
        {
            EnabledGenerator = _ => ValueTask.FromResult(chaos.Value.Enabled),
            InjectionRateGenerator = _ => ValueTask.FromResult(chaos.Value.FaultRate),
            OutcomeGenerator = _ => ValueTask.FromResult<Outcome<HttpResponseMessage>?>(
                Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)))
        });
});

Birincisi, chaos Production'da hiç kaydedilmiyor; konfigürasyonda yanlışlıkla Enabled: true kalsa bile production'daki client'ın pipeline'ında chaos strategy'si yok. Integration testi bunu sabitliyor: aynı ayarla Development'ta her ödeme başarısız, Production'da her ödeme geçiyor. İkincisi, açma ve kapama EnabledGenerator ve InjectionRateGenerator ile her çağrıda okunuyor; değerler serinin altıncı yazısındaki switches.json'dan ve sekizinci yazıdaki LastKnownGood üzerinden geliyor, yani game day restart olmadan başlıyor ve abort birkaç saniyede etkili oluyor. Polly'nin varsayılanları burada bilinçli olarak değiştirildi: dokümana göre chaos strategy'leri varsayılan olarak açık (Enabled true) ve InjectionRate 0,001. Üçüncüsü, oran: yüzde 10 hata ve yüzde 5 gecikme, gerçek bir sağlayıcı arızasına benzeyen ama sistemi tamamen durdurmayan bir seviye.

Game Day 1: İki Hipotez Çürüdü

Laboratuvarda dakikada yaklaşık 228 checkout üreten trafik aracını çalıştırdık, üç dakika steady state'i ölçtük ve 23:02:52'de (UTC; eşgüdümlü evrensel saat) switches.json'u değiştirdik. Aynı anda planın abort koşulunu 15 saniyede bir kontrol eden küçük bir döngü çalışıyordu (5 dakikalık hata oranı yüzde 20'yi geçerse chaos'u kapat). Bu ilk koşuda pipeline yalnız iki chaos strategy'sinden oluşuyordu ve adı payments-chaos'tu; aşağıdaki retry'ı game day'in sonucunda ekledik. 23:08:23'te deneyi bitirdik:

# 23:02:52 game day başlıyor
echo '{ "Payments": { "TimeoutSeconds": 3, "CheckoutEnabled": true }, "Chaos": { "Enabled": true, "FaultRate": 0.1, "LatencyRate": 0.05, "LatencyMs": 1000 } }' > switches.json
# 23:08:23 game day bitiyor (abort da aynı satır)
echo '{ "Payments": { "TimeoutSeconds": 3, "CheckoutEnabled": true }, "Chaos": { "Enabled": false, "FaultRate": 0.1, "LatencyRate": 0.05, "LatencyMs": 1000 } }' > switches.json

Trafik aracının dakikalık özeti (arama, sepet ve health sütunları çıkarıldı):

23:02 UTC checkout=228 5xx=0 slowest-checkout=1632ms
23:03 UTC checkout=228 5xx=21 slowest-checkout=1138ms
23:04 UTC checkout=228 5xx=21 slowest-checkout=1516ms
23:07 UTC checkout=229 5xx=21 slowest-checkout=1139ms
23:08 UTC checkout=228 5xx=16 slowest-checkout=1124ms
23:09 UTC checkout=228 5xx=0 slowest-checkout=1514ms
23:10 UTC checkout=228 5xx=0 slowest-checkout=170ms

Checkout'ların yaklaşık yüzde 9'u 502 aldı; abort döngüsünün okuduğu 5 dakikalık oran yüzde 9,2'de dengelendi ve abort koşulu tetiklenmedi. Gecikme enjeksiyonu hata üretmedi (1 saniye, 3 saniyelik timeout'un altında) ama latency SLO'sunu vurdu: 800 ms altında kalan checkout oranı yüzde 95,9'a indi (hedef yüzde 99). Chaos kapandıktan sonraki ilk tam dakikada 5xx sıfır; abort, restart olmadan 30 saniyenin altında etkili oldu. Üçüncü hipotez doğrulandı. Ama ilk ikisi tutmadı.

Birinci hipotez: "1 saatlik page 5 dakikada çalar." Çalmadı; deney boyunca hiç çalmadı. Laboratuvarda yalnız 6 saatlik page ve 3 günlük ticket çaldı, onlar da Prometheus'u deneyden hemen önce sıfırdan başlattığımız için (pencere birkaç dakikalık veriden ibaretti; serinin dördüncü yazısında açıkladık). Production'da ne olurdu? Yüzde 9,3 hata, 30 günlük yüzde 99,5 SLO için 18,6'lık bir burn rate. 1 saatlik pencerenin eşiği 14,4; temiz bir saatin ardından pencerenin ortalaması eşiğe 60 × 14,4 / 18,6 ≈ 46 dakikada ulaşır. Yani Emre'nin "birkaç dakika" tahmini, eşiğin hemen üstündeki bir burn rate için 46 dakika. Bu bir hata değil, multiwindow tasarımının bilinçli sonucu: yüzde 9'luk bir hata oranı bir saat içinde bütçenin ancak yüzde 2,6'sını harcar. Ama bunu ekip game day'de öğrendi, gerçek bir gecede değil; error budget policy'deki "yüzde 10 hata ne kadar sürede page olur?" sorusunun cevabı artık runbook'ta.

İkinci hipotez: "Runbook'un 3. adımı sebebi gösterir." Göstermedi. Deneyin sonunda runbook'un 2. ve 3. adımları ile Polly'nin kendi metric'i:

# 2. adım: checkout cevapları (saniyede)
sum by (http_response_status_code) (rate(http_server_request_duration_seconds_count{job="bookstore-api", http_route="/api/orders/"}[5m]))
{http_response_status_code="201"} 3.4455898555097857
{http_response_status_code="502"} 0.35438347801068065

# 3. adım: dışarıya giden çağrılar
sum by (server_address, http_response_status_code, error_type) (rate(http_client_request_duration_seconds_count{job="bookstore-api"}[5m]))
{http_response_status_code="200", server_address="127.0.0.1"} 3.4455898555097857
{http_response_status_code="200", server_address="localhost"} 0.06666619883369239

# Polly'nin olayları (son 5 dakika)
sum by (event_name, pipeline_name) (increase(resilience_polly_strategy_events_total[5m]))
{event_name="PipelineExecuting", pipeline_name="IPaymentGateway-payments-chaos"} 1139.99200005614
{event_name="Chaos.OnOutcome", pipeline_name="IPaymentGateway-payments-chaos"} 106.31504340320419
{event_name="Chaos.OnLatency", pipeline_name="IPaymentGateway-payments-chaos"} 43.15759187654823

# 800 ms altındaki checkout oranı
{} 0.9593721144967683

Checkout'ların saniyede 0,35'i 502 alıyor, ama ödeme sağlayıcısına (127.0.0.1) giden bütün çağrılar 200. Alert'in açıklaması da aynı şeyi söylüyordu: "Checkout errors (5m): 9.326%. No failing outgoing calls." Runbook nöbetçiyi "Neither" bölümüne, yani "sebep bilinmiyor, secondary'yi çağır" yoluna gönderiyordu. Sebep, metric'in nerede ölçüldüğü: http.client.request.duration isteğin gerçekten ağa çıktığı en içteki (primary) handler'da kaydediliyor ve gözlem de bunu gösteriyor; chaos strategy'si ise 503'ü istek process'ten hiç çıkmadan, resilience pipeline'ında üretiyor. Daha da yanıltıcısı, HttpClient'ın kendi log'u (dıştaki logical handler) "End processing HTTP request after 0.0825ms - 503" yazıyordu; log'a bakan biri sağlayıcının 503 döndüğünü düşünürdü. Tek doğru sinyal Polly'nin Chaos.OnOutcome olayıydı.

Bu bulgu chaos'a özgü değil. Aynı kör nokta, istek process'ten çıkmadan cevap üreten her resilience mekanizması için geçerli: açık bir circuit breaker, bir rate limiter, vazgeçen bir retry. Bookstore ileride bunlardan birini eklediğinde runbook'un 3. adımı aynı şekilde yanıltacaktı. Game day'in ilk geri beslemesi runbook'a girdi:

3. Is it the payment provider? Bookstore.Api's calls to it, by host and result:
   ```promql
   sum by (server_address, http_response_status_code, error_type) (rate(http_client_request_duration_seconds_count{job="bookstore-api"}[5m]))
   ```
   503, or `error_type="System.Threading.Tasks.TaskCanceledException"` (timeout; such series have no status code)
   from the provider's host: go to "Payment provider".
   No failing outgoing calls, but checkouts fail? Our own resilience pipeline may be answering before the request
   leaves the process (a chaos experiment left on, a retry giving up, later a circuit breaker). Polly's events:
   ```promql
   sum by (event_name, pipeline_name) (rate(resilience_polly_strategy_events_total{job="bookstore-api"}[5m]))
   ```
   `Chaos.*` events outside a game day: set `Chaos:Enabled` to false in switches.json, then go on.

Sonuçları Geri Beslemek

Bir game day'in değeri, bulduklarının sisteme geri dönmesinde. İlk geri besleme runbook'tu. İkincisi koddu: ödeme sağlayıcısının arada bir 503 dönmesi gerçek hayatta da olağan ve serinin ikinci yazısından beri Payments module'ünde bilinçli olarak retry yoktu (her sağlayıcı hatası SLI'da (servisin kullanıcı açısından ne kadar iyi çalıştığını ölçen oran) görünsün diye). Game day bu kararın bedelini ölçtü: yüzde 10 sağlayıcı hatası, yüzde 9'un üzerinde checkout hatası demek. Idempotency-Key header'ı zaten gönderildiği için ikinci bir deneme güvenli; pipeline'a, chaos'un dışına bir retry ekledik:

// Game day 2026-11-14: 503 ya da kopan bağlantıdan sonra bir retry (Idempotency-Key header'ı ikinci denemeyi
// güvenli yapar). Timeout'lar retry edilmez: çağrı başına timeout zaten iki denemeyi birlikte kapsıyor.
pipeline.AddRetry(new HttpRetryStrategyOptions
{
    MaxRetryAttempts = 1,
    Delay = TimeSpan.FromMilliseconds(200),
    BackoffType = DelayBackoffType.Constant,
    UseJitter = true,
    ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
        .Handle<HttpRequestException>()
        .HandleResult(response => response.StatusCode == HttpStatusCode.ServiceUnavailable)
});

Yeni bir integration testi, sağlayıcının ilk çağrıya 503, ikinciye 200 döndüğü durumda ödemenin geçtiğini ve iki çağrının aynı Idempotency-Key ile gittiğini doğruluyor; testler 19'da 19 geçti. Aynı game day'i aynı ayarlarla tekrar oynadık (23:16–23:21):

# 2. adım: checkout cevapları (saniyede)
{http_response_status_code="201"} 3.7683946077571386
{http_response_status_code="502"} 0.03508747307036442

# Polly'nin olayları (son 5 dakika)
{event_name="PipelineExecuting", pipeline_name="IPaymentGateway-payments"} 1139.99200005614
{event_name="Chaos.OnOutcome", pipeline_name="IPaymentGateway-payments"} 133.68327239808843
{event_name="OnRetry", pipeline_name="IPaymentGateway-payments"} 123.1570304769791
{event_name="Chaos.OnLatency", pipeline_name="IPaymentGateway-payments"} 70.52582087143247

# 800 ms altındaki checkout oranı
{} 0.9345018450184501

Chaos 5 dakikada 134 kez 503 üretti; retry 123 kez devreye girdi ve checkout hata oranı yüzde 9,3'ten yüzde 0,9'a indi (saniyede 0,035 / 3,8). Retry'ın kurtaramadığı yaklaşık 11 checkout, iki denemenin ikisine de 503 düşen çağrılar; yüzde 10'un karesi, beklenen yüzde 1. Burn rate 18,6'dan 1,8'e indi; ikinci koşuda yeni bir page çalmadı (ilk koşudan kalan 6 saatlik page 23:18'de sustu). Ama retry'ın da bedeli var: 800 ms altında kalan checkout oranı yüzde 95,9'dan yüzde 93,5'e düştü, çünkü her deneme gecikme enjeksiyonuna ayrıca maruz kalıyor ve araya 200 ms bekleme giriyor. Retry availability'yi latency ile takas etti; latency SLO'su artık daha hızlı yanıyor. Bu, retry'ın doğru sayısını ve zamanlamasını seçmenin (System Design serisinin reliability pattern'leri yazısı) neden ölçerek yapılması gerektiğini gösteriyor.

Game day'in plan dosyası sonuçlarla ve action item'larla kapandı; serinin yedinci yazısındaki dört sınıf burada da kullanılıyor:

## Action items
| ID | Action | Class | Owner | Due | Ticket | Status |
|---|---|---|---|---|---|---|
| GD-1 | Runbook step 3: look at Polly's resilience events when outgoing calls look healthy | detect | Emre | 2026-11-15 | BOOK-421 | done |
| GD-2 | One retry for a 503 from the provider (Idempotency-Key makes it safe) | mitigate | Ayse | 2026-11-15 | BOOK-422 | done |
| GD-3 | Error budget policy: write down how long a 10 % error rate takes to page (about 46 minutes) and decide if that is acceptable | process | Selin | 2026-11-28 | BOOK-423 | open |
| GD-4 | Repeat this game day in three months with a circuit breaker in the pipeline | process | Ayse | 2027-02-15 | BOOK-424 | open |

Üçüncü action item (GD-3) bir kod işi değil, bir karar: yüzde 10'luk bir hata oranının 46 dakika page üretmemesi Bookstore için kabul edilebilir mi? Kabul edilmezse çözüm daha hassas bir alert değil (serinin dördüncü yazısı bunun gürültü bedelini anlattı), daha hızlı bir mitigation; örneğin retry'ın kendisi. Game day bu soruyu bir varsayım olmaktan çıkarıp ölçülmüş bir sayıya çevirdi.

Altyapı Seviyesinde Provalar

Polly uygulamanın içinden bir bağımlılığın arızasını taklit ediyor; bazı arızalar ise uygulamanın dışında. Kubernetes'te bir pod'u silmek ya da bir node'u boşaltmak, readiness probe'larının, graceful shutdown'ın ve PodDisruptionBudget'ın gerçekten çalışıp çalışmadığını gösterir (Kubernetes serisinin health probe ve graceful shutdown yazıları). Bir pod'un CPU ya da bellek sınırını düşürmek, thread pool'un ve GC'nin (garbage collector; kullanılmayan belleği geri toplayan mekanizma) baskı altındaki davranışını gösterir. Bir bağımlılığın DNS kaydını ya da ağ erişimini laboratuvarda kesmek, timeout'ların ve hata mesajlarının gerçekten beklendiği gibi olup olmadığını gösterir. Bunlar için hazır araçlar var, ama aracın kendisi önemli değil: her biri aynı plan sayfasıyla (steady state, hipotez, blast radius, abort) yapılmalı.

Serinin dokuzuncu yazısındaki restore drill de bir game day türü: hipotez "yedekten 30 dakikada dönebiliriz", enjeksiyon "veritabanını kaybettik", ölçüm restore süresi. Break-glass erişim ve bağımlılık haritası da aynı şekilde provadan geçirilmeli. Google'ın SRE (Site Reliability Engineering; güvenilirliği yazılım mühendisliğiyle yönetme disiplini) kitabında anlatılan "Wheel of Misfortune" ise en ucuz türü: eski bir postmortem, bir kişinin nöbetçi rolünde olduğu bir masa başı oyununa dönüşüyor.

Ne Zaman Yapılmaz?

Chaos engineering olgunluk ister. SLO'ların ve alert'lerin yokken hata enjekte etmek, sonucu ölçemeyeceğin bir deney: steady state'i tanımlayamıyorsan hipotezi de doğrulayamazsın. Bu yüzden bu yazı serinin sonunda; önceki yazılar SLI, SLO, alert, runbook ve incident sürecini kurdu. Runbook'u olmayan bir alert'i chaos ile sınamak, yalnız "evet, alert çaldı ve kimse ne yapacağını bilmedi" sonucunu verir.

Production'da onaysız ve abort koşulsuz chaos ise hiç yapılmaz. Principles of Chaos Engineering metni deneylerin sonunda production'da, gerçek trafikle yapılmasını öneriyor; ama oraya küçük blast radius'la, önce staging'de kanıtlanmış deneylerle, iş tarafının (Bookstore'da Selin) onayıyla ve error budget'ın izin verdiği dönemlerde gidilir. Error budget'ı tükenmiş bir servis (serinin üçüncü yazısındaki policy'ye göre) zaten yalnız güvenilirlik işi yapıyor; orada chaos deneyi ancak o güvenilirlik işini doğrulamak için yapılır. Polly'nin dokümanı da production'da oranı ve hedefi (belirli kullanıcılar, belirli tenant'lar) InjectionRateGenerator ve EnabledGenerator ile daraltmayı öneriyor.

Üretimde Dikkat

Chaos kodu da production koduna gidiyor. Bookstore'da Production'da kaydedilmiyor ve bunu bir test sabitliyor; daha sıkı bir seçenek, chaos paketini ayrı bir build konfigürasyonuna almak. Hangisi olursa olsun, "chaos production'da kapalı" iddiasını bir testle kanıtla.

Game day'i takvime koy ve sonuçlarını izle. Tek seferlik bir game day bir gösteridir; üç ayda bir tekrarlanan ve her seferinde bir önceki seferin action item'larını doğrulayan bir game day bir süreçtir.

Enjeksiyonu telemetry'de görünür kıl. Polly'nin chaos olayları kendi metric'inde sayılıyor; bunu dashboard'a ve runbook'a eklemezsen, bir sonraki game day'de ya da unutulmuş bir Enabled: true durumunda sebebi yine yanlış yerde ararsın.

Sık Düşülen Tuzaklar

Hipotezsiz chaos. "Bir şeyleri kıralım, bakalım ne olacak" bir deney değil; ölçmediğin şeyden öğrenemezsin.

Abort'u düşünmemek. Durdurmanın bir deploy gerektirdiği bir chaos kurulumu, provayı gerçek bir kesintiye çevirebilir. Abort bir ayar değişikliği olmalı ve süresi ölçülmeli.

Sadece "sistem dayandı mı?" diye bakmak. Bizim deneyimizde asıl bulgu sistemle değil, runbook'un ve metric'lerin neyi göstermediğiyle ilgiliydi. İnsanları ve araçları da gözle.

Retry'ı chaos'un dışına koymak. Chaos strategy'si pipeline'da retry'dan önce (dışta) olursa, retry enjekte edilen hatayı hiç görmez ve deney gerçek bir arızayı temsil etmez. Polly'nin dokümanı da chaos'u pipeline'ın son (en içteki) strategy'si yapmayı öneriyor.

Küçük Bir Deneme

★☆☆ Kendi servisin için bir game day plan sayfası yaz: steady state'i bir SLI ile, hipotezi bir sayı ve süreyle, abort koşulunu ise herkesin anlayacağı tek bir cümleyle tanımla.

★★☆ Bookstore'un laboratuvarında LatencyRate'i 0,2 ve LatencyMs'yi 4000 yap. Ödeme timeout'u 3 saniye olduğu için gecikmeler timeout'a dönüşecek; retry bunları yakalıyor mu? Neden? Runbook'un 3. adımı bu kez ne gösteriyor?

★★★ EnabledGenerator'ı, yalnız belirli bir müşteri e-postasıyla (ör. gameday@example.com) verilen siparişlerde chaos'u açacak şekilde değiştir (ipucu: ResilienceContext'e bir property koy). Böylece blast radius bir test müşterisiyle sınırlı kalır; trafiğin geri kalanının SLO'sunu etkilemediğini Prometheus'ta göster.

Bu seri SRE'nin ne olduğuyla başladı ve buraya geldi: kullanıcı yolculuğundan SLI'a, SLO ve error budget'a, burn rate alert'lerine, on-call ve runbook'a, incident yönetimine, postmortem'e, başkalarının büyük kesintilerine ve sonunda kasıtlı olarak kırılan bir ödeme client'ına. Hepsinin ortak noktası, güvenilirliğin ölçülebilir, planlanabilir ve prova edilebilir bir mühendislik işi olması. Bookstore ekibi için bir sonraki adım, bu game day'i üç ay sonra tekrar yapıp action item'ların gerçekten kapandığını görmek.