Ba thành phần cơ bản của nền tảng Reusable .NET Microservice

Chiptl
2038
19-11-2021
Ba thành phần cơ bản của nền tảng Reusable .NET Microservice

Các nhóm phát triển thường xuyên cần xây dựng các microservices mới để thêm chức năng mới hoặc thay thế các microservices hiện có. Tuy nhiên, microservices phải hỗ trợ một số tính năng tiêu chuẩn như cung cấp thông tin chi tiết về tình trạng của chúng thông qua ghi nhật ký, cho phép giám sát và tuân theo các tiêu chuẩn bảo mật của tổ chức. Nền tảng reusable microservices có thể giúp các developer bắt đầu quá trình phát triển bằng cách cung cấp các thành phần reusable mà họ có thể sử dụng để xây dựng các microservices mới.

Để triển khai nền tảng reusable microservices, bạn có thể sử dụng mẫu sidecar hoặc xây dựng các gói NuGet được cài đặt trong mọi microservice. Giám sát (monitoring), truy tìm (tracing) và ghi nhật ký (logging) là những thành phần quan trọng phải tồn tại trong nền tảng của bạn. Tuy nhiên, bạn có thể xây dựng nhiều thành phần nền tảng khác để thực hiện các chính sách kỹ thuật tiêu chuẩn như xử lý kết nối database, retries, timeout, v.v.

Tại thời điểm này, một câu hỏi mà bạn có thể đang suy nghĩ là bạn nên đưa bao nhiêu vào nền tảng của mình? Mặc dù không có câu trả lời tiêu chuẩn cho câu hỏi, hãy cân nhắc những điểm sau khi bạn cần quyết định có nên thêm một tính năng vào nền tảng hay không:

  • Tác động đối với các dịch vụ không chạy cùng một phiên bản của nền tảng là gì?
  • Nếu nền tảng cần được đồng bộ hóa trên các dịch vụ và thường xuyên thay đổi, nó có thể trở thành một nút thắt phát triển.
  • Có phải phần lớn các dịch vụ sẽ sử dụng tính năng này không?

Cố gắng đạt được điểm tốt giữa việc triển khai các mối quan tâm xuyên suốt và khả năng triển khai các thay đổi nền tảng tăng dần. Việc triển khai nền tảng như vậy sẽ đảm bảo rằng các nhà phát triển có thể tập trung vào logic kinh doanh và không phải lo lắng về các mối quan tâm xuyên suốt và bạn có thể phát triển nền tảng mà không làm gián đoạn các nhóm phát triển.

Thành phần nền tảng

Bây giờ chúng tôi hiểu rằng có nhiều thứ để tạo một microservice mới hơn là chỉ tạo một dự án mới ASP.NET Core trống rỗng, chúng ta sẽ xây dựng ba thành phần sau đây sẽ có sẵn cho mọi microservice:

  1. Structured logging (Ghi nhật ký có cấu trúc)
  2. Health check (Kiểm tra sức khỏe)
  3. Tracing (Truy tìm)

Mục tiêu của chúng ta là chuẩn hóa các tính năng xuyên suốt và cho phép các developer sử dụng các thành phần nền tảng với nỗ lực tối thiểu. Một microservice mới sẽ chỉ phải cài đặt gói NuGet tùy chỉnh Microservice.Platform và một chút mã khởi động để sử dụng nền tảng. Đừng cảm thấy bị ràng buộc bởi phạm vi triển khai này và xác định các khu vực mà bạn muốn chuẩn hóa trên các microservice của mình. Ví dụ: bạn có thể muốn chuẩn hóa cách thông tin cá nhân được biên tập lại từ nhật ký, cách xử lý bộ nhớ đệm đầu ra, cách xử lý các kết nối cơ sở dữ liệu, v.v.

Mã nguồn

Sau đây là mã nguồn của nền tảng microservices để bạn tham khảo:

Source Code

Thành phần 1: Structured Logging

Chúng ta sẽ sử dụng Serilog để triển khai thành phần logging. Serilog là một thư viện logging có cấu trúc cho .NET. Serilog enrichers được sử dụng để làm phong phú các log event với thông tin bổ sung. Các enricher có thể được chỉ định bằng cách sử dụng Enrich.With fluent API của Serilog LoggerConfiguration. Chúng ta sẽ sử dụng các enricher sau trong quá trình triển khai của mình:

  1. Log context enricher: enricher này được sử dụng để thêm và xóa các thuộc tính khỏi log context xung quanh một cách linh động.
  2. Span enricher: enricher này thêm số nhận dạng duy nhất của span, số nhận dạng duy nhất của span cha, số nhận dạng theo dõi ASP.NET vào log event.

Tạo một thư viện lớp .NET Core tên Microservices.Platform và thêm các gói NuGet Microsoft.Extensions.Hosting, Serilog.AspNetCore và Serilog.Enrichers.Span với nó. Thêm một lớp có tên HostBuilderExtensions vào project và thêm đoạn code dưới đây vào đó:

public static IHostBuilder UseLogging(this IHostBuilder builder)

{

   return builder.UseSerilog((context, logger) =>

    {

        logger

            .Enrich.FromLogContext()

            .Enrich.WithSpan();

        if (context.HostingEnvironment.IsDevelopment())

        {

            logger.WriteTo.Console(

                outputTemplate:

                "{Timestamp:yyyy-MM-dd HH:mm:ss} {TraceId} {Level:u3} {Message}{NewLine}{Exception}");

        }

        else

        {

            logger.WriteTo.Console(new JsonFormatter());

        }

    });

}

Danh sách mã tóm tắt cấu hình logging từ các microservices và làm cho nó khả dụng thông qua một phương thức mở rộng thuận tiện được gọi là UseLogging. Chúng ta có thể gọi phương thức này từ lớp Program của mỗi microservice như sau:

CreateHostBuilder(args).Build().Run();

static IHostBuilder CreateHostBuilder(string[] args) =>

  Host.CreateDefaultBuilder(args)

    .UseLogging()

    .ConfigureWebHostDefaults(

        webBuilder => { webBuilder.UseStartup(); });

Khởi chạy ứng dụng kiểm thử microservice và tạo log từ nó. Bạn có thể quan sát các structured log trong bảng điều khiển đầu ra như sau:

Ba thành phần cơ bản của nền tảng Reusable .NET Microservice - Ảnh 1.

Application logs

Để tạo một gói thư viện này, hãy sử dụng lệnh dotnet pack như sau:

dotnet pack -c release

Lệnh tạo một gói NuGet có tên Microservices.Platform trong thư mục bin/Release. Vì gói của chúng ta phụ thuộc vào các gói NuGet khác, các phần phụ thuộc sẽ được tự động cài đặt bất cứ khi nào nó được cài đặt trong một dự án.

Thành phần 2: Health check (kiểm tra tình trạng)

Chúng ta sẽ sử dụng tính năng health check của ASP.NET Core để triển khai hai điểm cuối giám sát như sau:

  1. /health/live phản hồi mọi yêu cầu bằng mã trạng thái HTTP 200 OK để cho biết rằng dịch vụ hoạt động tốt và có thể xử lý các yêu cầu.
  2. /health/startup thực hiện kiểm tra tình trạng cơ bản và phản hồi với HTTP 200 OK nếu kiểm tra tình trạng thành công. Nếu kiểm tra tình trạng không thành công, dịch vụ sẽ phản hồi với dịch vụ HTTP 503 Không khả dụng.

Có hai phần để thêm kiểm tra tình trạng (health check) trong ứng dụng ASP.NET Core. Đầu tiên, thêm kiểm tra tình trạng service collection, và tiếp theo, định cấu hình kiểm tra tình trạng trong trình tạo ứng dụng. Hãy bắt đầu với việc thêm một lớp có tên ServiceCollectionExtensions vào dự án nền tảng. Điền vào lớp bằng mã sau đây để thực hiện kiểm tra tình trạng cơ bản và hai phương pháp tiện ích để thêm các kiểm tra tình trạng khác:

public static class ServiceCollectionExtensions

{

    private const string Liveness = "liveness";

    private const string Startup = "startup";

    public static IServiceCollection AddBasicHealthChecks(

        this IServiceCollection services)

    {

        services.AddHealthChecks()

            .AddCheck("BasicStartupHealthCheck",

                () => HealthCheckResult.Healthy(), new[] {Startup})

            .AddCheck("BasicLivenessHealthCheck",

                () => HealthCheckResult.Healthy(), new[] {Liveness});

        return services;

    }

    public static IServiceCollection AddAdditionStartupHealthChecks(

        this IServiceCollection services) where T : class, IHealthCheck

    {

        services.AddHealthChecks().AddCheck(nameof(T), tags: new[]

        {

            Startup

        });

        return services;

    }

    public static IServiceCollection AddAdditionLivenessHealthChecks(

        this IServiceCollection services) where T : class, IHealthCheck

    {

        services.AddHealthChecks().AddCheck(nameof(T), tags: new[]

        {

            Liveness

        });

        return services;

    }

}

Bước tiếp theo là thêm kiểm tra tình trạng vào trình tạo ứng dụng. Tạo một lớp có tên ApplicationBuilderExtension và thêm mã sau vào nó:

public static class ApplicationBuilderExtensions

{

    public static IApplicationBuilder UseKubernetesHealthChecks(this IApplicationBuilder app)

    {

        return

            app

                .UseHealthChecks("/health/startup",

                    new HealthCheckOptions {Predicate = x => x.Tags.Contains("startup")})

                .UseHealthChecks("/health/live",

                    new HealthCheckOptions {Predicate = x => x.Tags.Contains("liveness")});

    }

}

Các tag “startup” và “liveness” được sử dụng để xác định điểm cuối mà một kiểm tra thuộc về. Ngoài ra, các tag đảm bảo rằng các kiểm tra tình trạng bổ sung được thêm vào nhóm kiểm tra chính xác được thực thi trên lệnh gọi của điểm cuối startup và liveness.

Sau đây là cách triển khai health check giả để chứng minh cách một microservice có thể mở rộng chức năng health check:

public class DummyStartupCheck : IHealthCheck

{

    public Task CheckHealthAsync(HealthCheckContext context,

        CancellationToken cancellationToken = new()) => Task.FromResult(new HealthCheckResult(HealthStatus.Healthy));

}

Bạn có thể thêm kiểm tra tình trạng này vào service collection của microservice như sau:

public void ConfigureServices(IServiceCollection services)

{

    // Other service registrations

    services.AddBasicHealthChecks()

        .AddAdditionStartupHealthChecks();

}

Cuối cùng, chúng ta sẽ tận dụng tiện ích mở rộng trình tạo ứng dụng đã xây dựng trước đó để kích hoạt DummyStartupCheck trong điểm cuối giám sát tiêu chuẩn của microservice như sau:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

{

    app.UseRouting();

    app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

    app.UseKubernetesHealthChecks();

}

Hãy khởi chạy ứng dụng microservice và xác minh phản hồi từ tình trạng điểm cuối như sau:

Ba thành phần cơ bản của nền tảng Reusable .NET Microservice - Ảnh 2.

Thành phần 3: Tracing (Theo dõi)

Thành phần thứ ba mà bạn nên chuẩn hóa trên các microservices là theo dõi ứng dụng (tracing). Theo dõi phân tán cho phép bạn theo dõi việc thực hiện một yêu cầu trên nhiều dịch vụ. Tương quan yêu cầu xảy ra thông qua header traceparent như được chỉ định trong W3C Trace Context standard. Dưới đây là định dạng của header traceparent tương thích W3C:

traceparent: <version>-<trace-id>-<span-id>-<trace flags>

Trace id được tạo bởi service đầu tiên nhận được yêu cầu. Mỗi service tạo ra một id span mới cho mỗi yêu cầu và do đó, id span trong header sẽ thay đổi khi yêu cầu di chuyển qua hệ thống.

ASP.NET Core thực thi tiêu chuẩn trace context ra khỏi khung, vì vậy nó sẽ tìm kiếm các header traceparent trong các yêu cầu đến và truyền trace id cho các yêu cầu gửi đi được thực hiện cùng HTTPClient. Nếu không có header traceparent trong yêu cầu đến, ASP.NET Core sẽ tạo mới trace id và id span và thêm header trace context vào yêu cầu gửi đi.

Trong .NET Core, thiết bị theo dõi phân tán được thêm vào ứng dụng thông qua các lớp System.Diagnostics.ActivitySource và System.Diagnostics.Activity. Các yêu cầu HTTP trong ASP.NET có một hoạt động liên kết ( Activity) được tạo ra khi yêu cầu được nhận và bị hủy khi yêu cầu được hoàn thành. Hoạt động được truyền cho các yêu cầu gửi đi được thực hiện với HTTPClient. Bạn có thể sử dụng lớp Activity để tạo khoảng từ ActivitySource và ghi lại thông tin span như event, tag và baggage.

Để chuẩn hóa thiết bị theo dõi phân tán trên các microservices, hãy thêm một phương thức mới được đặt tên AddOtelServices cho lớp HostBuilderExtensions như sau

public static IServiceCollection AddOtelServices(this IServiceCollection services, string applicationName)

{

    Activity.DefaultIdFormat = ActivityIdFormat.W3C;

    services.AddOpenTelemetryTracing(builder => builder

        .AddAspNetCoreInstrumentation()

        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(applicationName))

        .AddHttpClientInstrumentation(options => options.SetHttpFlavor = true)

        .SetSampler(new AlwaysOnSampler())

        .AddConsoleExporter(options => options.Targets = ConsoleExporterOutputTargets.Console));

    return services;

}

Phương pháp AddAspNetCoreInstrumentation thêm OpenTelemetry instrumentation vào ứng dụng .NET Core. Các phương pháp SetResourceBuilder cho phép bạn thêm các thuộc tính chung cho tất cả span trong ứng dụng. Các phương pháp AddHttpClientInstrumentation cho phép bạn trang bị đo lường System.Net.Http.HttpClient và System.Net.HttpWebRequest để đo từ xa thu thập về các yêu cầu HTTP gửi đi. Các phương pháp SetSampler cho phép bạn điều chỉnh số lượng các mẫu theo dõi thu thập và gửi tới các bộ sưu tập OpenTelemetry. Các phương thức AddConsoleExporter xuất ra phép đo từ xa đã thu thập sang bảng điều khiển. Trong sản xuất, bạn nên sử dụng các exporter hữu ích khác như Jaeger, Zipkin hoặc Prometheus.

Để bật tính năng theo dõi phân tán trong ứng dụng của bạn, hãy thêm phần sau vào phương thức ConfigureServices của microservice:

public void ConfigureServices(IServiceCollection services)

{

    services.AddOtelServices(Env.ApplicationName);

}

Bây giờ chúng ta sẽ kiểm tra việc triển khai bằng cách kiểm tra theo dõi được tạo từ một yêu cầu HTTP được gửi từ ứng dụng kiểm thử. Đầu tiên, xác định một điểm cuối trong ứng dụng kiểm thử để thêm một event vào span và gửi một yêu cầu HTTP POST đến Hookbin service.

[HttpPost]

public async Task Post()

{

    Activity.Current?.AddEvent(new ActivityEvent("Sending request to HookBin"));

    return await _client.PostAsync("https://hookb.in/ggp0VpGyLYTG7Voo7Veg", new StringContent("Hello"));

}

Ảnh chụp màn hình sau đây thể hiện header traceparent tương thích W3C được tạo bởi ASP.NET Core và được nhận bởi Hookbin service:

Ba thành phần cơ bản của nền tảng Reusable .NET Microservice - Ảnh 3.

Traceparent header

Ảnh chụp màn hình dưới đây thể hiện trace được tạo bởi ứng dụng kiểm thử, sau đó được gửi đến bảng điều khiển. Trong trace, hãy lưu ý event mà chúng ta đã thêm vào khoảng thời gian ngay trước khi gửi yêu cầu đến Hookbin service:

Ba thành phần cơ bản của nền tảng Reusable .NET Microservice - Ảnh 4.

Kết luận

Để tăng tốc độ phát triển microservice, bạn nên phát triển một nền tảng tiêu chuẩn chuẩn hóa hành vi phổ biến của microservices như ghi nhật ký (logging), giám sát (monitoring) và các mối quan tâm xuyên suốt khác. Hãy nhớ rằng, rất dễ để nền tảng phát triển về quy mô và độ phức tạp. Do đó, bạn chỉ nên giải quyết các mối quan tâm kỹ thuật xuyên suốt trong nền tảng chứ không phải domain logic của bất kỳ microservice nào.

SHARE