Share via


Gecontroleerde chaos veroorzaken in Service Fabric-clusters

Grootschalige gedistribueerde systemen zoals cloudinfrastructuren zijn inherent onbetrouwbaar. Met Azure Service Fabric kunnen ontwikkelaars betrouwbare gedistribueerde services schrijven boven op een onbetrouwbare infrastructuur. Om robuuste gedistribueerde services te schrijven op een onbetrouwbare infrastructuur, moeten ontwikkelaars de stabiliteit van hun services kunnen testen terwijl de onderliggende onbetrouwbare infrastructuur complexe statusovergangen doorloopt vanwege fouten.

De foutinjectie en clusteranalyseservice (ook wel bekend als de Foutanalyseservice) bieden ontwikkelaars de mogelijkheid om fouten op te wekken om hun services te testen. Deze gerichte gesimuleerde fouten, zoals het opnieuw opstarten van een partitie, kunnen helpen de meest voorkomende statusovergangen uit te oefenen. Gerichte gesimuleerde fouten worden echter per definitie beïnvloed en kunnen dus fouten missen die alleen worden weergegeven in moeilijk te voorspellen, lange en gecompliceerde volgorde van statusovergangen. Voor een onbevoorbeerd testen kunt u Chaos gebruiken.

Chaos simuleert periodieke, interleaved fouten (zowel sierlijk als ondankbaar) in het hele cluster gedurende langere tijd. Een fout die probleemloos is, bestaat uit een set Service Fabric-API-aanroepen, bijvoorbeeld een fout bij het opnieuw starten van replica's is een fout die probleemloos is, omdat dit een close is gevolgd door een open op een replica. Replica verwijderen, primaire replica verplaatsen, secundaire replica verplaatsen en exemplaar verplaatsen zijn de andere probleemloze fouten die door Chaos worden uitgeoefend. Onbeheerde fouten zijn procesafsluitingen, zoals het opnieuw opstarten van het knooppunt en het codepakket opnieuw opstarten.

Zodra u Chaos hebt geconfigureerd met de snelheid en het soort fouten, kunt u Chaos starten via C#, PowerShell of REST API om te beginnen met het genereren van fouten in het cluster en in uw services. U kunt Chaos zo configureren dat deze wordt uitgevoerd voor een opgegeven periode (bijvoorbeeld één uur), waarna Chaos automatisch stopt, of u kunt StopChaos-API (C#, PowerShell of REST) aanroepen om deze op elk gewenst moment te stoppen.

Notitie

In de huidige vorm veroorzaakt Chaos alleen veilige fouten, wat impliceert dat bij afwezigheid van externe fouten een quorumverlies of gegevensverlies nooit optreedt.

Terwijl Chaos wordt uitgevoerd, produceert het verschillende gebeurtenissen die de status van de uitvoering op dit moment vastleggen. Een ExecutingFaultsEvent bevat bijvoorbeeld alle fouten die Chaos heeft besloten uit te voeren in die iteratie. Een ValidationFailedEvent bevat de details van een validatiefout (status- of stabiliteitsproblemen) die tijdens de validatie van het cluster zijn gevonden. U kunt de GetChaosReport-API (C#, PowerShell of REST) aanroepen om het rapport van Chaos-uitvoeringen op te halen. Deze gebeurtenissen worden bewaard in een betrouwbare woordenlijst, die een afkappingsbeleid heeft dat wordt bepaald door twee configuraties: MaxStoredChaosEventCount (standaardwaarde is 25000) en StoredActionCleanupIntervalInSeconds (standaardwaarde is 3600). Elke StoredActionCleanupIntervalInSeconds Chaos controleert en alle, behalve de meest recente gebeurtenissen MaxStoredChaosEventCount , worden opgeschoond uit de betrouwbare woordenlijst.

Fouten veroorzaakt in chaos

Chaos genereert fouten in het hele Service Fabric-cluster en comprimeert fouten die in maanden of jaren in een paar uur worden gezien. De combinatie van interleaved fouten met het hoge foutpercentage vindt hoekcases die anders kunnen worden gemist. Deze oefening van Chaos leidt tot een aanzienlijke verbetering van de codekwaliteit van de service.

Chaos veroorzaakt fouten uit de volgende categorieën:

  • Een knooppunt opnieuw opstarten
  • Een geïmplementeerd codepakket opnieuw starten
  • Een replica verwijderen
  • Een replica opnieuw opstarten
  • Een primaire replica verplaatsen (configureerbaar)
  • Een secundaire replica verplaatsen (configureerbaar)
  • Een exemplaar verplaatsen

Chaos wordt uitgevoerd in meerdere iteraties. Elke iteratie bestaat uit fouten en clustervalidatie voor de opgegeven periode. U kunt de tijd configureren die is besteed aan het cluster om te stabiliseren en om de validatie te laten slagen. Als er een fout wordt gevonden in clustervalidatie, genereert en persistenteert Chaos een ValidationFailedEvent met de UTC-tijdstempel en de foutdetails. Denk bijvoorbeeld aan een instantie van Chaos die is ingesteld om een uur te worden uitgevoerd met maximaal drie gelijktijdige fouten. Chaos veroorzaakt drie fouten en valideert vervolgens de clusterstatus. De vorige stap wordt herhaald totdat deze expliciet wordt gestopt via de StopChaosAsync-API of een uur. Als het cluster beschadigd raakt in een iteratie (dat wil gezegd, wordt het niet gestabiliseerd of wordt het niet in orde binnen de doorgegeven MaxClusterStabilizationTimeout), genereert Chaos een ValidationFailedEvent. Deze gebeurtenis geeft aan dat er iets mis is gegaan en mogelijk verder onderzoek nodig heeft.

Als u wilt weten welke fouten Chaos is geïnduceerd, kunt u GetChaosReport API (PowerShell, C# of REST) gebruiken. De API haalt het volgende segment van het Chaos-rapport op op basis van het doorgegeven vervolgtoken of het verstreken tijdsbereik. U kunt het Vervolgtoken opgeven om het volgende segment van het Chaos-rapport op te halen of u kunt het tijdsbereik tot en met StartTimeUtc en EndTimeUtc opgeven, maar u kunt niet zowel het Vervolgtoken als het tijdsbereik in dezelfde aanroep opgeven. Wanneer er meer dan 100 Chaos-gebeurtenissen zijn, wordt het Chaos-rapport geretourneerd in segmenten waarin een segment niet meer dan 100 Chaos-gebeurtenissen bevat.

Belangrijke configuratieopties

  • TimeToRun: Totale tijd die Chaos wordt uitgevoerd voordat het is voltooid met succes. U kunt Chaos stoppen voordat deze is uitgevoerd voor de TimeToRun-periode via de StopChaos-API.

  • MaxClusterStabilizationTimeout: de maximale hoeveelheid tijd die moet worden gewacht totdat het cluster in orde is voordat een ValidationFailedEvent wordt geproduceerd. Dit wacht om de belasting van het cluster te verminderen terwijl het wordt hersteld. De uitgevoerde controles zijn:

    • Als de clusterstatus OK is
    • Als de servicestatus OK is
    • Als de grootte van de doelreplicaset wordt bereikt voor de servicepartitie
    • Dat er geen InBuild-replica's bestaan
  • MaxConcurrentFaults: het maximum aantal gelijktijdige fouten dat in elke iteratie wordt geïnduceerd. Hoe hoger het getal, hoe agressiever Chaos is en de failovers en de combinaties van statusovergangen die het cluster doorloopt, zijn ook complexer.

Notitie

Ongeacht hoe hoog een waarde MaxConcurrentFaults heeft, garandeert Chaos - bij afwezigheid van externe fouten - geen quorumverlies of gegevensverlies.

  • EnableMoveReplicaFaults: schakelt de fouten in of uit die ervoor zorgen dat de primaire, secundaire replica's of exemplaren worden verplaatst. Deze fouten zijn standaard ingeschakeld.
  • WaitTimeBetweenIterations: de hoeveelheid tijd die moet worden gewacht tussen iteraties. Dat wil gezegd, de hoeveelheid tijd die Chaos zal onderbreken nadat een ronde fouten zijn uitgevoerd en de bijbehorende validatie van de status van het cluster is voltooid. Hoe hoger de waarde, hoe lager de gemiddelde foutinjectiesnelheid.
  • WaitTimeBetweenFaults: de hoeveelheid tijd die moet worden gewacht tussen twee opeenvolgende fouten in één iteratie. Hoe hoger de waarde, hoe lager de gelijktijdigheid van (of de overlapping tussen) fouten.
  • ClusterHealthPolicy: Clusterstatusbeleid wordt gebruikt om de status van het cluster tussen chaos-iteraties te valideren. Als de clusterstatus fout is of als er een onverwachte uitzondering optreedt tijdens de uitvoering van de fout, wacht Chaos 30 minuten voordat de volgende statuscontrole wordt uitgevoerd, om het cluster enige tijd te geven om te herstellen.
  • Context: Een verzameling sleutel-waardeparen (tekenreeks, tekenreeks). De kaart kan worden gebruikt om informatie over de Chaos-uitvoering vast te leggen. Er mogen maximaal 100 dergelijke paren zijn en elke tekenreeks (sleutel of waarde) mag maximaal 4095 tekens lang zijn. Deze kaart wordt ingesteld door de starter van de Chaos-uitvoering om eventueel de context over de specifieke uitvoering op te slaan.
  • ChaosTargetFilter: dit filter kan worden gebruikt om chaosfouten alleen te richten op bepaalde knooppunttypen of alleen op bepaalde toepassingsexemplaren. Als ChaosTargetFilter niet wordt gebruikt, krijgt Chaos fouten in alle clusterentiteiten. Als ChaosTargetFilter wordt gebruikt, worden alleen de entiteiten die voldoen aan de chaosTargetFilter-specificatie, door chaos defect. NodeTypeInclusionList en ApplicationInclusionList staan de semantiek van de samenvoeging alleen toe. Met andere woorden, het is niet mogelijk om een snijpunt van NodeTypeInclusionList en ApplicationInclusionList op te geven. Het is bijvoorbeeld niet mogelijk om 'deze toepassing alleen fout op te geven wanneer deze zich op dat knooppunttype bevindt'. Zodra een entiteit is opgenomen in NodeTypeInclusionList of ApplicationInclusionList, kan die entiteit niet worden uitgesloten met Behulp van ChaosTargetFilter. Zelfs als applicationX niet wordt weergegeven in ApplicationInclusionList, kan in sommige chaos-iteratietoepassingX worden beschadigd omdat deze zich op een knooppunt van nodeTypeY bevindt dat is opgenomen in NodeTypeInclusionList. Als zowel NodeTypeInclusionList als ApplicationInclusionList null of leeg zijn, wordt een ArgumentException gegenereerd.
    • NodeTypeInclusionList: een lijst met knooppunttypen die moeten worden opgenomen in Chaos-fouten. Alle typen fouten (opnieuw opstarten van knooppunt, codepackage opnieuw opstarten, replica verwijderen, replica opnieuw opstarten, primaire verplaatsen, secundaire verplaatsen en exemplaar verplaatsen) zijn ingeschakeld voor de knooppunten van deze knooppunttypen. Als een knooppunttype (bijvoorbeeld NodeTypeX) niet wordt weergegeven in de NodeTypeInclusionList, worden fouten op knooppuntniveau (zoals NodeRestart) nooit ingeschakeld voor de knooppunten van NodeTypeX, maar codepakket- en replicafouten kunnen nog steeds worden ingeschakeld voor NodeTypeX als een toepassing in applicationInclusionList zich op een knooppunt van NodeTypeX bevindt. Maximaal 100 namen van knooppunttypen kunnen in deze lijst worden opgenomen om dit aantal te verhogen. Er is een configuratie-upgrade vereist voor de configuratie van MaxNumberOfNodeTypesInChaosTargetFilter.
    • ApplicationInclusionList: een lijst met toepassings-URI's die moeten worden opgenomen in Chaos-fouten. Alle replica's die behoren tot services van deze toepassingen, kunnen worden beheerd door replicafouten (replica opnieuw opstarten, replica verwijderen, primaire replica verplaatsen, secundair verplaatsen en exemplaar verplaatsen) door Chaos. Chaos kan een codepakket alleen opnieuw opstarten als het codepakket alleen replica's van deze toepassingen host. Als een toepassing niet wordt weergegeven in deze lijst, kan er nog steeds een fout optreden in een chaos-iteratie als de toepassing terechtkomt op een knooppunt van een knooppunttype dat is opgenomen in NodeTypeInclusionList. Als applicationX echter is gekoppeld aan nodeTypeY via plaatsingsbeperkingen en applicationX afwezig is in ApplicationInclusionList en nodeTypeY ontbreekt in NodeTypeInclusionList, krijgt applicationX nooit een fout. Maximaal 1000 toepassingsnamen kunnen worden opgenomen in deze lijst, om dit aantal te verhogen, is een configuratie-upgrade vereist voor de configuratie van MaxNumberOfApplicationsInChaosTargetFilter.

Chaos uitvoeren

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
}