Nguon: Microsoft Learn · .NET 8.0

Chẩn đoán sự cố với proxy dựa trên YARP

Nguồn: Diagnosing proxy issues

Khi sử dụng reverse proxy (proxy ngược), có thêm một bước nhảy từ client (máy khách) đến proxy, và sau đó từ proxy đến destination (đích) - khiến cho mọi thứ có thể gặp sự cố. Chủ đề này sẽ cung cấp một số gợi ý và mẹo để debug (gỡ lỗi) và chẩn đoán sự cố khi chúng xảy ra. Nội dung giả định rằng proxy đang chạy bình thường, và vì vậy không bao gồm các vấn đề xảy ra lúc khởi động như lỗi cấu hình.

Logging (Ghi nhật ký)

Bước đầu tiên để hiểu được những gì đang xảy ra với YARP là bật logging. Đây là một flag cấu hình nên có thể thay đổi linh hoạt. YARP được triển khai như một middleware component (thành phần phần mềm trung gian) cho ASP.NET Core, vì vậy bạn cần bật logging cho cả YARP và ASP.NET để có bức tranh đầy đủ về những gì đang diễn ra.

Theo mặc định, ASP.NET sẽ ghi log ra console (bảng điều khiển), và file cấu hình có thể được sử dụng để kiểm soát mức độ logging.

json
  //Đặt mức Logging cho ASP.NET
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      // Bỏ comment để ẩn các thông điệp chẩn đoán từ runtime và proxy
      // "Microsoft": "Warning",
      // "Yarp" : "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },

Bạn muốn có thông tin logging từ các providers (nhà cung cấp) Microsoft.AspNetCore.\*Yarp.ReverseProxy.\*. Ví dụ trên phát ra các sự kiện ở mức Information từ cả hai providers ra console. Thay đổi mức thành Debug sẽ hiển thị thêm các mục. ASP.NET triển khai phát hiện thay đổi cho các file cấu hình, vì vậy bạn có thể chỉnh sửa file appsettings.json (hoặc appsettings.development.json cho môi trường Development) trong khi dự án đang chạy và quan sát các thay đổi trong đầu ra log.

Các cài đặt trong file appsettings.development.json ghi đè các cài đặt trong appsettings.json khi chạy trong môi trường Development, vì vậy hãy đảm bảo rằng nếu bạn đang chỉnh sửa appsettings.json thì các giá trị không bị ghi đè.

Hiểu các mục log

Đầu ra logging gắn liền trực tiếp với cách ASP.NET Core xử lý các request (yêu cầu). Điều quan trọng cần nhận ra là với vai trò là middleware, YARP đang dựa vào nhiều chức năng của ASP.NET để xử lý các request, ví dụ sau đây là quá trình xử lý một request khi bật chế độ "Debug":

MứcThông điệp LogMô tả
dbugMicrosoft.AspNetCore.Server.Kestrel.Connections[39]Connection id "0HMCD0JK7K51U" accepted.Connections (kết nối) độc lập với request, đây là một kết nối mới
dbugMicrosoft.AspNetCore.Server.Kestrel.Connections[1]Connection id "0HMCD0JK7K51U" started.
infoMicrosoft.AspNetCore.Hosting.Diagnostics[1]Request starting HTTP/1.1 GET http://localhost:5000/ - -Đây là request đến ASP.NET
dbugMicrosoft.AspNetCore.HostFiltering.HostFilteringMiddleware[0]Wildcard detected, all requests with hosts will be allowed.Cấu hình không ràng buộc endpoint với tên hostname cụ thể
dbugMicrosoft.AspNetCore.Routing.Matching.DfaMatcher[1001]1 candidate(s) found for the request path '/'Hiển thị các kết quả khớp có thể có cho route (tuyến đường)
dbugMicrosoft.AspNetCore.Routing.Matching.DfaMatcher[1005]Endpoint 'minimumroute' with route pattern '{\\catch-all}' is valid for the request path '/'Route tối giản từ cấu hình YARP đã khớp
dbugMicrosoft.AspNetCore.Routing.EndpointRoutingMiddleware[1]Request matched endpoint 'minimumroute'
infoMicrosoft.AspNetCore.Routing.EndpointMiddleware[0]Executing endpoint 'minimumroute'
infoYarp.ReverseProxy.Forwarder.HttpForwarder[9]Proxying to http://www.example.com/YARP đang proxy request đến example.com
infoMicrosoft.AspNetCore.Routing.EndpointMiddleware[1]Executed endpoint 'minimumroute'
dbugMicrosoft.AspNetCore.Server.Kestrel.Connections[9]Connection id "0HMCD0JK7K51U" completed keep alive response.Response đã hoàn thành, nhưng kết nối có thể được giữ alive
infoMicrosoft.AspNetCore.Hosting.Diagnostics[2]Request finished HTTP/1.1 GET http://localhost:5000/ - - - 200 1256 text/html;+charset=utf-8 12.7797msResponse hoàn thành với mã trạng thái 200, trả về 1256 bytes dạng text/html trong ~13ms
dbugMicrosoft.AspNetCore.Server.Kestrel.Transport.Sockets[6]Connection id "0HMCD0JK7K51U" received FIN.Thông tin chẩn đoán về kết nối để xác định ai đã đóng nó và đóng như thế nào
dbugMicrosoft.AspNetCore.Server.Kestrel.Connections[10]Connection id "0HMCD0JK7K51U" disconnecting.
dbugMicrosoft.AspNetCore.Server.Kestrel.Connections[2]Connection id "0HMCD0JK7K51U" stopped.
dbugMicrosoft.AspNetCore.Server.Kestrel.Transport.Sockets[7]Connection id "0HMCD0JK7K51U" sending FIN because: "The Socket transport's send loop completed gracefully."

Thông tin trên cung cấp thông tin chung về request và cách nó được xử lý.

Sử dụng ASP.NET request logging

ASP.NET bao gồm một middleware component có thể được sử dụng để cung cấp thêm chi tiết về request và response (phản hồi). Component UseHttpLogging có thể được thêm vào request pipeline (đường ống xử lý request), giúp thêm các mục bổ sung vào log với chi tiết về các header (tiêu đề) của request đến và đi.

csharp
app.UseHttpLogging();
// Bật endpoint routing, bắt buộc cho reverse proxy
app.UseRouting();
// Đăng ký các route của reverse proxy
app.MapReverseProxy();

Ví dụ:

console
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
      Request:
      Protocol: HTTP/1.1
      Method: GET
      Scheme: http
      PathBase:
      Path: /
      Accept: */*
      Host: localhost:5000
      User-Agent: curl/7.55.1
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      StatusCode: 200
      Content-Type: text/html; charset=utf-8
      Date: Tue, 12 Oct 2021 23:29:20 GMT
      Server: ECS,(sec/97A5)
      Age: 113258
      Cache-Control: [Redacted]
      ETag: [Redacted]
      Expires: Tue, 19 Oct 2021 23:29:20 GMT
      Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
      Vary: [Redacted]
      Content-Length: 1256
      X-Cache: [Redacted]

Sử dụng telemetry events (sự kiện đo từ xa)

Chúng tôi khuyến nghị đọc Networking telemetry in .NET như tài liệu cơ bản về cách sử dụng networking telemetry trong .NET.

Metrics sample (mẫu metrics) cho thấy cách lắng nghe các sự kiện từ các providers khác nhau thu thập telemetry (dữ liệu đo từ xa) như một phần của YARP. Quan trọng nhất từ góc độ chẩn đoán là:

Để sử dụng bất kỳ cái nào trong số này, hãy tạo một class triển khai một interface Yarp.Telemetry.Consumption, chẳng hạn như IForwarderTelemetryConsumer:

csharp
public class ForwarderTelemetry : IForwarderTelemetryConsumer
{
    /// Được gọi trước khi chuyển tiếp một request.
    public void OnForwarderStart(DateTime timestamp, string destinationPrefix)
    {
        Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => " +
            $"OnForwarderStart :: Destination prefix: {destinationPrefix}");
    }

    /// Được gọi sau khi chuyển tiếp một request.
    public void OnForwarderStop(DateTime timestamp, int statusCode)
    {
        Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => " +
            $"OnForwarderStop :: Status: {statusCode}");
    }

    /// Được gọi trước <see cref="OnForwarderStop(DateTime, int)"/> nếu chuyển tiếp request thất bại.
    public void OnForwarderFailed(DateTime timestamp, ForwarderError error)
    {
        Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => " +
            $"OnForwarderFailed :: Error: {error.ToString()}");
    }

    /// Được gọi khi đạt đến một giai đoạn nhất định của quá trình chuyển tiếp request.
    public void OnForwarderStage(DateTime timestamp, ForwarderStage stage)
    {
        Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => " +
            $"OnForwarderStage :: Stage: {stage.ToString()}");
    }

    /// Được gọi định kỳ trong khi truyền nội dung đang hoạt động.
    public void OnContentTransferring(DateTime timestamp, bool isRequest, long contentLength,
        long iops, TimeSpan readTime, TimeSpan writeTime)
    {
        Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => " +
            $"OnContentTransferring :: Is request: {isRequest}, Content length: {contentLength}, " +
            $"IOps: {iops}, Read time: {readTime:s\\.fff}, Write time: {writeTime:s\\.fff}");
    }

    /// Được gọi sau khi truyền nội dung request hoặc response.
    public void OnContentTransferred(DateTime timestamp, bool isRequest, long contentLength,
        long iops, TimeSpan readTime, TimeSpan writeTime, TimeSpan firstReadTime)
    {
        Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => " +
            $"OnContentTransferred :: Is request: {isRequest}, Content length: {contentLength}, " +
            $"IOps: {iops}, Read time: {readTime:s\\.fff}, Write time: {writeTime:s\\.fff}");
    }

    /// Được gọi trước khi chuyển tiếp request từ `ForwarderMiddleware`, vì vậy không được gọi trong các tình huống direct forwarding (chuyển tiếp trực tiếp).
    public void OnForwarderInvoke(DateTime timestamp, string clusterId, string routeId,
        string destinationId)
    {
        Console.WriteLine($"Forwarder Telemetry [{timestamp:HH:mm:ss.fff}] => " +
            $"OnForwarderInvoke:: Cluster id: {clusterId}, Route Id: {routeId}, Destination: {destinationId}");
    }
}

Đăng ký class như một phần của services, ví dụ:

csharp
services.AddTelemetryConsumer<ForwarderTelemetry>();

// Thêm khả năng reverse proxy vào server
var proxyBuilder = services.AddReverseProxy();
// Khởi tạo reverse proxy từ mục "ReverseProxy" trong cấu hình
proxyBuilder.LoadFromConfig(Configuration.GetSection("ReverseProxy"));

Chi tiết được ghi log ở từng phần của request, ví dụ:

console
Forwarder Telemetry [06:40:48.186] => OnForwarderInvoke::
    Cluster id: minimumcluster, Route Id: minimumroute, Destination: example.com
Forwarder Telemetry [06:41:00.269] => OnForwarderStart ::
    Destination prefix: http://www.example.com/
Forwarder Telemetry [06:41:00.298] => OnForwarderStage :: Stage: SendAsyncStart
Forwarder Telemetry [06:41:00.507] => OnForwarderStage :: Stage: SendAsyncStop
Forwarder Telemetry [06:41:00.530] => OnForwarderStage :: Stage:
    ResponseContentTransferStart
Forwarder Telemetry [06:41:03.655] => OnForwarderStop :: Status: 200

Các sự kiện telemetry được kích hoạt khi chúng xảy ra, vì vậy bạn có thể lấy HttpContext và feature của YARP từ nó:

csharp
services.AddTelemetryConsumer<ForwarderTelemetry>();
services.AddHttpContextAccessor();
...
public void OnForwarderInvoke(DateTime timestamp, string clusterId, string routeId,
    string destinationId)
{
    var context = new HttpContextAccessor().HttpContext;
    var YarpFeature = context.GetReverseProxyFeature();

    var dests = from d in YarpFeature.AvailableDestinations
        select d.Model.Config.Address;

    Console.WriteLine($"Destinations: {string.Join(", ", dests)}");
}

Sử dụng custom middleware (phần mềm trung gian tùy chỉnh)

Một cách khác để kiểm tra trạng thái của các request là chèn thêm middleware vào request pipeline. Bạn có thể chèn vào giữa các giai đoạn khác để xem trạng thái của request.

csharp
// Chúng ta có thể tùy chỉnh proxy pipeline và thêm/xóa/thay thế các bước
app.MapReverseProxy(proxyPipeline =>
{
    // Sử dụng custom proxy middleware, được định nghĩa bên dưới
    proxyPipeline.Use(MyCustomProxyStep);
    // Đừng quên bao gồm hai middleware này khi tạo custom proxy pipeline (nếu cần).
    proxyPipeline.UseSessionAffinity();
    proxyPipeline.UseLoadBalancing();
});
...
public Task MyCustomProxyStep(HttpContext context, Func<Task> next)
{
    // Có thể đọc dữ liệu từ request qua context
    foreach (var header in context.Request.Headers)
    {
        Console.WriteLine($"{header.Key}: {header.Value}");
    }

    // Context cũng lưu trữ một ReverseProxyFeature chứa dữ liệu proxy cụ thể như cluster, route và destinations
    var proxyFeature = context.GetReverseProxyFeature();
    Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(proxyFeature.Route.Config));

    // Quan trọng - cần thiết để chuyển sang bước tiếp theo trong proxy pipeline
    return next();
}

Bạn cũng có thể sử dụng ASP.NET middleware trong Configure để kiểm tra request trước khi vào proxy pipeline.

Proxy streams (truyền dữ liệu) response từ destination server trở lại client, vì vậy các response header và body không dễ dàng truy cập được qua middleware.

Sử dụng debugger

Một debugger (trình gỡ lỗi), chẳng hạn như Visual Studio, có thể được đính kèm vào proxy process. Tuy nhiên, trừ khi bạn có middleware hiện có, không có nơi tốt nào trong code ứng dụng để đặt breakpoint và kiểm tra trạng thái của request. Do đó, debugger được sử dụng tốt nhất kết hợp với một trong các kỹ thuật trên để bạn có các vị trí rõ ràng để chèn breakpoint.

Network tracing (truy tìm dấu vết mạng)

Có thể hấp dẫn khi sử dụng các công cụ network tracing như Fiddler hoặc Wireshark để cố gắng theo dõi những gì đang xảy ra ở cả hai phía của proxy. Tuy nhiên, hãy thận trọng khi sử dụng cả hai công cụ:

Lựa chọn giao thức cho lưu lượng đi được dựa trên URL destination trong cấu hình cluster. Nếu việc theo dõi lưu lượng được sử dụng để chẩn đoán, việc thay đổi các URL đi thành http://, nếu có thể, có thể là cách đơn giản nhất để cho phép các công cụ theo dõi hoạt động, miễn là các vấn đề đang được chẩn đoán không liên quan đến giao thức truyền tải.