Dela via


Inducera kontrollerat kaos i Service Fabric-kluster

Storskaliga distribuerade system som molninfrastrukturer är otillförlitliga. Med Azure Service Fabric kan utvecklare skriva tillförlitliga distribuerade tjänster ovanpå en opålitlig 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.

Felinmatnings- och klusteranalystjänsten (kallas även felanalystjänsten) ger utvecklare möjlighet att inducera 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, överlagrade fel (både graciösa och felaktiga) i hela klustret under längre tidsperioder. Ett korrekt fel består av en uppsättning Service Fabric API-anrop, till exempel omstartsreplikfel är ett korrekt 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 hastighet och typ 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 skapar det olika händelser som fångar körningens tillstånd för tillfället. En ExecutingFaultsEvent innehåller till exempel 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 framkallas i 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 i Chaos leder till en betydande förbättring av tjänstens kodkvalitet.

Kaos orsakar 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 klusterverifiering för den angivna perioden. Du kan konfigurera den tid som används för att klustret ska stabiliseras och för att verifieringen ska lyckas. Om ett fel hittas i klusterverifieringen 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 orsakar tre fel och validerar sedan klustrets hälsa. Den itererar genom föregående steg tills den uttryckligen stoppas via StopChaosAsync-API:et eller en timmes pass. Om klustret blir felfritt i någon iteration (det vill sa 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.

Om du vill se vilka fel Som kaos orsakar kan du använda GetChaosReport API (PowerShell, C# eller REST). API:et hämtar nästa segment i Chaos-rapporten baserat på den skickade 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 den har körts för TimeToRun-perioden via StopChaos-API:et.

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

    • Om klusterhälsan är OK
    • Om tjänstens hälsa ä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 siffra, 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 kvorumförlust eller dataförlust.

  • EnableMoveReplicaFaults: Aktiverar eller inaktiverar de fel som orsakar 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 tiden 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. Ju högre värde, desto lägre samtidighet för (eller överlappning mellan) fel.
  • ClusterHealthPolicy: Klustrets hä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 innan 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) får innehålla högst 4 095 tecken. 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 kaosfel 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 iteration-programX 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 codepackage, ta bort replik, omstartsreplik, flytta primär, flytta sekundär och flytta instans) är aktiverade för noderna i dessa nodtyper. Om en nodtyp (till exempel NodeTypeX) inte visas i NodeTypeInclusionList aktiveras aldrig fel på nodnivå (t.ex. 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. Om du vill öka antalet krävs en konfigurationsuppgradering för konfigurationen av MaxNumberOfNodeTypesInChaosTargetFilter.
    • ApplicationInclusionList: En lista över program-URI:er som ska inkluderas i Chaos-fel. Alla repliker som hör till tjänster i dessa program kan användas för replikfel (omstartsreplik, ta bort replik, flytta primär, flytta sekundär och flytta instans) av 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 i NodeTypeInclusionList, kommer applicationX aldrig att felas. Högst 1 000 programnamn kan tas med i den här listan. Om du vill öka antalet krävs en konfigurationsuppgradering för konfiguration av MaxNumberOfApplicationsInChaosTargetFilter.

Så här kör du 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
}