Inducera kontrollerat kaos i Service Fabric-kluster

Storskaliga distribuerade system som molninfrastrukturer är i sig otillförlitliga. Med Azure Service Fabric kan utvecklare skriva tillförlitliga distribuerade tjänster ovanpå en otillförlitlig infrastruktur. För att kunna skriva robusta distribuerade tjänster ovanpå en otillförlitlig infrastruktur måste utvecklare kunna testa stabiliteten i sina tjänster medan den underliggande otillförlitliga infrastrukturen genomgår komplicerade tillståndsövergångar på grund av fel.

Tjänsten För felinmatning och klusteranalys (kallas även tjänsten för felanalys) ger utvecklare möjlighet att orsaka fel för att testa sina tjänster. Dessa riktade simulerade fel, som att starta om en partition, kan hjälpa dig att utföra de vanligaste tillståndsövergångarna. Riktade simulerade fel är dock partiska per definition och kan därför missa buggar som bara visas i svåra att förutsäga, långa och komplicerade sekvenser av tillståndsövergångar. För en opartisk testning kan du använda Chaos.

Kaos simulerar periodiska, interfolierade fel (både graciösa och ospårbara) i hela klustret under längre tidsperioder. Ett smidigt fel består av en uppsättning Service Fabric API-anrop, till exempel att omstartsreplikfel är ett smidigt fel eftersom detta är en stängning följt av en öppning på en replik. Ta bort replik, flytta primär replik, flytta sekundär replik och flytta instans är de andra graciösa fel som utförs av Chaos. Felaktiga fel är processavslut, till exempel omstartsnod och kodpaket för omstart.

När du har konfigurerat Chaos med hastigheten och typen av fel kan du starta Chaos via C#, PowerShell eller REST API för att börja generera fel i klustret och i dina tjänster. Du kan konfigurera Chaos att köras under en angiven tidsperiod (till exempel i en timme), varefter Chaos stoppas automatiskt, eller så kan du anropa StopChaos API (C#, PowerShell eller REST) för att stoppa det när som helst.

Anteckning

I sin nuvarande form inducerar Chaos endast säkra fel, vilket innebär att om det inte finns externa fel uppstår aldrig en kvorumförlust eller dataförlust.

Medan Chaos körs genererar det olika händelser som fångar körningens tillstånd just nu. Till exempel innehåller en ExecutingFaultsEvent alla fel som Chaos har beslutat att köra i iterationen. En ValidationFailedEvent innehåller information om ett valideringsfel (hälso- eller stabilitetsproblem) som hittades under verifieringen av klustret. Du kan anropa GetChaosReport API (C#, PowerShell eller REST) för att hämta rapporten om Chaos-körningar. Dessa händelser sparas i en tillförlitlig ordlista, som har en trunkeringsprincip som styrs av två konfigurationer: MaxStoredChaosEventCount (standardvärdet är 25000) och StoredActionCleanupIntervalInSeconds (standardvärdet är 3600). Alla StoredActionCleanupIntervalInSeconds Chaos-kontroller och alla utom de senaste MaxStoredChaosEventCount-händelserna rensas från den tillförlitliga ordlistan.

Fel som orsakas av kaos

Kaos genererar fel i hela Service Fabric-klustret och komprimerar fel som visas under månader eller år till några timmar. Kombinationen av interfolierade fel med den höga felfrekvensen hittar hörnfall som annars kan missas. Den här övningen av Chaos leder till en betydande förbättring av tjänstens kodkvalitet.

Kaos leder till fel från följande kategorier:

  • Starta om en nod
  • Starta om ett distribuerat kodpaket
  • Ta bort en replik
  • Starta om en replik
  • Flytta en primär replik (kan konfigureras)
  • Flytta en sekundär replik (kan konfigureras)
  • Flytta en instans

Kaos körs i flera iterationer. Varje iteration består av fel och klustervalidering för den angivna perioden. Du kan konfigurera hur länge klustret ska stabiliseras och för att verifieringen ska lyckas. Om ett fel hittas i klustervalidering genererar chaos och bevarar en ValidationFailedEvent med UTC-tidsstämpeln och felinformationen. Tänk dig till exempel en instans av Chaos som är inställd på att köras i en timme med högst tre samtidiga fel. Kaos leder till tre fel och validerar sedan klustrets hälsa. Det itererar genom föregående steg tills det uttryckligen stoppas via StopChaosAsync-API:et eller en timmes pass. Om klustret blir felfritt i en iteration (dvs. att det inte stabiliseras eller om det inte blir felfritt inom det överförda MaxClusterStabilizationTimeout), genererar Chaos en ValidationFailedEvent. Den här händelsen anger att något har gått fel och kan behöva undersökas ytterligare.

Du kan använda GetChaosReport-API :et (PowerShell, C#eller REST) för att få fram vilka fel chaos har orsakat. API:et hämtar nästa segment i Chaos-rapporten baserat på den införda fortsättningstoken eller det införda tidsintervallet. Du kan antingen ange ContinuationToken för att hämta nästa segment i Chaos-rapporten eller ange tidsintervallet via StartTimeUtc och EndTimeUtc, men du kan inte ange både ContinuationToken och tidsintervallet i samma anrop. När det finns fler än 100 Chaos-händelser returneras chaos-rapporten i segment där ett segment inte innehåller mer än 100 Chaos-händelser.

Viktiga konfigurationsalternativ

  • TimeToRun: Total tid som Chaos körs innan det slutförs med framgång. Du kan stoppa Chaos innan det har körts för TimeToRun-perioden via StopChaos-API:et.

  • MaxClusterStabilizationTimeout: Den maximala väntetiden för att klustret ska bli felfritt innan ett ValidationFailedEvent skapas. Den här väntetiden är att minska belastningen på klustret medan det återställs. Kontrollerna som utförs är:

    • Om klustrets hälsotillstånd är OK
    • Om tjänstens hälsotillstånd är OK
    • Om målreplikuppsättningens storlek uppnås för tjänstpartitionen
    • Att det inte finns några InBuild-repliker
  • MaxConcurrentFaults: Det maximala antalet samtidiga fel som induceras i varje iteration. Ju högre tal, desto mer aggressivt kaos är och redundansväxlingarna och de kombinationer av tillståndsövergångar som klustret går igenom är också mer komplexa.

Anteckning

Oavsett hur högt ett värde MaxConcurrentFaults har garanterar Chaos – i avsaknad av externa fel – ingen förlust av kvorum eller dataförlust.

  • EnableMoveReplicaFaults: Aktiverar eller inaktiverar de fel som gör att de primära, sekundära replikerna eller instanserna flyttas. Dessa fel är aktiverade som standard.
  • WaitTimeBetweenIterations: Hur lång tid det tar att vänta mellan iterationer. Det innebär att den tid som Chaos pausar efter att ha kört en felrunda och har slutfört motsvarande validering av klustrets hälsotillstånd. Desto högre värde, desto lägre är den genomsnittliga felinmatningshastigheten.
  • WaitTimeBetweenFaults: Hur lång tid det tar att vänta mellan två på varandra följande fel i en enda iteration. Desto högre värde, desto lägre samtidighet för (eller överlappningen mellan) fel.
  • ClusterHealthPolicy: Klusterhälsoprincip används för att verifiera hälsotillståndet för klustret mellan chaos-iterationer. Om klusterhälsan är felaktig eller om ett oväntat undantag inträffar under felkörningen väntar Chaos i 30 minuter före nästa hälsokontroll – för att ge klustret lite tid att återhämta sig.
  • Kontext: En samling nyckel/värde-par av typen (sträng, sträng). Kartan kan användas för att registrera information om Chaos-körningen. Det får inte finnas fler än 100 sådana par och varje sträng (nyckel eller värde) kan vara högst 4 095 tecken lång. Den här kartan anges av startprogrammet för Chaos-körningen för att eventuellt lagra kontexten om den specifika körningen.
  • ChaosTargetFilter: Det här filtret kan endast användas för att rikta Chaos-fel till vissa nodtyper eller endast för vissa programinstanser. Om ChaosTargetFilter inte används felar Chaos alla klusterentiteter. Om ChaosTargetFilter används felar Chaos endast de entiteter som uppfyller ChaosTargetFilter-specifikationen. NodeTypeInclusionList och ApplicationInclusionList tillåter endast union semantik. Med andra ord går det inte att ange en skärningspunkt för NodeTypeInclusionList och ApplicationInclusionList. Det går till exempel inte att ange "fel endast för det här programmet när det är på den nodtypen". När en entitet ingår i NodeTypeInclusionList eller ApplicationInclusionList kan den entiteten inte uteslutas med Hjälp av ChaosTargetFilter. Även om applicationX inte visas i ApplicationInclusionList kan det i vissa Chaos-iterationsprogramX felas eftersom det råkar finnas på en nod av nodeTypeY som ingår i NodeTypeInclusionList. Om både NodeTypeInclusionList och ApplicationInclusionList är null eller tomma genereras ett ArgumentException.
    • NodeTypeInclusionList: En lista över nodtyper som ska inkluderas i Chaos-fel. Alla typer av fel (omstartsnod, omstart av kodpaket, ta bort replik, omstartsreplik, flytta primär, flytta sekundär och flytta instans) är aktiverade för noderna i dessa nodtyper. Om en nodtyp (t.ex. NodeTypeX) inte visas i NodeTypeInclusionList aktiveras aldrig fel på nodnivå (som NodeRestart) för noderna i NodeTypeX, men kodpaket- och replikfel kan fortfarande aktiveras för NodeTypeX om ett program i ApplicationInclusionList råkar finnas på en nod i NodeTypeX. Högst 100 nodtypsnamn kan tas med i den här listan, för att öka det här antalet krävs en konfigurationsuppgradering för Konfiguration av MaxNumberOfNodeTypesInChaosTargetFilter.
    • ApplicationInclusionList: En lista över program-URI:er som ska inkluderas i Chaos-fel. Alla repliker som tillhör tjänster i dessa program kan användas för replikeringsfel (starta om repliken, ta bort repliken, flytta primär, flytta sekundär och flytta instansen) efter Chaos. Kaos kan bara starta om ett kodpaket om kodpaketet endast är värd för repliker av dessa program. Om ett program inte visas i den här listan kan det fortfarande felas i en viss Chaos-iteration om programmet hamnar på en nod av en nodtyp som ingår i NodeTypeInclusionList. Men om applicationX är kopplat till nodeTypeY via placeringsbegränsningar och applicationX saknas i ApplicationInclusionList och nodeTypeY saknas från NodeTypeInclusionList, kommer applicationX aldrig att felas. Högst 1 000 programnamn kan tas med i den här listan, för att öka det här antalet krävs en konfigurationsuppgradering för Konfiguration av MaxNumberOfApplicationsInChaosTargetFilter.At most 1000 application names can be included in this number, config upgrade is required for MaxNumberOfApplicationsInChaosTargetFilter configuration.

Så här kör du kaos

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
}