Nguon: Microsoft Learn · .NET 8.0

Kiểm tra sức khỏe destination trong YARP

Nguồn: YARP Destination health checks

Trong hầu hết các hệ thống thực tế, các node (nút mạng) đôi khi gặp sự cố tạm thời hoặc ngừng hoạt động hoàn toàn vì nhiều lý do như quá tải, rò rỉ tài nguyên, lỗi phần cứng, v.v. Cách tiếp cận phản ứng (reactive approach) nhằm giảm thiểu tác động tiêu cực của lỗi đến các yêu cầu client là proxy phân tích sức khỏe từng node và ngừng gửi lưu lượng đến những node không lành mạnh cho đến khi chúng phục hồi. YARP triển khai cách tiếp cận này dưới dạng kiểm tra sức khỏe destination (destination health checks) chủ động (active) và thụ động (passive). Hai cơ chế này độc lập với nhau và được lưu trữ trong các thuộc tính tương ứng của mỗi destination. Trạng thái sức khỏe được khởi tạo với giá trị Unknown và có thể được thay đổi thành Healthy hoặc Unhealthy bởi các policy tương ứng.

Kiểm tra sức khỏe chủ động (Active health checks)

YARP có thể chủ động theo dõi sức khỏe destination bằng cách gửi các yêu cầu thăm dò (probing requests) định kỳ đến các endpoint sức khỏe được chỉ định và phân tích các phản hồi. Quá trình phân tích này được thực hiện bởi một active health check policy được chỉ định cho cluster và dẫn đến việc tính toán trạng thái sức khỏe mới của destination. Cuối cùng, policy đánh dấu mỗi destination là healthy hoặc unhealthy dựa trên mã phản hồi HTTP (2xx được coi là healthy) và xây dựng lại tập hợp destination lành mạnh của cluster.

Có một số cài đặt cấu hình cấp cluster để kiểm soát kiểm tra sức khỏe chủ động, có thể được thiết lập trong file cấu hình hoặc trong code. Một endpoint sức khỏe chuyên dụng cũng có thể được chỉ định cho từng destination.

Ví dụ file cấu hình

json
"Clusters": {
  "cluster1": {
    "HealthCheck": {
      "Active": {
        "Enabled": "true",
        "Interval": "00:00:10",
        "Timeout": "00:00:10",
        "Policy": "ConsecutiveFailures",
        "Path": "/api/health",
        "Query": "?foo=bar"
      }
    },
    "Metadata": {
      "ConsecutiveFailuresHealthPolicy.Threshold": "3"
    },
    "Destinations": {
      "cluster1/destination1": {
        "Address": "https://localhost:10000/"
      },
      "cluster1/destination2": {
        "Address": "http://localhost:10010/",
        "Health": "http://localhost:10020/"
      }
    }
  }
}

Ví dụ code

csharp
var clusters = new[]
{
    new ClusterConfig()
    {
        ClusterId = "cluster1",
        HealthCheck = new HealthCheckConfig
        {
            Active = new ActiveHealthCheckConfig
            {
                Enabled = true,
                Interval = TimeSpan.FromSeconds(10),
                Timeout = TimeSpan.FromSeconds(10),
                Policy = HealthCheckConstants.ActivePolicy.ConsecutiveFailures,
                Path = "/api/health",
                Query = "?foo=bar",
            }
        },
        Metadata = new Dictionary<string, string> { { ConsecutiveFailuresHealthPolicyOptions.ThresholdMetadataName, "5" } },
        Destinations =
        {
            { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } },
            { "destination2", new DestinationConfig() { Address = "https://localhost:10010", Health = "https://localhost:10010" } }
        }
    }
};

Cấu hình

Hầu hết các cài đặt kiểm tra sức khỏe chủ động được chỉ định ở cấp cluster trong phần Cluster/HealthCheck/Active. Ngoại lệ duy nhất là phần tử Destination/Health tùy chọn, chỉ định một endpoint kiểm tra sức khỏe chủ động riêng biệt. URI thăm dò sức khỏe thực tế được xây dựng dựa trên Destination/Address (hoặc Destination/Health khi được thiết lập) + Cluster/HealthCheck/Active/Path.

Cài đặt kiểm tra sức khỏe chủ động cũng có thể được định nghĩa trong code thông qua các kiểu tương ứng trong namespace Yarp.ReverseProxy.Configuration.

Phần Cluster/HealthCheck/ActiveActiveHealthCheckConfig:

Phần DestinationDestinationConfig:

Health: Endpoint thăm dò sức khỏe chuyên dụng, ví dụ http://destination:12345/. Mặc định null, fallback (dự phòng) về Destination/Address.

Các policy tích hợp sẵn

Hiện có một policy kiểm tra sức khỏe chủ động tích hợp sẵn là ConsecutiveFailuresHealthPolicy. Policy này đếm số lần thất bại liên tiếp của yêu cầu thăm dò và đánh dấu destination là unhealthy khi đạt ngưỡng cho trước. Khi nhận được phản hồi thành công đầu tiên, destination được đánh dấu là healthy và bộ đếm được reset. Tham số policy được thiết lập trong metadata của cluster như sau:

ConsecutiveFailuresHealthPolicy.Threshold - số lần thất bại liên tiếp yêu cầu để đánh dấu destination là unhealthy. Mặc định 2.

Thiết kế

Dịch vụ chính trong quá trình này là IActiveHealthCheckMonitor, định kỳ tạo yêu cầu thăm dò thông qua IProbingRequestFactory, gửi chúng đến tất cả DestinationConfig của mỗi ClusterConfig đã bật kiểm tra sức khỏe chủ động, sau đó chuyển tất cả các phản hồi cho một IActiveHealthCheckPolicy được chỉ định cho cluster. IActiveHealthCheckMonitor không tự đưa ra quyết định về việc destination có lành mạnh hay không, mà ủy thác nhiệm vụ này cho IActiveHealthCheckPolicy được chỉ định cho cluster. Policy được gọi để đánh giá các trạng thái sức khỏe mới sau khi hoàn thành thăm dò tất cả destination của cluster. Nó nhận ClusterState đại diện cho trạng thái động của cluster và một tập hợp DestinationProbingResult lưu trữ kết quả thăm dò của các destination. Sau khi đánh giá trạng thái sức khỏe mới cho mỗi destination, policy gọi IDestinationHealthUpdater để thực sự cập nhật các giá trị DestinationHealthState.Active.

code
-{Cho mỗi destination của cluster}-
IActiveHealthCheckMonitor <--(Tạo yêu cầu thăm dò)--> IProbingRequestFactory
        |
        V
 HttpMessageInvoker <--(Gửi thăm dò và nhận phản hồi)--> Destination
        |
(Lưu kết quả thăm dò)
        |
        V
DestinationProbingResult
--------------{KẾT THÚC}---------------
        |
(Đánh giá trạng thái sức khỏe chủ động mới của destination sử dụng kết quả thăm dò)
        |
        V
IActiveHealthCheckPolicy --(Trạng thái sức khỏe chủ động mới)--> IDestinationHealthUpdater --(Cập nhật từng destination)--> DestinationState.Health.Active

Có các triển khai tích hợp sẵn mặc định cho tất cả các thành phần đã đề cập, và chúng cũng có thể được thay thế bằng các triển khai tùy chỉnh khi cần thiết.

Mở rộng (Extensibility)

Có 2 điểm mở rộng chính trong hệ thống kiểm tra sức khỏe chủ động.

IActiveHealthCheckPolicy

IActiveHealthCheckPolicy phân tích cách các destination phản hồi với các yêu cầu thăm dò sức khỏe chủ động được gửi bởi IActiveHealthCheckMonitor, đánh giá các trạng thái sức khỏe chủ động mới cho tất cả các destination được thăm dò, sau đó gọi IDestinationHealthUpdater.SetActive để thiết lập trạng thái sức khỏe chủ động mới và xây dựng lại tập hợp destination lành mạnh dựa trên các giá trị đã cập nhật.

Dưới đây là ví dụ đơn giản về IActiveHealthCheckPolicy tùy chỉnh đánh dấu destination là Healthy nếu nhận được mã phản hồi thành công cho yêu cầu thăm dò, và là Unhealthy trong trường hợp ngược lại.

csharp
public class FirstUnsuccessfulResponseHealthPolicy : IActiveHealthCheckPolicy
{
    private readonly IDestinationHealthUpdater _healthUpdater;

    public FirstUnsuccessfulResponseHealthPolicy(IDestinationHealthUpdater healthUpdater)
    {
        _healthUpdater = healthUpdater;
    }

    public string Name => "FirstUnsuccessfulResponse";

    public void ProbingCompleted(ClusterState cluster, IReadOnlyList<DestinationProbingResult> probingResults)
    {
        if (probingResults.Count == 0)
        {
            return;
        }

        var newHealthStates = new NewActiveDestinationHealth[probingResults.Count];
        for (var i = 0; i < probingResults.Count; i++)
        {
            var response = probingResults[i].Response;
            var newHealth = response is not null && response.IsSuccessStatusCode ? DestinationHealth.Healthy : DestinationHealth.Unhealthy;
            newHealthStates[i] = new NewActiveDestinationHealth(probingResults[i].Destination, newHealth);
        }

        _healthUpdater.SetActive(cluster, newHealthStates);
    }
}

IProbingRequestFactory

IProbingRequestFactory tạo các yêu cầu thăm dò sức khỏe chủ động để gửi đến các endpoint sức khỏe của destination. Nó có thể xem xét ActiveHealthCheckOptions.Path, DestinationConfig.Health và các cài đặt cấu hình khác để xây dựng yêu cầu thăm dò.

IProbingRequestFactory mặc định sử dụng cùng cấu hình HttpRequest như các yêu cầu proxy. Để tùy chỉnh, hãy triển khai IProbingRequestFactory riêng và đăng ký trong DI (Dependency Injection) như sau:

csharp
services.AddSingleton<IProbingRequestFactory, CustomProbingRequestFactory>();

Dưới đây là ví dụ đơn giản về IProbingRequestFactory tùy chỉnh ghép DestinationConfig.Address với một đường dẫn thăm dò sức khỏe cố định để tạo URI thăm dò.

csharp
public class CustomProbingRequestFactory : IProbingRequestFactory
{
    public HttpRequestMessage CreateRequest(ClusterConfig clusterConfig, DestinationConfig destinationConfig)
    {
        var probeUri = new Uri(destinationConfig.Address + "/api/probe-health");
        return new HttpRequestMessage(HttpMethod.Get, probeUri) { Version = ProtocolHelper.Http11Version };
    }
}

Kiểm tra sức khỏe thụ động (Passive health checks)

YARP có thể thụ động theo dõi các thành công và thất bại trong việc proxy yêu cầu client để đánh giá trạng thái sức khỏe của destination một cách phản ứng. Các phản hồi cho các yêu cầu được proxy được chặn bởi một middleware kiểm tra sức khỏe thụ động chuyên dụng, middleware này chuyển chúng đến một policy được cấu hình trên cluster. Policy phân tích các phản hồi để đánh giá xem các destination tạo ra chúng có lành mạnh hay không. Sau đó, nó tính toán và gán các trạng thái sức khỏe thụ động mới cho các destination tương ứng và xây dựng lại tập hợp destination lành mạnh của cluster.

Lưu ý rằng phản hồi thường được gửi đến client trước khi policy kiểm tra sức khỏe thụ động chạy, vì vậy policy không thể chặn nội dung phản hồi hay sửa đổi bất kỳ thứ gì trong header phản hồi trừ khi ứng dụng proxy giới thiệu tính năng đệm (buffering) phản hồi đầy đủ.

Có một điểm khác biệt quan trọng so với logic kiểm tra sức khỏe chủ động. Khi một destination được gán trạng thái thụ động unhealthy, nó sẽ ngừng nhận tất cả lưu lượng mới, điều này ngăn chặn việc tái đánh giá sức khỏe trong tương lai. Policy cũng lên lịch kích hoạt lại destination sau khoảng thời gian được cấu hình. Kích hoạt lại (reactivation) có nghĩa là reset trạng thái sức khỏe thụ động từ Unhealthy về giá trị ban đầu Unknown, làm cho destination đủ điều kiện nhận lưu lượng trở lại.

Có một số cài đặt cấu hình cấp cluster để kiểm soát kiểm tra sức khỏe thụ động.

Ví dụ file cấu hình

json
"Clusters": {
  "cluster1": {
    "HealthCheck": {
      "Passive": {
        "Enabled": "true",
        "Policy": "TransportFailureRate",
        "ReactivationPeriod": "00:02:00"
      }
    },
    "Metadata": {
      "TransportFailureRateHealthPolicy.RateLimit": "0.5"
    },
    "Destinations": {
      "cluster1/destination1": {
        "Address": "https://localhost:10000/"
      },
      "cluster1/destination2": {
        "Address": "http://localhost:10010/"
      }
    }
  }
}

Ví dụ code

csharp
var clusters = new[]
{
    new ClusterConfig()
    {
        ClusterId = "cluster1",
        HealthCheck = new HealthCheckConfig
        {
            Passive = new PassiveHealthCheckConfig
            {
                Enabled = true,
                Policy = HealthCheckConstants.PassivePolicy.TransportFailureRate,
                ReactivationPeriod = TimeSpan.FromMinutes(2)
            }
        },
        Metadata = new Dictionary<string, string> { { TransportFailureRateHealthPolicyOptions.FailureRateLimitMetadataName, "0.5" } },
        Destinations =
        {
            { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } },
            { "destination2", new DestinationConfig() { Address = "https://localhost:10010" } }
        }
    }
};

Cấu hình

Cài đặt kiểm tra sức khỏe thụ động được chỉ định ở cấp cluster trong phần Cluster/HealthCheck/Passive. Ngoài ra, chúng có thể được định nghĩa trong code thông qua các kiểu tương ứng trong namespace Yarp.ReverseProxy.Configuration.

Kiểm tra sức khỏe thụ động yêu cầu PassiveHealthCheckMiddleware được thêm vào pipeline để hoạt động. Phương thức mặc định MapReverseProxy(this IEndpointRouteBuilder endpoints) thực hiện điều này tự động, nhưng trong trường hợp xây dựng pipeline thủ công, phương thức UsePassiveHealthChecks phải được gọi để thêm middleware đó vào như ví dụ dưới đây.

csharp
endpoints.MapReverseProxy(proxyPipeline =>
{
    proxyPipeline.UseAffinitizedDestinationLookup();
    proxyPipeline.UseProxyLoadBalancing();
    proxyPipeline.UseRequestAffinitizer();
    proxyPipeline.UsePassiveHealthChecks();
});

Phần Cluster/HealthCheck/PassivePassiveHealthCheckConfig:

Các policy tích hợp sẵn

Hiện có một policy kiểm tra sức khỏe thụ động tích hợp sẵn là TransportFailureRateHealthPolicy. Policy này tính toán tỷ lệ thất bại của các yêu cầu được proxy cho mỗi destination và đánh dấu nó là unhealthy nếu vượt quá giới hạn được chỉ định. Tỷ lệ được tính là phần trăm các yêu cầu thất bại trên tổng số yêu cầu được proxy đến destination trong khoảng thời gian nhất định. Bộ đếm thất bại và tổng số được theo dõi trong cửa sổ thời gian trượt (sliding time window), nghĩa là chỉ các số liệu gần đây nằm trong cửa sổ mới được tính đến. Có hai tập tham số policy được định nghĩa toàn cục và cấp cluster.

Tham số toàn cục được thiết lập thông qua cơ chế options sử dụng kiểu TransportFailureRateHealthPolicyOptions với các thuộc tính sau:

Tùy chọn policy toàn cục có thể được thiết lập trong code như sau:

csharp
services.Configure<TransportFailureRateHealthPolicyOptions>(o =>
{
    o.DetectionWindowSize = TimeSpan.FromSeconds(30);
    o.MinimalTotalCountThreshold = 5;
    o.DefaultFailureRateLimit = 0.5;
});

Tham số cụ thể của cluster được thiết lập trong metadata của cluster: TransportFailureRateHealthPolicy.RateLimit - giới hạn tỷ lệ thất bại cho destination bị đánh dấu unhealthy. Giá trị trong khoảng (0,1). Giá trị mặc định được cung cấp bởi tham số toàn cục DefaultFailureRateLimit.

Thiết kế

Thành phần chính là PassiveHealthCheckMiddleware nằm trong request pipeline và phân tích các phản hồi được trả về bởi các destination. Với mỗi phản hồi từ destination thuộc cluster đã bật kiểm tra sức khỏe thụ động, PassiveHealthCheckMiddleware gọi IPassiveHealthCheckPolicy được chỉ định cho cluster. Policy phân tích phản hồi đã cho, đánh giá trạng thái sức khỏe thụ động mới của destination và gọi IDestinationHealthUpdater để thực sự cập nhật giá trị DestinationHealthState.Passive. Cập nhật xảy ra bất đồng bộ trong nền và không chặn request pipeline. Khi destination bị đánh dấu unhealthy, nó ngừng nhận yêu cầu mới cho đến khi được kích hoạt lại sau khoảng thời gian được cấu hình.

code
      (Phản hồi cho yêu cầu được proxy)
                  |
      PassiveHealthCheckMiddleware
                  |
                  V
      IPassiveHealthCheckPolicy
                  |
    (Đánh giá trạng thái sức khỏe thụ động mới)
                  |
      IDestinationHealthUpdater --(Cập nhật trạng thái thụ động bất đồng bộ)--> DestinationState.Health.Passive
                  |
                  V
      (Lên lịch kích hoạt lại) --(Đặt về Unknown)--> DestinationState.Health.Passive

Mở rộng (Extensibility)

Có một điểm mở rộng chính trong hệ thống kiểm tra sức khỏe thụ động, đó là IPassiveHealthCheckPolicy.

IPassiveHealthCheckPolicy

IPassiveHealthCheckPolicy phân tích cách destination phản hồi với một yêu cầu client được proxy, đánh giá trạng thái sức khỏe thụ động mới của nó và cuối cùng gọi IDestinationHealthUpdater.SetPassiveAsync để tạo một task bất đồng bộ thực sự cập nhật trạng thái sức khỏe thụ động và xây dựng lại tập hợp destination lành mạnh.

Dưới đây là ví dụ đơn giản về IPassiveHealthCheckPolicy tùy chỉnh đánh dấu destination là Unhealthy khi phản hồi không thành công đầu tiên cho yêu cầu được proxy.

csharp
public class FirstUnsuccessfulResponseHealthPolicy : IPassiveHealthCheckPolicy
{
    private static readonly TimeSpan _defaultReactivationPeriod = TimeSpan.FromSeconds(60);
    private readonly IDestinationHealthUpdater _healthUpdater;

    public FirstUnsuccessfulResponseHealthPolicy(IDestinationHealthUpdater healthUpdater)
    {
        _healthUpdater = healthUpdater;
    }

    public string Name => "FirstUnsuccessfulResponse";

    public void RequestProxied(HttpContext context, ClusterState cluster, DestinationState destination)
    {
        var error = context.Features.Get<IForwarderErrorFeature>();
        if (error is not null)
        {
            var reactivationPeriod = cluster.Model.Config.HealthCheck?.Passive?.ReactivationPeriod ?? _defaultReactivationPeriod;
            _healthUpdater.SetPassive(cluster, destination, DestinationHealth.Unhealthy, reactivationPeriod);
        }
    }
}

Tập hợp destination khả dụng (Available destination collection)

Trạng thái sức khỏe của destination được dùng để xác định destination nào đủ điều kiện nhận các yêu cầu được proxy. Mỗi cluster duy trì danh sách destination khả dụng của riêng nó trong thuộc tính AvailableDestinations của kiểu ClusterDestinationState. Danh sách đó được xây dựng lại khi trạng thái sức khỏe của bất kỳ destination nào thay đổi. IClusterDestinationsUpdater kiểm soát quá trình đó và gọi một IAvailableDestinationsPolicy được cấu hình trên cluster để thực sự chọn các destination khả dụng từ tất cả destination của cluster. Các policy tích hợp sẵn sau đây được cung cấp và có thể triển khai các policy tùy chỉnh nếu cần.

LƯU Ý: Policy destination khả dụng được cấu hình trên cluster sẽ luôn được gọi bất kể có kiểm tra sức khỏe nào được bật trên cluster hay không. Trạng thái sức khỏe của kiểm tra sức khỏe bị tắt được đặt thành Unknown.

Cấu hình

Ví dụ file cấu hình

json
"Clusters": {
  "cluster1": {    
    "HealthCheck": {
      "AvailableDestinationsPolicy": "HealthyOrPanic",
      "Passive": {
        "Enabled": "true"
      }
    },
    "Destinations": {
      "cluster1/destination1": {
        "Address": "https://localhost:10000/"
      },
      "cluster1/destination2": {
        "Address": "http://localhost:10010/"
      }
    }
  }
}

Ví dụ code

csharp
var clusters = new[]
{
    new ClusterConfig()
    {
        ClusterId = "cluster1",
        HealthCheck = new HealthCheckConfig
        {
            AvailableDestinationsPolicy = HealthCheckConstants.AvailableDestinations.HealthyOrPanic,
            Passive = new PassiveHealthCheckConfig
            {
                Enabled = true
            }
        },
        Destinations =
        {
            { "destination1", new DestinationConfig() { Address = "https://localhost:10000" } },
            { "destination2", new DestinationConfig() { Address = "https://localhost:10010" } }
        }
    }
};