Wywoływanie kontrolowanego chaosu w klastrach usługi Service Fabric
Systemy rozproszone na dużą skalę, takie jak infrastruktury chmurowe, są z natury zawodne. Usługa Azure Service Fabric umożliwia deweloperom pisanie niezawodnych usług rozproszonych na podstawie zawodnej infrastruktury. Aby napisać niezawodne usługi rozproszone na podstawie zawodnej infrastruktury, deweloperzy muszą mieć możliwość przetestowania stabilności swoich usług, podczas gdy podstawowa zawodna infrastruktura przechodzi skomplikowane przejścia stanu z powodu błędów.
Usługa iniekcji błędów i usługi analizy klastra (znana również jako usługa analizy błędów) umożliwia deweloperom wywoływanie błędów w celu przetestowania swoich usług. Te ukierunkowane symulowane błędy, takie jak ponowne uruchamianie partycji, mogą pomóc w wykonywaniu najczęstszych przejść stanu. Jednak ukierunkowane symulowane błędy są stronnicze według definicji i w związku z tym mogą pomijać błędy, które pojawiają się tylko w trudnej do przewidzenia, długiej i skomplikowanej sekwencji przejść stanu. W przypadku testowania bez uprzedzeń można użyć chaosu.
Chaos symuluje okresowe, przeplatane błędy (zarówno bezproblemowe, jak i niegrabne) w całym klastrze w dłuższych okresach czasu. Błąd z wdziękiem składa się z zestawu wywołań interfejsu API usługi Service Fabric, na przykład błędu ponownego uruchamiania repliki, ponieważ jest to błąd zamykany, po którym następuje otwarcie repliki. Usuń replikę, przenieś replikę podstawową, przenieś replikę pomocniczą i wystąpienie przenoszenia to inne bezproblemowe błędy wykonywane przez chaos. Nieprawidłowe błędy to zakończenia procesu, takie jak ponowne uruchomienie węzła i ponowne uruchomienie pakietu kodu.
Po skonfigurowaniu chaosu z szybkością i rodzajem błędów możesz rozpocząć chaos za pośrednictwem języka C#, programu PowerShell lub interfejsu API REST, aby rozpocząć generowanie błędów w klastrze i w usługach. Można skonfigurować chaos tak, aby był uruchamiany przez określony czas (na przykład przez jedną godzinę), po którym chaos zostanie zatrzymany automatycznie lub wywołać interfejs API StopChaos (C#, PowerShell lub REST), aby zatrzymać go w dowolnym momencie.
Uwaga
W obecnej formie Chaos wywołuje tylko bezpieczne błędy, co oznacza, że w przypadku braku błędów zewnętrznych utrata kworum lub utrata danych nigdy nie występuje.
Gdy chaos jest uruchomiony, generuje różne zdarzenia, które przechwytują stan uruchomienia w tej chwili. Na przykład element ExecuteingFaultsEvent zawiera wszystkie błędy, które chaos postanowił wykonać w tej iteracji. Element ValidationFailedEvent zawiera szczegóły niepowodzenia weryfikacji (problemy z kondycją lub stabilnością), które zostały znalezione podczas walidacji klastra. Aby uzyskać raport o uruchomieniu chaosu, możesz wywołać interfejs API GetChaosReport (C#, PowerShell lub REST). Te zdarzenia są utrwalane w niezawodnym słowniku, który ma zasady obcinania dyktowane przez dwie konfiguracje: MaxStoredChaosEventCount (wartość domyślna to 25000) i StoredActionCleanupIntervalInSeconds (wartość domyślna to 3600). Każda funkcja StoredActionCleanupIntervalInSeconds Chaos sprawdza i wszystkie, ale najnowsze zdarzenia MaxStoredChaosEventCount , są czyszczone z niezawodnego słownika.
Błędy wywołane w chaosie
Chaos generuje błędy w całym klastrze usługi Service Fabric i kompresuje błędy występujące w miesiącach lub latach w ciągu kilku godzin. Połączenie przeplatanych błędów z wysoką szybkością błędów znajduje przypadki narożne, które w przeciwnym razie mogą zostać pominięte. To ćwiczenie chaosu prowadzi do znacznej poprawy jakości kodu usługi.
Chaos powoduje błędy z następujących kategorii:
- Ponowne uruchamianie węzła
- Ponowne uruchamianie wdrożonego pakietu kodu
- Usuwanie repliki
- Ponowne uruchamianie repliki
- Przenoszenie repliki podstawowej (możliwe do skonfigurowania)
- Przenoszenie repliki pomocniczej (możliwe do skonfigurowania)
- Przenoszenie wystąpienia
Chaos działa w wielu iteracji. Każda iteracja składa się z błędów i weryfikacji klastra dla określonego okresu. Możesz skonfigurować czas spędzony na stabilizacji klastra i pomyślne sprawdzenie poprawności. Jeśli błąd zostanie znaleziony w weryfikacji klastra, chaos generuje i utrwala element ValidationFailedEvent ze znacznikiem czasu UTC i szczegółami niepowodzenia. Rozważmy na przykład wystąpienie chaosu ustawionego na godzinę z maksymalnie trzema współbieżnymi błędami. Chaos wywołuje trzy błędy, a następnie weryfikuje kondycję klastra. Iteruje przez poprzedni krok, dopóki nie zostanie jawnie zatrzymany za pośrednictwem interfejsu API StopChaosAsync lub jednej godziny przechodzi. Jeśli klaster stanie się w złej kondycji w jakiejkolwiek iteracji (oznacza to, że nie jest stabilizowany lub nie stanie się w dobrej kondycji w ramach przekazanego elementu MaxClusterStabilizationTimeout), chaos generuje element ValidationFailedEvent. To zdarzenie wskazuje, że coś poszło nie tak i może wymagać dalszego zbadania.
Aby uzyskać błędy wywołane przez chaos, możesz użyć interfejsu API GetChaosReport (PowerShell, C# lub REST). Interfejs API pobiera następny segment raportu chaosu na podstawie przekazanego tokenu kontynuacji lub przekazanego zakresu czasu. Możesz określić parametr ContinuationToken, aby uzyskać następny segment raportu Chaosu lub określić zakres czasu za pomocą parametru StartTimeUtc i EndTimeUtc, ale nie można określić zarówno parametru ContinuationToken, jak i zakresu czasu w tym samym wywołaniu. Gdy istnieje więcej niż 100 zdarzeń chaosu, raport Chaos jest zwracany w segmentach, w których segment zawiera nie więcej niż 100 zdarzeń Chaosu.
Ważne opcje konfiguracji
TimeToRun: łączny czas działania chaosu przed jego powodzeniem. Możesz zatrzymać chaos przed uruchomieniem okresu TimeToRun za pośrednictwem interfejsu API StopChaos.
MaxClusterStabilizationTimeout: maksymalny czas oczekiwania na kondycję klastra przed utworzeniem elementu ValidationFailedEvent. To oczekiwanie polega na zmniejszeniu obciążenia klastra podczas odzyskiwania. Wykonane testy to:
- Jeśli kondycja klastra jest ok
- Jeśli kondycja usługi jest ok
- Jeśli rozmiar zestawu replik docelowych zostanie osiągnięty dla partycji usługi
- Że nie istnieją żadne repliki w programie InBuild
MaxConcurrentFaults: maksymalna liczba współbieżnych błędów, które są wywoływane w każdej iteracji. Im większa liczba, tym bardziej agresywny jest Chaos, a tryb failover i kombinacje przejścia stanu, które przechodzi klaster, są również bardziej złożone.
Uwaga
Niezależnie od tego, jak wysoka wartość ma MaxConcurrentFaults , chaos gwarantuje - w przypadku braku błędów zewnętrznych - nie ma utraty kworum ani utraty danych.
- EnableMoveReplicaFaults: włącza lub wyłącza błędy, które powodują przenoszenie replik podstawowych, pomocniczych lub wystąpień. Te błędy są domyślnie włączone.
- WaitTimeBetweenIterations: czas oczekiwania między iteracjami. Oznacza to, że czas wstrzymania chaosu po wykonaniu rundy błędów i zakończeniu odpowiedniej walidacji kondycji klastra. Im wyższa wartość, tym niższa jest średnia szybkość iniekcji błędów.
- WaitTimeBetweenFaults: czas oczekiwania między dwoma kolejnymi błędami w jednej iteracji. Im większa wartość, tym niższa współbieżność (lub nakładanie się między nimi) błędów.
- ClusterHealthPolicy: zasady kondycji klastra są używane do weryfikowania kondycji klastra między iteracjami chaosu. Jeśli kondycja klastra jest błędna lub wystąpi nieoczekiwany wyjątek podczas wykonywania błędu, chaos będzie czekać 30 minut przed następnym sprawdzeniem kondycji — aby zapewnić klastrowi trochę czasu na odzyskanie.
- Kontekst: kolekcja par klucz-wartość typu (ciąg, ciąg). Mapa może służyć do rejestrowania informacji o przebiegu chaosu. Nie może być więcej niż 100 takich par, a każdy ciąg (klucz lub wartość) może mieć długość maksymalnie 4095 znaków. Ta mapa jest ustawiana przez początkowy przebieg chaosu, aby opcjonalnie przechowywać kontekst dotyczący określonego przebiegu.
- ChaosTargetFilter: ten filtr może służyć do określania błędów chaosu tylko dla niektórych typów węzłów lub tylko dla niektórych wystąpień aplikacji. Jeśli funkcja ChaosTargetFilter nie jest używana, chaos błędy wszystkich jednostek klastra. Jeśli użyto metody ChaosTargetFilter, chaos uszkodzi tylko jednostki spełniające specyfikację ChaosTargetFilter. NodeTypeInclusionList i ApplicationInclusionList zezwalają tylko na semantyka unii. Innymi słowy, nie można określić skrzyżowania wartości NodeTypeInclusionList i ApplicationInclusionList. Na przykład nie można określić "błędu tej aplikacji tylko wtedy, gdy znajduje się ona w tym typie węzła". Po dołączeniu jednostki do elementu NodeTypeInclusionList lub ApplicationInclusionList nie można wykluczyć tej jednostki przy użyciu metody ChaosTargetFilter. Nawet jeśli aplikacjaX nie jest wyświetlana w elemecie ApplicationInclusionList, w niektórych aplikacjach iteracji chaosu X może zostać uszkodzony, ponieważ występuje on w węźle nodeTypeY, który znajduje się w elemecie NodeTypeInclusionList. Jeśli wartości NodeTypeInclusionList i ApplicationInclusionList mają wartość null lub są puste, zgłaszany jest wyjątek ArgumentException.
- NodeTypeInclusionList: lista typów węzłów do uwzględnienia w błędach chaosu. Wszystkie typy błędów (ponowne uruchamianie węzła, ponowne uruchamianie pakietu kodu, usuwanie repliki, ponowne uruchamianie repliki, przenoszenie podstawowej, przenoszenie pomocnicze i przenoszenie wystąpienia) są włączone dla węzłów tych typów węzłów. Jeśli typ węzła (np. NodeTypeX) nie pojawi się w elemecie NodeTypeInclusionList, błędy na poziomie węzła (takie jak NodeRestart) nigdy nie zostaną włączone dla węzłów NodeTypeX, ale nadal można włączyć pakiet kodu i błędy repliki dla nodeTypeX, jeśli aplikacja w elememencie ApplicationInclusionList będzie znajdować się w węźle NodeTypeX. Na tej liście można uwzględnić maksymalnie 100 nazw typów węzłów, aby zwiększyć tę liczbę, uaktualnienie konfiguracji jest wymagane dla konfiguracji MaxNumberOfNodeTypesInChaosTargetFilter.
- ApplicationInclusionList: lista identyfikatorów URI aplikacji do uwzględnienia w błędach chaosu. Wszystkie repliki należące do usług tych aplikacji są dostępne do replikacji błędów (ponowne uruchamianie repliki, usuwanie repliki, przenoszenie podstawowej, przenoszenie pomocnicze i przenoszenie wystąpienia) przez Chaos. Chaos może ponownie uruchomić pakiet kodu tylko wtedy, gdy pakiet kodu hostuje repliki tych aplikacji. Jeśli aplikacja nie jest wyświetlana na tej liście, nadal może zostać uszkodzona w niektórych iteracji Chaos, jeśli aplikacja kończy się na węźle typu węzła, który jest uwzględniony w nodeTypeInclusionList. Jeśli jednak element applicationX jest powiązany z elementem nodeTypeY za pośrednictwem ograniczeń umieszczania, a element applicationX jest nieobecny w elementy ApplicationInclusionList, a element nodeTypeInclusionList jest nieobecny w pliku NodeTypeInclusionList, aplikacjaX nigdy nie zostanie uszkodzona. Na tej liście można uwzględnić maksymalnie 1000 nazw aplikacji, aby zwiększyć tę liczbę, wymagane jest uaktualnienie konfiguracji dla konfiguracji MaxNumberOfApplicationsInChaosTargetFilter.
Jak uruchomić 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
}