Link Search Menu Expand Document

Middlewares

Advanced

Dựa trên thực hành Web API, trong bài viết này chúng ta áp dụng một số middlewares phổ biến trong việc phát triển ứng dụng Microservices.

  • Swashbuckle - mô tả API cung cấp bởi một microservice theo chuẩn OpenAPI. Mô tả này cung cấp thông tin giúp client sử dụng API dễ dàng hơn.

  • Health Check - microservices thông báo trạng thái về một trung tâm giám sát theo chu kì thời gian. Trung tâm tổng hợp báo cáo, gửi cảnh báo khi cần thiết.

  • Prometheus - báo cáo về tần suất, hiệu suất hoạt động của ứng dụng, hoặc một chức năng trong ứng dụng thông qua những tham số đo lường cụ thể - metrics

Nội dung

  1. Swashbuckle
  2. Health Check
  3. Prometheus

Swashbuckle

Middleware Swashbuckle bao gồm ba thành phần:

  • SwaggerGen: tạo SwaggerDocument từ controllers, routes và models
  • Swagger: cung cấp JSON endpoint dựa trên SwaggerDocument object
  • SwaggerUI: tạo giao diện UI dựa trên việc biên dịch JSON endpoint

Trong folder RemindersManagement.API, thực hiện câu lệnh sau để cài đặt package Swashbuckle:

dotnet add package Swashbuckle.AspNetCore

Cập nhật phương thức trong Startup.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RemindersManagement.API.Domain.Interfaces;
using RemindersManagement.API.Domain.Services;
using RemindersManagement.API.Infrastructure.Data;
using RemindersManagement.API.Infrastructure.Repositories;
using Serilog;

namespace RemindersManagement.API
{
    public class Startup
    {
      ...
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddScoped<IRemindersRepository, RemindersRepository>();
            services.AddScoped<ICategoriesRepository, CategoriesRepository>();
            services.AddScoped<IRemindersService, RemindersService>();
            services.AddDbContext<RemindersDbContext>(options => {
                options.UseSqlite("Data Source=FriendReminders.db");
            });

            // Register the Swagger generator
            services.AddSwaggerGen();
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseHttpsRedirection();
            app.UseSerilogRequestLogging();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            // Middleware generate JSON endpoint.
            app.UseSwagger();
            // Middleware generate Swagger-UI
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "RemindersManagement V1");
            });
        }        
    }
}

Trong foler gốc của solution thực hiện lệnh:

dotnet run --urls="http://localhost:8000/" --project Services/RemindersManagement/RemindersManagement.API/RemindersManagement.API.csproj

Truy cập http://localhost:8000/swagger/index.html xác nhận kết quả:

SwaggerUI Output Giao diện Swagger của RemindersManagement API

Tip: Để swagger/index.html được sử dụng mặc định khi truy cập địa chỉ http://localhost:8000/, chúng ta có thể tạo thêm một HomeController.cs trong folder Controllers với nội dụng:

using Microsoft.AspNetCore.Mvc;

namespace RemindersManagement.API.Controllers
{
    public class HomeController : ControllerBase
    {
        [Route(""), HttpGet]
        [ApiExplorerSettings(IgnoreApi = true)]
        public RedirectResult RedirectToSwaggerUI()
        {
            return Redirect("/swagger/");
        }
    }
}

Health Check

Việc kiểm tra Health Check kết hợp hai middlewares:

  • Microsoft.AspNetCore.Diagnostics.HealthChecks: cung cấp bởi Microsoft, trả về kết quả Health Check của ứng dụng .NET core
  • AspNetCore.Diagnostics.HealthChecks: cung cấp bởi Xabaril, bổ sung thêm khả năng tích hợp và hiển thị Health Check UI

Các bước thực hiện:

  • Cài đặt packages trong project RemindersManagement.API, với các lệnh:
dotnet add package AspNetCore.HealthChecks.UI
dotnet add package AspNetCore.HealthChecks.UI.Client
dotnet add package AspNetCore.HealthChecks.UI.InMemory.Storage
dotnet add package Microsoft.AspNetCore.Diagnostics.HealthChecks

Chú ý

Health Check UI sử dụng storage để ghi lại kết quả mỗi khi trạng thái chuyển đổi giữa Healthy và Unhealthy. Do vậy, để hiển thị Health Check UI, chúng ta cần cài đặt một trong các storage providers, đây là lý do chúng ta sử dụng đến package AspNetCore.HealthChecks.UI.InMemory.Storage trong phần cài đặt.

Khai báo trong appsettings.Development.json:

{
  "HealthChecksUI": {
    "HealthChecks": [
      {
        "Name": "RemindersManagement",
        "Uri": "http://localhost:8000/health"
      }
    ],
    "EvaluationTimeOnSeconds": 10,
    "MinimumSecondsBetweenFailureNotifications": 60
  },
  "Serilog": {
    "Using": [
      "Serilog.Sinks.Console"
    ],
    "MinimumLevel": {
      "Default": "Information"
    },
    "WriteTo": [
      {
        "Name": "Console"
      }
    ],
    "Enrich": [
      "FromLogContext"
    ]
  },
  "AllowedHosts": "*"
}
  • Cập nhật các phương thức trong Startup.cs:
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RemindersManagement.API.Domain.Interfaces;
using RemindersManagement.API.Domain.Services;
using RemindersManagement.API.Infrastructure.Data;
using RemindersManagement.API.Infrastructure.Repositories;
using Serilog;

namespace RemindersManagement.API
{
    public class Startup
    {
        ...
        public void ConfigureServices(IServiceCollection services)
        {
            ...
            // Register the Swagger generator
            services.AddSwaggerGen();

            // Register health check services
            services.AddHealthChecks()

            // Register HealthChecks UI and storage provider
            services
                .AddHealthChecksUI()
                .AddInMemoryStorage();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            ...
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapHealthChecksUI();
            });

            // Middleware generate JSON endpoint.
            app.UseSwagger();
            // Middleware generate Swagger-UI
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "RemindersManagement V1");
            });

            app.UseHealthChecks("/health", new HealthCheckOptions()
            {
                Predicate = _ => true,
                ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
            });
        }
    }
}

Trong thư mục gốc của solution, thực hiện lệnh dotnet run:

dotnet run --urls="http://localhost:8000/" --project Services/RemindersManagement/RemindersManagement.API/RemindersManagement.API.csproj

Xác nhận kết quả với địa chỉ http://localhost:8000/healthchecks-ui:

Health Ouput Giao diện Health Check UI của RemindersManagement API


Prometheus

Prometheus là một hệ thống giám sát chức năng của các ứng dụng hoặc cơ sở hạ tầng thông qua những thông số metrics được đo lường theo chuỗi thời gian. Bằng cách cài đặt Node Exporters trên những thành phần hệ thống (virtual machine, database server…) hoặc sử dụng thư viện Prometheus - instrumention bên trong logic ứng dụng, Prometheus tự động thực hiện việc tìm kiếm - service discovery, khai thác - scraping dữ liệu và kết hợp cùng những hệ thống khác như Grafana Dashboard, Alert Management để xây dựng một giải pháp toàn diện cho khả năng giám sát hoạt động của toàn bộ hệ thống ứng dụng.

Prometheus Architecture Kiến trúc hệ thống giám sát Prometheus

Prometheus cung cấp những loại metrics sau:

  • Counter - giá trị tăng dần theo cách cộng dồn của một thông số.
  • Gause - giá trị biến thiên có khả năng tăng hoặc giảm
  • Histogram - giá trị phân phối trong một phạm vi hoặc thời gian
  • Summary - tương tự History, bổ sung giá trị phân vị theo thời gian (*)

Trong phần này, chúng ta áp dụng Prometheus middleware trên RemindersManagement để tạo metric endpoint /metrics sau đó cài đặt và sử dụng Prometheus thực hiện việc thu thập metrics và hiện thị kết quả với một số câu lệnh truy vấn đơn giản dựa trên ngôn ngữ PromQL

Các bước thực hiện:

Bước 1: Cài đặt Prometheus middleware

Trong folder RemindersManagement.API, cài đặt Prometheus packages:

dotnet add package prometheus-net.AspNetCore

Thay đổi nội dung Startup.cs:

using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RemindersManagement.API.Domain.Interfaces;
using RemindersManagement.API.Domain.Services;
using RemindersManagement.API.Infrastructure.Data;
using RemindersManagement.API.Infrastructure.Repositories;
using Microsoft.AspNetCore.HttpOverrides;
using Serilog;
using System.Diagnostics.CodeAnalysis;
using Prometheus;

namespace RemindersManagement.API
{
    [ExcludeFromCodeCoverage]
    public class Startup
    {
      ...
      public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
      {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseForwardedHeaders();
        }
        else
        {
            app.UseForwardedHeaders();
            app.UseHsts();                
        }

        app.UseSerilogRequestLogging();
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseHttpMetrics();
        app.UseAuthorization();

        // ASP.NET Core 2
        // app.UseMetricServer();

        // Create health check endpoint `/health`.
        app.UseHealthChecks("/health", new HealthCheckOptions()
        {
          Predicate = _ => true,
          // Return HealthReport data to show in HealthCheck UI.
          ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
        });
        app.UseEndpoints(endpoints =>
        {
          // ASP.NET Core 3 or newer
          endpoints.MapMetrics();
          endpoints.MapControllers();
        });

        // Enable middleware to serve generated Swagger as a JSON endpoint.
        app.UseSwagger();
        // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
        app.UseSwaggerUI(c =>
        {
          c.SwaggerEndpoint("/swagger/v1/swagger.json", "RemindersManagement V1");
        });
      }
    }
}

Các chú ý trong thay đổi trên:

  • Sử dụng ASP.NET Core Exporter middleware endpoints.MapMetrics();
    • Cung cấp metrics qua URL endpoint /metrics
  • Sử dụng library ASP.NET Core HTTP request metrics cung cấp thông tin:
    • Số lượng HTTP request đang xử lý
    • Số lượng HTTP request nhận được
    • Thời gian thực thi HTTP request

Chú ý

  • Cách thức cài đặt Prometheus middleware có sự thay đổi giữa ASP.NET Core 2.x và 3.x
  • Prometheus cung cấp package tích hợp hoạt động Health Check tương tự như Xabaril

Bước 2: Thực thi và xác nhận kết quả

  • Trong solution folder FriendReminders, thực thi project với lệnh
dotnet run --urls="http://localhost:8000/" --project Services/RemindersManagement/RemindersManagement.API/RemindersManagement.API.csproj
  • Truy cập endpoint http://localhost:8000/metrics và xác nhận kết quả

Metrics Ouput Kết quả hiển thị trên metric endpoint

Dữ liệu Histogram trong kết quả trên được phân loại theo giá trị các tham số Status Code, Method Controller. Mỗi giá trị được biểu diễn theo một trong cách định dạng:

- <basename>_bucket{...,le = "<bound_value>"}
- <basename>_sum
_ <basename>_count

Ví dụ:

  • Số lượng HTTP Get request gửi đến endpoint ‘/’ và nhận được 404 Not Found Error trong 1 giây
http_request_duration_seconds_count{code="200",method="GET",controller="",action=""} 2
  • Thời gian phản hồi HTTP Get request gửi đến endpoint ‘/’ và nhận được 200 OK trong 1 giây
http_request_duration_seconds_sum{code="200",method="GET",controller="",action=""} 0.2245351
  • Với bucket chia theo khoảng thời gian phản hồi: 0.001, 0.002, 0.004, 00.008…những giá trị sau thể hiển số lượng HTTP Get request gửi đến ‘Home’ controller và nhận được 302 Found Redirect.
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.001"} 0
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.002"} 0
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.004"} 0
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.008"} 0
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.016"} 0
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.032"} 0
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.064"} 0
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.128"} 1
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.256"} 1
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="0.512"} 1
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="1.024"} 1
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="2.048"} 1
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="4.096"} 1
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="8.192"} 1
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="16.384"} 1
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="32.768"} 1
http_request_duration_seconds_bucket{code="302",method="GET",controller="Home",action="RedirectToSwaggerUI",le="+Inf"} 1

Bước 3: Cài đặt Prometheus và truy vấn dữ liệu

  • Download và cài đặt Prometheus

Link download: Prometheus

Với hệ điều hành MacOS sử dụng prometheus-2.21.0.darwin-amd64.tar.gz

Giải nén download file:

tar xvfz prometheus-*.tar
rm prometheus-*.tar
  • Folder giải nén prometheus-2.21.0.darwin-amd64 có sẵn file cấu hình prometheus.yml:
# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

Theo cấu hình trên, Prometheus thực hiện giám sát chính mình qua địa chỉ localhost:9090

  • Để giám sát RemindersManagement service, bổ sung thêm job_name remindersmanagement với cấu hình sau:
# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

  - job_name: 'remindersmanagement'

    # Override the global default and scrape targets from this job every 5 seconds.
    scrape_interval: 5s

    static_configs:
    - targets: ['localhost:8000']
  • Thực thi Prometheus với cấu hình cập nhật
 # Start Prometheus.
# By default, Prometheus stores its database in ./data (flag --storage.tsdb.path).
./prometheus --config.file=prometheus.yml

Chú ý

  • Khi gặp lỗi “prometheus cannot be opened because the developer cannot be verified.”, có thể sử dụng chức năng Security & Privacy trong MacOS để xác nhận quyền thực hiện câu lệnh.

  • Trường hợp port 9090 đang sử dụng bởi một ứng dụng khác, có thể sử dụng câu lệnh lsof -i :9090 and sudo kill -9 [PID] để lấy lại cổng 9090.

  • Truy cập địa chỉ http://localhost:9090/targets và xác nhận danh sách Target cùng trạng thái hoạt động:

Prometheus Ouput Danh sách Prometheus Targets

  • Truy cập địa chỉ http://localhost:9090/graph, thực hiện một số câu lênh truy vấn với PromQL. Kết quả trả về có thể hiển thị dưới dạng Console hoặc Graphp.

Kiểm tra trạng thái target

// Up == 1
Up

Up Ouput Kết quả kiểm tra trạng thái của Prometheus Targets

Tổng số HTTP Request gửi đến

http_request_duration_seconds_count

Count Ouput Thống kê số lượng HTTP Requests

Tốc độ thay đổi của tổng số HTTP Request gửi đến trong 1 giây

rate(http_request_duration_seconds_count[1m])

Rate Ouput Tốc độ thay đổi số lượng HTTP Request gửi đến trong 1 giây

Bước 4: Kết hợp Prometheus và Alert Management

Prometheus kết hợp PromQL và Alert Rule để tạo ra cảnh báo với những trạng thái bất thường bên trong hệ thống. Những cảnh bảo này được gửi đến Alert Management từ đó tạo ra những message gửi đến người quản trị thông qua hệ thống email, chat etc…

Trong bước 3, truy vấn PromQL Up cho phép hiển thị Targets tuỳ theo trạng thái hoạt động:

  • Up == 1: Targets đang hoạt động
  • Up == 0: Targets đang ngừng hoạt động

Dựa theo truy vấn này, tạo một file cấu hình rules.yml theo nội dung:

groups:
- name: AllTargets
  rules:
  - alert: TargetDown
    # Condition for alerting
    expr: up == 0
    for: 1m
    # Annotation - additional informational labels to store more information
    annotations:
      title: "Instance  down"
      description: " of job  has been down for more than 1 minute."
    # Labels - additional labels to be attached to the alert
    labels:
      severity: 'critical'

Cập nhật prometheus.yml để tham chiếu đến qui tắc trên:

# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  - rules.yml

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

  - job_name: 'remindersmanagement'

    # Override the global default and scrape targets from this job every 5 seconds.
    scrape_interval: 5s

    static_configs:
    - targets: ['localhost:8000']

Theo thay đổi này, alert rule được kiểm tra sau mỗi 15 giây, nếu một Target không hoạt động liên tục trong 1 phút thì cảnh báo sẽ được hệ thống Prometheus ghi lại.

Để xác nhận rules check, thực thi Prometheus đồng thời ngừng hoạt động của RemindersManagement:

./prometheus --config.file=prometheus.yml

Danh sách Targets qua endpoint http://localhost:9090/targets

Target Down Ouput Danh sách Prometheus Targets với trạng thái Up và Down

Danh sách Alerts qua endpoint http://localhost:9090/alerts

Alert Ouput Danh sách Alerts trong hệ thống

Kết luận

Các Middlewares được sử dụng như những thư viện có khả năng liên kết với nhau để bổ sung thêm những chức năng trong một ứng dụng, đồng thời tích hợp ứng dụng với những thành phần hệ thống thông tin khác. Bên cạnh đó, bài viết cũng cung cấp những thông tin khái quát về các thành phần trong một hệ thống Prometheus, giám sát và cảnh báo giúp kiểm soát chất lượng thực thi của một ứng dụng. Để tìm hiểu thêm về cách áp dụng Prometheus trong môi trường Production trên AWS Cloud, chúng ta có thể tham khảo thêm bài viết Swarm P.3.


Copyright © 2019-2022 Tuan Anh Le.