Menginduksi Chaos terkontrol dalam klaster Fabric Servis

Sistem terdistribusi skala besar seperti infrastruktur cloud pada dasarnya tidak dapat diandalkan. Azure Service Fabric memungkinkan pengembang untuk menulis layanan terdistribusi yang andal di atas infrastruktur yang tidak dapat diandalkan. Untuk menulis layanan terdistribusi yang kuat di atas infrastruktur yang tidak dapat diandalkan, pengembang harus dapat menguji stabilitas layanan mereka sementara infrastruktur yang tidak dapat diandalkan sedang mengalami transisi status yang rumit karena kesalahan.

Layanan Cluster Analysis dan Fault Injection (juga dikenal sebagai Layanan Fault Analysis) memberi pengembang kemampuan untuk menyebabkan kesalahan untuk menguji layanan mereka. Kesalahan simulasi yang ditargetkan ini, seperti merestart partisi, dapat membantu menjalankan transisi status yang paling umum. Namun kesalahan simulasi yang ditargetkan bias menurut definisi dan dengan demikian mungkin melewatkan bug yang hanya muncul dalam urutan transisi status yang sulit diprediksi, panjang dan rumit. Untuk pengujian yang tidak bias, Anda dapat menggunakan Chaos.

Chaos mensimulasikan kesalahan berkala dan berselang-seling (baik ditoleransi maupun tidak ditoleransi) di seluruh klaster dalam waktu yang lama. Kesalahan yang ditoleransi terdiri dari sekumpulan panggilan API Service Fabric, misalnya, kesalahan restart replika adalah kesalahan lancar karena ini adalah penutupan diikuti oleh pembukaan pada replika. Hapus replika, pindahkan replika utama, pindahkan replika sekunder, dan pindahkan contoh adalah kesalahan yang ditoleransi lainnya yang dilakukan oleh Chaos. Kesalahan yang tidak ditoleransi adalah proses keluar, seperti simpul restart dan paket kode restart.

Ketika Anda mengonfigurasi Chaos dengan rate dan jenis kesalahan, Anda dapat memulai Chaos melalui C#, Powershell, atau REST API untuk mulai menghasilkan kesalahan di kluster dan di layanan Anda. Anda dapat mengonfigurasi Chaos agar berjalan selama jangka waktu tertentu (misalnya, selama satu jam), ketika Chaos berhenti secara otomatis, atau Anda dapat memanggil API StopChaos (C#, Powershell, atau REST) ​​untuk menghentikannya kapan saja.

Catatan

Dalam bentuknya saat ini, Chaos hanya menyebabkan kesalahan yang aman, yang menyiratkan bahwa dengan tidak adanya kesalahan eksternal, kehilangan kuorum, atau kehilangan data tidak pernah terjadi.

Saat Chaos sedang berjalan, ia menghasilkan peristiwa berbeda yang menangkap keadaan berjalan saat ini. Misalnya, ExecutingFaultsEvent berisi semua kesalahan yang telah diputuskan oleh Chaos untuk dieksekusi dalam iterasi itu. ValidationFailedEvent berisi detail kegagalan validasi (masalah kesehatan atau stabilitas) yang ditemukan selama validasi klaster. Anda dapat menjalankan GetChaosReport API (C#, Powershell, atau REST) ​​untuk mendapatkan laporan Chaos berjalan. Peristiwa ini disimpan dalam kamus yang andal, yang memiliki kebijakan pemotongan yang ditentukan oleh dua konfigurasi: MaxStoredChaosEventCount (nilai default adalah 25000) dan StoredActionCleanupIntervalInSeconds (nilai default adalah 3600). Setiap pemeriksaan Chaos StoredActionCleanupIntervalInSeconds dan semua kecuali peristiwa MaxStoredChaosEventCount terbaru, dihapus dari kamus yang dapat diandalkan.

Kesalahan diinduksi dalam Chaos

Chaos menghasilkan kesalahan di seluruh klaster Service Fabric dan memampatkan kesalahan yang terlihat dalam beberapa bulan atau tahun menjadi beberapa jam. Kombinasi kesalahan yang disisipkan dengan tingkat kesalahan tinggi menemukan kasus sudut yang mungkin terlewatkan. Latihan Chaos ini mengarah pada peningkatan yang signifikan dalam kualitas kode layanan.

Chaos menyebabkan kesalahan dari kategori berikut:

  • Merestart node
  • Merestart paket kode yang disebarkan
  • Menghapus replika
  • Merestart replika
  • Memindahkan replika utama (dapat dikonfigurasi)
  • Memindahkan replika sekunder (dapat dikonfigurasi)
  • Memindahkan instans

Chaos berjalan dalam beberapa iterasi. Setiap iterasi terdiri dari kesalahan dan validasi klaster untuk periode yang ditentukan. Anda dapat mengonfigurasi waktu yang dihabiskan agar klaster stabil dan validasi berhasil. Jika ditemukan kegagalan dalam validasi klaster, Chaos menghasilkan dan mempertahankan ValidationFailedEvent dengan stempel waktu UTC dan detail kegagalan. Misalnya, pertimbangkan instans Chaos yang diatur untuk berjalan selama satu jam dengan maksimum tiga kesalahan bersamaan. Chaos menyebabkan tiga kesalahan, dan kemudian memvalidasi kesehatan klaster. Hal ini mengulangi langkah sebelumnya hingga secara eksplisit dihentikan melalui API StopChaosAsync atau satu jam berlalu. Jika klaster menjadi tidak sehat dalam iterasi apa pun (yaitu, klaster tidak stabil atau tidak menjadi sehat dalam MaxClusterStabilizationTimeout yang diteruskan), Chaos menghasilkan ValidationFailedEvent. Peristiwa ini menunjukkan bahwa ada yang tidak beres dan mungkin perlu probe lebih lanjut.

Untuk mendapatkan kesalahan mana yang diinduksi oleh Chaos, Anda dapat menggunakan GetChaosReport API (PowerShell, C#, atau REST). API mendapatkan segmen berikutnya dari laporan Chaos berdasarkan token kelanjutan yang diteruskan atau rentang waktu yang diteruskan. Anda dapat menentukan ContinuationToken untuk mendapatkan segmen berikutnya dari laporan Chaos atau Anda dapat menentukan rentang waktu melalui StartTimeUtc dan EndTimeUtc, tetapi Anda tidak dapat menentukan ContinuationToken dan rentang waktu dalam panggilan yang sama. Jika ada lebih dari 100 peristiwa Chaos, laporan Chaos dikembalikan dalam segmen yang segmennya berisi tidak lebih dari 100 peristiwa Chaos.

Opsi konfigurasi penting

  • TimeToRun: Total waktu yang dijalankan Chaos sebelum selesai dengan kesuksesan. Anda dapat menghentikan Chaos sebelum dijalankan selama periode TimeToRun melalui API StopChaos.

  • MaxClusterStabilizationTimeout: Jumlah waktu maksimum untuk menunggu klaster menjadi sehat sebelum menghasilkan ValidationFailedEvent. Penantian ini untuk mengurangi beban pada klaster saat sedang memulihkan. Pemeriksaan yang dilakukan adalah:

    • Jika kesehatan klaster OK
    • Jika kesehatan layanan OK
    • Jika ukuran set replika target tercapai untuk partisi layanan
    • Tidak ada replika InBuild
  • MaxConcurrentFaults: Jumlah maksimum kesalahan serentak yang diinduksi di setiap iterasi. Semakin tinggi angkanya, semakin agresif Chaos dan failover serta kombinasi transisi status yang dilalui klaster juga lebih kompleks.

Catatan

Terlepas dari seberapa tinggi nilai yang dimiliki MaxConcurrentFaults, Chaos menjamin - jika tidak ada kesalahan eksternal - tidak ada kehilangan kuorum atau kehilangan data.

  • EnableMoveReplicaFaults: Mengaktifkan atau menonaktifkan kesalahan yang menyebabkan pemindahan replika utama, sekunder, atau instans. Kesalahan ini diaktifkan secara default.
  • WaitTimeBetweenIterations: Jumlah waktu tunggu di antara iterasi. Artinya, jumlah waktu Chaos akan berhenti aturah menjalankan putaran kesalahan dan menyelesaikan validasi kesehatan klaster yang sesuai. Semakin tinggi nilainya, semakin rendah laju injeksi kesalahan rata-rata.
  • WaitTimeBetweenFaults: Jumlah waktu menunggu di antara dua kesalahan yang berurutan dalam satu iterasi. Semakin tinggi nilainya, semakin rendah konkurensi (atau tumpang tindih antara) kesalahan.
  • ClusterHealthPolicy: Kebijakan kesehatan klaster digunakan untuk memvalidasi kesehatan klaster di antara iterasi Chaos. Jika kesehatan klaster dalam kesalahan atau jika pengecualian tak terduga terjadi selama eksekusi kesalahan, Chaos akan menunggu selama 30 menit sebelum probe kesehatan berikutnya - untuk memberi klaster waktu pemulihan.
  • Konteks: Kumpulan pasangan nilai-kunci jenis (string, string). Peta tersebut dapat digunakan untuk mencatat informasi tentang Chaos berjalan. Tidak boleh ada lebih dari 100 pasangan seperti itu dan setiap string (key atau nilai) maksimal 4095 karakter. Peta ini diatur oleh starter dari proses Chaos untuk secara opsional menyimpan konteks tentang proses tertentu.
  • ChaosTargetFilter: Filter ini dapat digunakan untuk menargetkan kesalahan Chaos hanya untuk jenis node tertentu atau hanya untuk instans aplikasi tertentu. Jika ChaosTargetFilter tidak digunakan, Chaos akan merusak semua entitas klaster. Jika ChaosTargetFilter digunakan, Chaos hanya merusak entitas yang memenuhi spesifikasi ChaosTargetFilter. NodeTypeInclusionList dan ApplicationInclusionList hanya mengizinkan semantik gabungan. Dengan kata lain, tidak mungkin menentukan perpotongan NodeTypeInclusionList dan ApplicationInclusionList. Misalnya, tidak dimungkinkan untuk menentukan "kesalahan aplikasi ini hanya ketika aplikasi berada pada jenis node itu." Setelah entitas disertakan dalam NodeTypeInclusionList atau ApplicationInclusionList, entitas tersebut tidak dapat dikecualikan menggunakan ChaosTargetFilter. Bahkan jika applicationX tidak muncul di ApplicationInclusionList, di beberapa iterasi Chaos applicationX dapat disalahkan karena kebetulan berada di node nodeTypeY yang termasuk dalam NodeTypeInclusionList. Jika NodeTypeInclusionList dan ApplicationInclusionList nihil atau kosong, ArgumentException akan ditampilkan.
    • NodeTypeInclusionList: Daftar jenis node untuk disertakan dalam kesalahan Chaos. Semua jenis kesalahan (restart node, restart paket kode, hapus replika, restart replika, pindahkan utama, pindahkan sekunder, dan pindahkan contoh) diaktifkan untuk node dari jenis node ini. Jika nodetype (katakanlah NodeTypeX) tidak muncul di NodeTypeInclusionList, maka kesalahan tingkat node (seperti NodeRestart) tidak akan pernah diaktifkan untuk node NodeTypeX, tetapi kesalahan paket kode dan replika masih dapat diaktifkan untuk NodeTypeX jika aplikasi di ApplicationInclusionList kebetulan berada di node NodeTypeX. Maksimal 100 nama jenis node dapat disertakan dalam daftar ini, untuk menambah jumlah ini, diperlukan peningkatan konfigurasi untuk konfigurasi MaxNumberOfNodeTypesInChaosTargetFilter.
    • ApplicationInclusionList: Daftar URI aplikasi untuk disertakan dalam kesalahan Chaos. Semua replika milik layanan aplikasi ini dapat menerima kesalahan replika (restart replika, hapus replika, pindahkan utama, pindahkan sekunder, dan pindahkan instance) oleh Chaos. Chaos dapat merestart paket kode hanya jika paket kode tersebut menampung replika dari aplikasi ini saja. Jika sebuah aplikasi tidak muncul dalam daftar, hal ini masih bisa disalahkan dalam beberapa iterasi Chaos jika aplikasi berakhir pada node dari jenis node yang termasuk dalam NodeTypeInclusionList. Namun jika applicationX terkait dengan nodeTypeY melalui batasan penempatan dan applicationX tidak ada di ApplicationInclusionList dan nodeTypeY tidak ada di NodeTypeInclusionList, maka applicationX tidak akan pernah disalahkan. Maksimal 1000 nama aplikasi dapat dimasukkan dalam daftar ini, untuk menambah jumlah nama tersebut, diperlukan upgrade konfigurasi untuk konfigurasi MaxNumberOfApplicationsInChaosTargetFilter.

Bagaimana menjalankan 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
}