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.
//Đặ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.\* và 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ức | Thông điệp Log | Mô tả |
|---|---|---|
| dbug | Microsoft.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 |
| dbug | Microsoft.AspNetCore.Server.Kestrel.Connections[1]Connection id "0HMCD0JK7K51U" started. | |
| info | Microsoft.AspNetCore.Hosting.Diagnostics[1]Request starting HTTP/1.1 GET http://localhost:5000/ - - | Đây là request đến ASP.NET |
| dbug | Microsoft.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ể |
| dbug | Microsoft.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) |
| dbug | Microsoft.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 |
| dbug | Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[1]Request matched endpoint 'minimumroute' | |
| info | Microsoft.AspNetCore.Routing.EndpointMiddleware[0]Executing endpoint 'minimumroute' | |
| info | Yarp.ReverseProxy.Forwarder.HttpForwarder[9]Proxying to http://www.example.com/ | YARP đang proxy request đến example.com |
| info | Microsoft.AspNetCore.Routing.EndpointMiddleware[1]Executed endpoint 'minimumroute' | |
| dbug | Microsoft.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 |
| info | Microsoft.AspNetCore.Hosting.Diagnostics[2]Request finished HTTP/1.1 GET http://localhost:5000/ - - - 200 1256 text/html;+charset=utf-8 12.7797ms | Response hoàn thành với mã trạng thái 200, trả về 1256 bytes dạng text/html trong ~13ms |
| dbug | Microsoft.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 |
| dbug | Microsoft.AspNetCore.Server.Kestrel.Connections[10]Connection id "0HMCD0JK7K51U" disconnecting. | |
| dbug | Microsoft.AspNetCore.Server.Kestrel.Connections[2]Connection id "0HMCD0JK7K51U" stopped. | |
| dbug | Microsoft.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.
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ụ:
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à:
ForwarderTelemetryConsumerHttpClientTelemetryConsumer
Để 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:
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ụ:
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ụ:
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: 200Cá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ó:
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.
// 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ụ:
- Fiddler tự đăng ký làm proxy và dựa vào các ứng dụng sử dụng proxy mặc định để có thể theo dõi lưu lượng. Điều này hoạt động cho lưu lượng đến từ trình duyệt đến YARP nhưng sẽ không bắt được các request đi, vì YARP được cấu hình để không sử dụng cài đặt proxy cho lưu lượng đi.
- Trên Windows, Wireshark sử dụng Npcap để thu thập dữ liệu packet cho lưu lượng mạng, vì vậy nó bắt được cả lưu lượng vào và ra và có thể được sử dụng để theo dõi HTTP traffic.
- HTTPS traffic được mã hóa và không thể tự động giải mã bởi các công cụ theo dõi mạng. Mỗi công cụ có các giải pháp thay thế có thể cho phép theo dõi lưu lượng, nhưng chúng yêu cầu sử dụng certificate (chứng chỉ) rủi ro và thay đổi các mối quan hệ tin cậy. Vì YARP đang thực hiện các request đi, các kỹ thuật để đánh lừa trình duyệt không áp dụng cho quy trình YARP.
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.