Gecontroleerde chaos in Service Fabric-clusters veroorzaken

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

De foutinjectie- en clusteranalyseservice (ook wel de Foutanalyseservice genoemd) biedt ontwikkelaars de mogelijkheid om fouten te veroorzaken om hun services te testen. Deze gerichte gesimuleerde fouten, zoals het opnieuw opstarten van een partitie, kunnen helpen bij het uitvoeren van de meest voorkomende statusovergangen. Gerichte gesimuleerde fouten zijn echter per definitie bevooroordeeld en kunnen dus fouten missen die alleen voorkomen in moeilijk te voorspellen, lange en gecompliceerde reeks statusovergangen. Voor een objectieve test kunt u Chaos gebruiken.

Chaos simuleert periodieke, interleaved fouten (zowel sierlijk als ondankbaar) in het hele cluster gedurende langere perioden. Een goede fout bestaat uit een set Service Fabric-API-aanroepen. Een fout bij het opnieuw opstarten van replica's is bijvoorbeeld een probleem dat probleem probleemloos is, omdat dit een bijna gevolgd door een open op een replica is. Replica verwijderen, primaire replica verplaatsen, secundaire replica verplaatsen en exemplaar verplaatsen zijn de andere probleemloze fouten die door Chaos worden gebruikt. Ungraceful fouten zijn procesafsluitingen, zoals het opnieuw opstarten van het knooppunt en het codepakket voor 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 gedurende een opgegeven periode (bijvoorbeeld één uur) wordt uitgevoerd, waarna Chaos automatisch stopt, of u kunt De StopChaos-API (C#, PowerShell of REST) op elk gewenst moment aanroepen om deze 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 is gevonden tijdens de validatie van het cluster. U kunt de GetChaosReport-API (C#, PowerShell of REST) aanroepen om het rapport van Chaos-uitvoeringen op te halen. Deze gebeurtenissen worden opgeslagen in een betrouwbare woordenlijst, met een afkappingsbeleid dat wordt bepaald door twee configuraties: MaxStoredChaosEventCount (standaardwaarde is 25000) en StoredActionCleanupIntervalInSeconds (standaardwaarde is 3600). Elke StoredActionCleanupIntervalInSeconds Chaos-controles en alle gebeurtenissen, behalve de meest recente MaxStoredChaosEventCount , worden verwijderd 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 worden gezien in een paar uur. De combinatie van interleaved fouten met het hoge foutpercentage vindt hoekcases die anders mogelijk 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 om het cluster te stabiliseren en om de validatie te laten slagen. Als er een fout wordt gevonden in clustervalidatie, genereert chaos een ValidationFailedEvent met de UTC-tijdstempel en de details van de fout. Denk bijvoorbeeld aan een exemplaar van Chaos dat is ingesteld op uitvoering van een uur 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 in een iteratie beschadigd raakt (dat wil gezegd, het wordt niet gestabiliseerd of niet in orde wordt binnen de doorgegeven MaxClusterStabilizationTimeout), genereert Chaos een ValidationFailedEvent. Deze gebeurtenis geeft aan dat er iets mis is gegaan en mogelijk verder moet worden onderzocht.

Als u wilt zien welke fouten Chaos heeft veroorzaakt, 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 doorgegeven tijdsbereik. U kunt het ContinuationToken opgeven om het volgende segment van het Chaos-rapport op te halen of u kunt het tijdsbereik opgeven via StartTimeUtc en EndTimeUtc, maar u kunt niet zowel het ContinuationToken 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 uitvoert voordat deze met succes wordt voltooid. U kunt Chaos stoppen voordat deze is uitgevoerd voor de TimeToRun-periode via de StopChaos-API.

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

    • Als de clusterstatus in orde is
    • Als de servicestatus in orde is
    • Als de grootte van de doelreplicaset is bereikt voor de servicepartitie
    • Dat er geen InBuild-replica's bestaan
  • MaxConcurrentFaults: het maximum aantal gelijktijdige fouten dat wordt veroorzaakt in elke iteratie. Hoe hoger het aantal, hoe agressiever Chaos is en de failovers en de statusovergangscombinaties 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: hiermee schakelt u 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 onderbreekt nadat een reeks fouten is 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 een fout heeft of als er een onverwachte uitzondering optreedt tijdens het uitvoeren van de fout, wacht Chaos 30 minuten voordat de volgende statuscontrole wordt uitgevoerd, zodat het cluster enige tijd heeft om te herstellen.
  • Context: een verzameling (tekenreeks, tekenreeks) sleutel-waardeparen. De kaart kan worden gebruikt om informatie over de Chaos-uitvoering vast te leggen. Er kunnen niet meer dan 100 van dergelijke paren zijn en elke tekenreeks (sleutel of waarde) mag maximaal 4095 tekens lang zijn. Deze kaart wordt door de starter van de Chaos-uitvoering ingesteld om eventueel de context over de specifieke uitvoering op te slaan.
  • ChaosTargetFilter: dit filter kan worden gebruikt om Chaos-fouten alleen te richten op bepaalde typen knooppunten of alleen op bepaalde toepassingsexemplaren. Als ChaosTargetFilter niet wordt gebruikt, maakt Chaos fouten in alle clusterentiteiten. Als ChaosTargetFilter wordt gebruikt, maakt Chaos alleen fouten met de entiteiten die voldoen aan de specificatie ChaosTargetFilter. NodeTypeInclusionList en ApplicationInclusionList staan alleen samenvoegingssemantiek toe. Met andere woorden, het is niet mogelijk om een snijpunt van NodeTypeInclusionList en ApplicationInclusionList op te geven. Het is bijvoorbeeld niet mogelijk om 'fout in deze toepassing alleen 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 er in sommige Chaos-iteraties fouten optreden in applicationX 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 (knooppunt opnieuw opstarten, codepakket opnieuw opstarten, replica verwijderen, replica opnieuw opstarten, primaire replica verplaatsen, secundaire 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 de ApplicationInclusionList zich op een knooppunt van NodeTypeX bevindt. Er kunnen maximaal 100 namen van knooppunttypen worden opgenomen in deze lijst. Als u dit aantal wilt verhogen, is een configuratie-upgrade vereist voor de configuratie MaxNumberOfNodeTypesInChaosTargetFilter.
    • ApplicationInclusionList: een lijst met toepassings-URI's die moeten worden opgenomen in Chaos-fouten. Alle replica's die behoren tot de services van deze toepassingen kunnen worden gebruikt voor replicafouten (opnieuw opstarten van replica, replica verwijderen, primaire replica verplaatsen, secundaire instantie verplaatsen en exemplaar verplaatsen) door Chaos. Chaos kan een codepakket alleen opnieuw starten als het codepakket alleen als host fungeert voor replica's van deze toepassingen. Als een toepassing niet in deze lijst wordt weergegeven, kan er nog steeds een fout optreden in een Chaos-iteratie als de toepassing op een knooppunt van een knooppunttype terechtkomt dat is opgenomen in NodeTypeInclusionList. Als applicationX echter is gekoppeld aan nodeTypeY via plaatsingsbeperkingen en applicationX niet aanwezig is in ApplicationInclusionList en nodeTypeY niet aanwezig is in NodeTypeInclusionList, zal applicationX nooit fouten krijgen. In deze lijst kunnen maximaal 1000 toepassingsnamen worden opgenomen. 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
}