Vyvolání ří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 na nespolehlivé infrastruktuře. Aby vývojáři mohli napsat robustní distribuované služby na nespolehlivé infrastruktuře, musí být schopni otestovat stabilitu svých služeb, zatímco základní nespolehlá infrastruktura 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) umožňuje vývojářům indukovat chyby, aby mohli otestovat své služby. 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 z definice neobjektivní, a proto můžou chybět chyby, které se zobrazují pouze v obtížně předvídatelných, dlouhých a komplikovaných sekvencích přechodů stavu. Pro nestranné testování můžete použít Chaos.

Chaos simuluje periodické prokládané chyby (řádné i nevracené) v celém clusteru po delší dobu. Řádné selhání se skládá ze sady volání rozhraní API Service Fabric, například selhání restartování repliky je nenákladná chyba, protože se jedná o uzavření následované otevřením repliky. Odebrání repliky, přesunutí primární repliky, přesunutí sekundární repliky a přesunutí instance jsou další řádné chyby, které chaos vykonává. Chyby, které jsou nevýkonné, jsou ukončení procesů, jako je restartování uzlu a restartování balíčku kódu.

Jakmile nakonfigurujete Chaos s rychlostí a druhem chyb, můžete spustit 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 určité časové období (například na jednu hodinu), po kterém se chaos automaticky zastaví, nebo můžete kdykoli volat rozhraní STOPChaos API (C#, PowerShell nebo REST).

Poznámka

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

Zatímco je chaos spuštěný, vytváří různé události, které zachycují stav spuštění v daném okamžiku. Například ExecutingFaultsEvent obsahuje všechny chyby, které se Chaos rozhodl provést v této iteraci. ValidationFailedEvent obsahuje podrobnosti o selhání ověřování (problémy se stavem nebo stabilitou), které se zjistilo během ověřování clusteru. Pokud chcete získat sestavu spuštění Chaosu, můžete vyvolat rozhraní API GetChaosReport (C#, PowerShell nebo REST). Tyto události jsou trvalé ve spolehlivém slovníku, který má zásady zkrácení, které jsou diktovány dvěma konfiguracemi: MaxStoredChaosEventCount (výchozí hodnota je 25000) a StoredActionCleanupIntervalInSeconds (výchozí hodnota je 3600). Ze spolehlivého slovníku se vymažou všechny kontroly chaosu StoredActionCleanupIntervalInSeconds a všechny kromě nejnovějších událostí MaxStoredChaosEventCount .

Chyby vyvolané v chaosu

Chaos generuje chyby v celém clusteru Service Fabric a komprimuje chyby, ke kterým dochází v měsících nebo letech, do několika hodin. Kombinace prokládaných chyb s vysokou chybovou frekvencí najde rohové případy, které by jinak mohly být vynechány. Toto cvičení chaosu vede k výrazné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ěží v několika iteracích. Každá iterace se skládá z chyb a ověření clusteru pro zadané období. Můžete nakonfigurovat čas strávený stabilizací clusteru a úspěšné ověření. Pokud se při ověřování clusteru zjistí selhání, Chaos vygeneruje a zachovají ValidationFailedEvent 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 hodinu s maximálně třemi souběžnými chybami. Chaos vyvolá tři chyby a pak ověří stav clusteru. Iteruje předchozí krok, dokud se explicitně nezastaví prostřednictvím rozhraní API StopChaosAsync nebo hodinové průchody. Pokud cluster přestane být v pořádku při jakékoli iteraci (to znamená, že se nestabiluje nebo se v rámci předávané hodnoty MaxClusterStabilizationTimeout nestane v pořádku), vygeneruje chaos ValidationFailedEvent. Tato událost značí, že došlo k nějaké chybě a může být potřeba další šetření.

Pokud chcete zjistit, které chyby chaos vyvolal, můžete použít rozhraní API GetChaosReport (PowerShell, C# nebo REST). Rozhraní API získá další segment sestavy Chaos na základě předávaného pokračovacího tokenu nebo předávaného časového rozsahu. Můžete buď zadat ContinuationToken pro získání dalšího segmentu sestavy Chaos, nebo můžete určit časový rozsah prostřednictvím StartTimeUtc a EndTimeUtc, ale nemůžete zadat continuationToken a č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 jeho spuštěním v období TimeToRun prostřednictvím rozhraní API StopChaos.

  • MaxClusterStabilizationTimeout: Maximální doba čekání na v pořádku clusteru, než se vytvoří ValidationFailedEvent. Toto čekání má snížit zatížení clusteru při jeho obnovování. Provádí se následující kontroly:

    • Pokud je stav clusteru v pořádku
    • Pokud je stav služby v pořádku
    • Pokud je dosaženo cílové velikosti sady replik pro oddíl služby
    • Že neexistují žádné repliky nástroje InBuild
  • 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ší je chaos a složitější jsou také převzetí služeb při selhání a kombinace přechodu stavu, kterými cluster prochází.

Poznámka

Bez ohledu na to, jak vysoká je hodnota MaxConcurrentFaults , chaos zaručuje , že při absenci externích chyb nedojde ke ztrátě kvora nebo ztrátě dat.

  • EnableMoveReplicaFaults: Povolí nebo zakáže chyby, které způsobují přesun 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 provedení řadu chyb a dokončení odpovídajícího ověření stavu clusteru. Čím vyšší hodnota, tím nižší je průměrná míra injektáže chyb.
  • WaitTimeBetweenFaults: Doba čekání mezi dvěma po sobě jdoucími chybami v jedné iteraci. Čím vyšší je hodnota, tím nižší je souběžnost chyb (nebo jejich překrývání).
  • ClusterHealthPolicy: Zásady stavu clusteru se používají k ověření stavu clusteru mezi iteracemi chaosu. Pokud je stav clusteru chybný nebo dojde k neočekávané výjimce během provádění chyby, chaos počká 30 minut před další kontrolou stavu – aby clusteru poskytl určitou dobu na zozdravení.
  • Kontext: Kolekce dvojic klíč-hodnota typu (řetězec, řetězec). Mapu lze použít k zaznamenávání informací o běhu chaosu. Takových párů nemůže být více než 100 a každý řetězec (klíč nebo hodnota) může mít délku maximálně 4095 znaků. Tato mapa je nastavena startérem běhu Chaos tak, aby volitelně ukládaly kontext konkrétního běhu.
  • ChaosTargetFilter: Tento filtr lze použít k cílení chyb chaosu pouze na určité typy uzlů nebo pouze na určité instance aplikací. Pokud není použit filtr ChaosTargetFilter, chaos zachytí všechny entity clusteru. Pokud je použit Filtr ChaosTargetFilter, chaos vychytá pouze entity, které splňují specifikaci ChaosTargetFilter. NodeTypeInclusionList a ApplicationInclusionList umožňují pouze sémantiku sjednocení. Jinými slovy, není možné zadat průnik NodeTypeInclusionList a ApplicationInclusionList. Není například možné zadat chybu této aplikace pouze v případě, že je na daném typu uzlu. Jakmile je entita zahrnuta v NodeTypeInclusionList nebo ApplicationInclusionList, nelze ji vyloučit pomocí ChaosTargetFilter. I když se applicationX nezobrazuje v seznamu ApplicationInclusionList, v některých iteraci chaosu může být chyba applicationX, protože se nachází na uzlu nodeTypeY, který je součástí NodeTypeInclusionList. Pokud nodeTypeInclusionList a ApplicationInclusionList jsou null nebo prázdné, argumentException je vyvolán.
    • 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í instance, přesunutí sekundární instance a přesunutí instance). Pokud se typ uzlu (například NodeTypeX) nezobrazuje v seznamu NodeTypeInclusionList, chyby na úrovni uzlu (například NodeRestart) nebudou nikdy povoleny pro uzly NodeTypeX, ale chyby balíčku kódu a repliky je možné povolit pro NodeTypeX, pokud se aplikace v ApplicationInclusionList nachází na uzlu NodeTypeX. Do tohoto seznamu lze zahrnout maximálně 100 názvů typů uzlů. Pro zvýšení tohoto počtu se vyžaduje upgrade konfigurace pro konfiguraci MaxNumberOfNodeTypesInChaosTargetFilter.
    • ApplicationInclusionList: Seznam identifikátorů URI aplikací, které se mají zahrnout do chyb chaosu. Všechny repliky, které patří službám těchto aplikací, se dají přizpůsobit chybám repliky (restartování repliky, odebrání repliky, přesunutí primární instance, přesunutí sekundární instance a přesunutí instance). Chaos může restartovat balíček kódu pouze v případě, že balíček kódu hostuje repliky pouze těchto aplikací. Pokud se aplikace v tomto seznamu nezobrazí, může být stále chybou v určité iteraci Chaos, 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í v NodeTypeInclusionList, pak applicationX nikdy nebude chybná. Do tohoto seznamu lze zahrnout maximálně 1 000 názvů aplikací. Pro zvýšení tohoto počtu se vyžaduje upgrade konfigurace pro konfiguraci 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
}