Sdílet prostřednictvím


Indukce řízeného chaosu v clusterech Service Fabric

Rozsáhlé distribuované systémy, jako jsou cloudové infrastruktury, jsou ze své podstaty nespolehlivé. Azure Service Fabric umožňuje vývojářům psát spolehlivé distribuované služby nad nespolehlivou infrastrukturou. Aby mohli vývojáři psát robustní distribuované služby nad nespolehlivým infrastrukturou, musí být schopni otestovat stabilitu svých služeb, zatímco základní nespolehlivé infrastruktury prochází složitými přechody stavu kvůli chybám.

Služba injektáže chyb a služba Cluster Analysis Service (označovaná také jako Služba analýzy chyb) poskytuje vývojářům možnost vyvolat chyby k otestování služeb. Tyto cílené simulované chyby, jako je restartování oddílu, vám můžou pomoct s nejběžnějšími přechody stavu. Cílené simulované chyby jsou však zkreslené definicí, a proto mohou chybět, které se zobrazují pouze v těžko předvídatelné, dlouhé a složité sekvenci přechodů stavu. Pro nestranné testování můžete použít Chaos.

Chaos simuluje pravidelné prokládání chyb (graceful i nevrácené) v průběhu delších časových období. Elegantní chyba se skládá ze sady volání rozhraní Service Fabric API, například selhání restartování repliky je elegantně chybná, protože se jedná o závěr následovaný otevřením na replice. Odebrat repliku, přesunout primární repliku, přesunout sekundární repliku a přesunout instanci jsou další elegantní chyby, které chaos vykonává. Nevracené chyby jsou ukončení procesu, jako je restartování uzlu a restartování balíčku kódu.

Jakmile nakonfigurujete chaos s rychlostí a druhem chyb, můžete začít chaos prostřednictvím C#, PowerShellu nebo rozhraní REST API a začít generovat chyby v clusteru a ve vašich službách. Chaos můžete nakonfigurovat tak, aby běžel po zadaném časovém období (například po jedné hodině), po kterém se Chaos zastaví automaticky, nebo můžete kdykoli zastavovat rozhraní API StopChaos (C#, PowerShell nebo REST).

Poznámka:

Ve své současné podobě chaos indukuje pouze bezpečné chyby, což znamená, že v případě absence externích chyb ztráta kvora nebo ke ztrátě dat nikdy nedojde.

Zatímco chaos běží, vytváří různé události, které zachytí stav spuštění v tuto chvíli. Například ExecutingFaultsEvent obsahuje všechny chyby, které chaos rozhodl provést v této iteraci. Událost ValidationFailedEvent obsahuje podrobnosti o selhání ověření (problémy se stavem nebo stabilitou), které byly nalezeny během ověřování clusteru. Můžete vyvolat rozhraní GetChaosReport API (C#, PowerShell nebo REST), abyste získali sestavu spuštění Chaosu. Tyto události se uchovávají ve spolehlivém slovníku, který má zásady zkrácení diktované dvěma konfiguracemi: MaxStoredChaosEventCount (výchozí hodnota je 25000) a StoredActionCleanupIntervalInSeconds (výchozí hodnota je 3600). Všechny StoredActionCleanupIntervalInSeconds Chaos zkontrolují a všechny kromě nejnovějších událostí MaxStoredChaosEventCount se vymažou ze spolehlivého slovníku.

Chyby vyvolané chaosem

Chaos generuje chyby v celém clusteru Service Fabric a komprimuje chyby, které se zobrazují v měsících nebo letech, do několika hodin. Kombinace prokládání chyb s vysokou rychlostí selhání najde rohové případy, které by jinak mohly chybět. Toto cvičení chaosu vede k významnému zlepšení kvality kódu služby.

Chaos vyvolává chyby z následujících kategorií:

  • Restartování uzlu
  • Restartování nasazeného balíčku kódu
  • Odebrání repliky
  • Restartování repliky
  • Přesunutí primární repliky (konfigurovatelné)
  • Přesunutí sekundární repliky (konfigurovatelné)
  • Přesunutí instance

Chaos běží ve více iteracích. Každá iterace se skládá z chyb a ověření clusteru pro zadané období. Můžete nakonfigurovat dobu strávenou clusterem, aby se stabilizuje a aby ověření proběhlo úspěšně. Pokud se v ověření clusteru najde chyba, chaos vygeneruje a zachová OvěřovacíFailedEvent s časovým razítkem UTC a podrobnostmi o selhání. Představte si například instanci Chaosu, která je nastavená tak, aby běžela po dobu jedné hodiny s maximálně třemi souběžnými chybami. Chaos indukuje tři chyby a pak ověří stav clusteru. Iteruje předchozí krok, dokud se explicitně nezastaví prostřednictvím rozhraní STOPChaosAsync API nebo jednohodinových průchodů. Pokud cluster není v pořádku v jakékoli iteraci (to znamená, že se ne stabilizuje nebo se v předaném MaxCluster StabilizTimeout přestane v pořádku), Chaos vygeneruje ValidationFailedEvent. Tato událost značí, že se něco nepovedlo a může vyžadovat další šetření.

K získání chyb vyvolaných chaosem můžete použít rozhraní GetChaosReport API (PowerShell, C#nebo REST). Rozhraní API získá další segment sestavy Chaos na základě předávaného tokenu pokračování nebo předaného časového rozsahu. Můžete zadat pokračováníToken získat další segment chaos sestavy, nebo můžete zadat časový rozsah prostřednictvím StartTimeUtc a EndTimeUtc, ale nemůžete zadat jak ContinuationToken, tak časový rozsah ve stejném volání. Pokud existuje více než 100 událostí chaosu, vrátí se sestava Chaos v segmentech, kde segment neobsahuje více než 100 událostí chaosu.

Důležité možnosti konfigurace

  • TimeToRun: Celkový čas, který chaos běží před dokončením s úspěchem. Chaos můžete zastavit před spuštěním období TimeToRun prostřednictvím rozhraní API StopChaos.

  • MaxClusterStabilizationTimeout: Maximální doba čekání na to, aby cluster byl v pořádku před vytvořením hodnoty ValidationFailedEvent. Toto čekání je snížit zatížení clusteru při obnovování. Provedené kontroly:

    • Pokud je stav clusteru v pořádku
    • Pokud je stav služby v pořádku
    • Pokud se pro oddíl služby dosáhne velikosti sady cílových replik.
    • Že neexistují žádné repliky inbuildu
  • MaxConcurrentFaults: Maximální počet souběžných chyb, které jsou vyvolány v každé iteraci. Čím vyšší je číslo, tím agresivnější chaos je a převzetí služeb při selhání a kombinace přechodů stavu, kterými cluster prochází, jsou také složitější.

Poznámka:

Bez ohledu na to, jak vysoká hodnota MaxConcurrentFaults má, chaos zaručuje - v nepřítomnosti externích chyb - neexistuje žádná ztráta kvora ani ztráta dat.

  • EnableMoveReplicaFaults: Povolí nebo zakáže chyby, které způsobují přesunutí primárních, sekundárních replik nebo instancí. Tyto chyby jsou ve výchozím nastavení povolené.
  • WaitTimeBetweenIterations: Doba čekání mezi iteracemi. To znamená, že doba, po které se chaos pozastaví po spuštění kola chyb a dokončení odpovídajícího ověření stavu clusteru. Čím vyšší je hodnota, tím nižší je průměrná míra injektáže selhání.
  • WaitTimeBetweenFaults: Doba čekání mezi dvěma po sobě jdoucími chybami v jedné iteraci. Čím vyšší je hodnota, tím nižší souběžnost (nebo překrytí mezi) chybami.
  • ClusterHealthPolicy: Zásady stavu clusteru slouží k ověření stavu clusteru mezi iteracemi chaosu. Pokud je stav clusteru chybný nebo pokud dojde k neočekávané výjimce během provádění chyb, chaos počká 30 minut před další kontrolou stavu – aby clusteru poskytl nějaký čas na rekuperaci.
  • Kontext: Kolekce párů klíč-hodnota typu (řetězec, řetězec). Mapu lze použít k zaznamenání informací o spuštění chaosu. Nemůže existovat více než 100 takových párů a každý řetězec (klíč nebo hodnota) může mít maximálně 4095 znaků. Tato mapa je nastavena počátečním spuštěním chaosu a volitelně uloží kontext o konkrétním spuštění.
  • ChaosTargetFilter: Tento filtr lze použít k cílení chyb Chaosu pouze na určité typy uzlů nebo pouze na určité instance aplikace. Pokud se chaosTargetFilter nepoužívá, chaos chybuje všechny entity clusteru. Pokud se používá ChaosTargetFilter, chaos chybuje pouze entity, které splňují specifikaci ChaosTargetFilter. NodeTypeInclusionList a ApplicationInclusionList umožňují sémantika sjednocení pouze. Jinými slovy, není možné zadat průnik NodeTypeInclusionList a ApplicationInclusionList. Například není možné zadat "chyba této aplikace pouze v případě, že je v daném typu uzlu". Jakmile je entita zahrnuta v NodeTypeInclusionList nebo ApplicationInclusionList, nelze tuto entitu vyloučit pomocí ChaosTargetFilter. I když se applicationX nezobrazí v ApplicationInclusionList, v některých Chaos iteration applicationX může být chybný, protože se stane na uzlu nodeTypeY, který je součástí NodeTypeInclusionList. Pokud nodeTypeInclusionList a ApplicationInclusionList jsou null nebo prázdné, je vyvolána výjimka ArgumentException.
    • NodeTypeInclusionList: Seznam typů uzlů, které se mají zahrnout do chyb Chaosu. Pro uzly těchto typů uzlů jsou povolené všechny typy chyb (restartování uzlu, restartování balíčku kódu, odebrání repliky, restartování repliky, přesunutí primární, sekundární instance a instance přesunutí). Pokud se nodetype (řekněme NodeTypeX) nezobrazuje v NodeTypeInclusionList, chyby na úrovni uzlů (například NodeRestart) se pro uzly NodeTypeX nikdy nepovolí, ale pro NodeTypeX může být povolena chyba balíčku kódu a repliky, pokud se aplikace v ApplicationInclusionListu nachází v uzlu NodeTypeX. Do tohoto seznamu lze zahrnout maximálně 100 názvů typů uzlů, aby se zvýšilo toto číslo, je pro konfiguraci MaxNumberOfNodeTypesInChaosTargetFilter vyžadován upgrade konfigurace.
    • ApplicationInclusionList: Seznam identifikátorů URI aplikací, které se mají zahrnout do chyb chaosu. Všechny repliky, které patří ke službám těchto aplikací, se dají přizpůsobit chybám repliky (restartování repliky, odebrání repliky, přesunutí primární, sekundární a přesun instance) pomocí Chaosu. Chaos může restartovat balíček kódu pouze v případě, že balíček kódu hostuje pouze repliky těchto aplikací. Pokud se aplikace v tomto seznamu nezobrazí, může být v některých iteraci chaosu chybná, pokud aplikace skončí na uzlu typu uzlu, který je součástí NodeTypeInclusionList. Pokud je však applicationX vázán na nodeTypeY prostřednictvím omezení umístění a applicationX chybí v ApplicationInclusionList a nodeTypeY chybí nodeTypeInclusionList, applicationX nebude nikdy chybný. Do tohoto seznamu je možné zahrnout maximálně 1000 názvů aplikací, aby se zvýšilo toto číslo, pro konfiguraci MaxNumberOfApplicationsInChaosTargetFilter se vyžaduje upgrade konfigurace Konfigurace MaxNumberOfApplicationsInChaosTargetFilter.

Jak spustit Chaos

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Fabric;

using System.Diagnostics;
using System.Fabric.Chaos.DataStructures;

static class Program
{
    private class ChaosEventComparer : IEqualityComparer<ChaosEvent>
    {
        public bool Equals(ChaosEvent x, ChaosEvent y)
        {
            return x.TimeStampUtc.Equals(y.TimeStampUtc);
        }
        public int GetHashCode(ChaosEvent obj)
        {
            return obj.TimeStampUtc.GetHashCode();
        }
    }

    static async Task Main(string[] args)
    {
        var clusterConnectionString = "localhost:19000";
        using (var client = new FabricClient(clusterConnectionString))
        {
            var startTimeUtc = DateTime.UtcNow;

            // The maximum amount of time to wait for all cluster entities to become stable and healthy. 
            // Chaos executes in iterations and at the start of each iteration it validates the health of cluster
            // entities. 
            // During validation if a cluster entity is not stable and healthy within
            // MaxClusterStabilizationTimeoutInSeconds, Chaos generates a validation failed event.
            var maxClusterStabilizationTimeout = TimeSpan.FromSeconds(30.0);

            var timeToRun = TimeSpan.FromMinutes(60.0);

            // MaxConcurrentFaults is the maximum number of concurrent faults induced per iteration. 
            // Chaos executes in iterations and two consecutive iterations are separated by a validation phase.
            // The higher the concurrency, the more aggressive the injection of faults -- inducing more complex
            // series of states to uncover bugs.
            // The recommendation is to start with a value of 2 or 3 and to exercise caution while moving up.
            var maxConcurrentFaults = 3;

            // Describes a map, which is a collection of (string, string) type key-value pairs. The map can be
            // used to record information about the Chaos run. There cannot be more than 100 such pairs and
            // each string (key or value) can be at most 4095 characters long.
            // This map is set by the starter of the Chaos run to optionally store the context about the specific run.
            var startContext = new Dictionary<string, string>{{"ReasonForStart", "Testing"}};

            // Time-separation (in seconds) between two consecutive iterations of Chaos. The larger the value, the
            // lower the fault injection rate.
            var waitTimeBetweenIterations = TimeSpan.FromSeconds(10);

            // Wait time (in seconds) between consecutive faults within a single iteration.
            // The larger the value, the lower the overlapping between faults and the simpler the sequence of
            // state transitions that the cluster goes through. 
            // The recommendation is to start with a value between 1 and 5 and exercise caution while moving up.
            var waitTimeBetweenFaults = TimeSpan.Zero;

            // Passed-in cluster health policy is used to validate health of the cluster in between Chaos iterations. 
            var clusterHealthPolicy = new ClusterHealthPolicy
            {
                ConsiderWarningAsError = false,
                MaxPercentUnhealthyApplications = 100,
                MaxPercentUnhealthyNodes = 100
            };

            // All types of faults, restart node, restart code package, restart replica, move primary
            // replica, move secondary replica, and move instance will happen for nodes of type 'FrontEndType'
            var nodetypeInclusionList = new List<string> { "FrontEndType"};

            // In addition to the faults included by nodetypeInclusionList,
            // restart code package, restart replica, move primary replica, move secondary replica,
            //  and move instance faults will happen for 'fabric:/TestApp2' even if a replica or code
            // package from 'fabric:/TestApp2' is residing on a node which is not of type included
            // in nodeypeInclusionList.
            var applicationInclusionList = new List<string> { "fabric:/TestApp2" };

            // List of cluster entities to target for Chaos faults.
            var chaosTargetFilter = new ChaosTargetFilter
            {
                NodeTypeInclusionList = nodetypeInclusionList,
                ApplicationInclusionList = applicationInclusionList
            };

            var parameters = new ChaosParameters(
                maxClusterStabilizationTimeout,
                maxConcurrentFaults,
                true, /* EnableMoveReplicaFault */
                timeToRun,
                startContext,
                waitTimeBetweenIterations,
                waitTimeBetweenFaults,
                clusterHealthPolicy) {ChaosTargetFilter = chaosTargetFilter};

            try
            {
                await client.TestManager.StartChaosAsync(parameters);
            }
            catch (FabricChaosAlreadyRunningException)
            {
                Console.WriteLine("An instance of Chaos is already running in the cluster.");
            }

            var filter = new ChaosReportFilter(startTimeUtc, DateTime.MaxValue);

            var eventSet = new HashSet<ChaosEvent>(new ChaosEventComparer());

            string continuationToken = null;

            while (true)
            {
                ChaosReport report;
                try
                {
                    report = string.IsNullOrEmpty(continuationToken)
                        ? await client.TestManager.GetChaosReportAsync(filter)
                        : await client.TestManager.GetChaosReportAsync(continuationToken);
                }
                catch (Exception e)
                {
                    if (e is FabricTransientException)
                    {
                        Console.WriteLine("A transient exception happened: '{0}'", e);
                    }
                    else if(e is TimeoutException)
                    {
                        Console.WriteLine("A timeout exception happened: '{0}'", e);
                    }
                    else
                    {
                        throw;
                    }

                    await Task.Delay(TimeSpan.FromSeconds(1.0));
                    continue;
                }

                continuationToken = report.ContinuationToken;

                foreach (var chaosEvent in report.History)
                {
                    if (eventSet.Add(chaosEvent))
                    {
                        Console.WriteLine(chaosEvent);
                    }
                }

                // When Chaos stops, a StoppedEvent is created.
                // If a StoppedEvent is found, exit the loop.
                var lastEvent = report.History.LastOrDefault();

                if (lastEvent is StoppedEvent)
                {
                    break;
                }

                await Task.Delay(TimeSpan.FromSeconds(1.0));
            }
        }
    }
}
$clusterConnectionString = "localhost:19000"
$timeToRunMinute = 60

# The maximum amount of time to wait for all cluster entities to become stable and healthy.
# Chaos executes in iterations and at the start of each iteration it validates the health of cluster entities.
# During validation if a cluster entity is not stable and healthy within MaxClusterStabilizationTimeoutInSeconds,
# Chaos generates a validation failed event.
$maxClusterStabilizationTimeSecs = 30

# MaxConcurrentFaults is the maximum number of concurrent faults induced per iteration.
# Chaos executes in iterations and two consecutive iterations are separated by a validation phase.
# The higher the concurrency, the more aggressive the injection of faults -- inducing more complex series of
# states to uncover bugs.
# The recommendation is to start with a value of 2 or 3 and to exercise caution while moving up.
$maxConcurrentFaults = 3

# Time-separation (in seconds) between two consecutive iterations of Chaos. The larger the value, the lower the
# fault injection rate.
$waitTimeBetweenIterationsSec = 10

# Wait time (in seconds) between consecutive faults within a single iteration.
# The larger the value, the lower the overlapping between faults and the simpler the sequence of state
# transitions that the cluster goes through.
# The recommendation is to start with a value between 1 and 5 and exercise caution while moving up.
$waitTimeBetweenFaultsSec = 0

# Passed-in cluster health policy is used to validate health of the cluster in between Chaos iterations. 
$clusterHealthPolicy = new-object -TypeName System.Fabric.Health.ClusterHealthPolicy
$clusterHealthPolicy.MaxPercentUnhealthyNodes = 100
$clusterHealthPolicy.MaxPercentUnhealthyApplications = 100
$clusterHealthPolicy.ConsiderWarningAsError = $False

# Describes a map, which is a collection of (string, string) type key-value pairs. The map can be used to record
# information about the Chaos run.
# There cannot be more than 100 such pairs and each string (key or value) can be at most 4095 characters long.
# This map is set by the starter of the Chaos run to optionally store the context about the specific run.
$context = @{"ReasonForStart" = "Testing"}

#List of cluster entities to target for Chaos faults.
$chaosTargetFilter = new-object -TypeName System.Fabric.Chaos.DataStructures.ChaosTargetFilter
$chaosTargetFilter.NodeTypeInclusionList = new-object -TypeName "System.Collections.Generic.List[String]"

# All types of faults, restart node, restart code package, restart replica, move primary replica, and move
# secondary replica will happen for nodes of type 'FrontEndType'
$chaosTargetFilter.NodeTypeInclusionList.AddRange( [string[]]@("FrontEndType") )
$chaosTargetFilter.ApplicationInclusionList = new-object -TypeName "System.Collections.Generic.List[String]"

# In addition to the faults included by nodetypeInclusionList, 
# restart code package, restart replica, move primary replica, move secondary replica faults will happen for
# 'fabric:/TestApp2' even if a replica or code package from 'fabric:/TestApp2' is residing on a node which is
# not of type included in nodeypeInclusionList.
$chaosTargetFilter.ApplicationInclusionList.Add("fabric:/TestApp2")

Connect-ServiceFabricCluster $clusterConnectionString

$events = @{}
$now = [System.DateTime]::UtcNow

Start-ServiceFabricChaos -TimeToRunMinute $timeToRunMinute -MaxConcurrentFaults $maxConcurrentFaults -MaxClusterStabilizationTimeoutSec $maxClusterStabilizationTimeSecs -EnableMoveReplicaFaults -WaitTimeBetweenIterationsSec $waitTimeBetweenIterationsSec -WaitTimeBetweenFaultsSec $waitTimeBetweenFaultsSec -ClusterHealthPolicy $clusterHealthPolicy -ChaosTargetFilter $chaosTargetFilter -Context $context

while($true)
{
    $stopped = $false
    $report = Get-ServiceFabricChaosReport -StartTimeUtc $now -EndTimeUtc ([System.DateTime]::MaxValue)

    foreach ($e in $report.History) {

        if(-Not ($events.Contains($e.TimeStampUtc.Ticks)))
        {
            $events.Add($e.TimeStampUtc.Ticks, $e)
            if($e -is [System.Fabric.Chaos.DataStructures.ValidationFailedEvent])
            {
                Write-Host -BackgroundColor White -ForegroundColor Red $e
            }
            else
            {
                Write-Host $e
                # When Chaos stops, a StoppedEvent is created.
                # If a StoppedEvent is found, exit the loop.
                if($e -is [System.Fabric.Chaos.DataStructures.StoppedEvent])
                {
                    return
                }
            }
        }
    }

    Start-Sleep -Seconds 1
}