只有累積,沒有奇蹟

2023年2月22日 星期三

[NETCore] 如何設定 ASP.NET Core 健康檢查(Health Check)功能 - Health Check UI

前言
在前一篇針對 ASP.NET Core 2.2 新特性 Health Check功能做基本的介紹,接著要分享的是在 BeatPulse 中實用的功能 Health Check UI,提供 UI 介面顯示及儲存 Health Check 檢查的結果內容,如果有多台時也可以在設定檔加上指定 URL 達到同時監控多台的效果,此篇就針對 Health Check UI 做介紹若有問題或是錯誤的地方歡迎網路的高手大大給予指導

Health Check UI
要使用 Health Check UI 起手式是要安裝其套件  AspNetCore.HealthChecks.UI ,指令如下
install-package AspNetCore.HealthChecks.UI  
安裝完 nuget package 之後,在 Startup.cs 要註冊 service 與 middleware,定義 healthCheck 的路徑為 /hc,UI 顯示的路徑為 /hc-ui
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHealthChecksUI();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseHealthChecks("/hc", new HealthCheckOptions
        {
            Predicate = _ => true,
            ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
        });

        app.UseHealthChecksUI(options =>
        {
            options.UIPath = "/hc-ui";
        });        
    }
}  
下一步是 appsettings.json 加上 healthCheckUI 設定資訊,設定包含要檢查的內容網址、多久執行檢查的動作與發生意外時的處理機制,這裡設定每 10 秒進行一次檢查,檢查的內容來源為 /hc
"HealthChecks-UI": {
    "HealthChecks": [
      {
        "Name": "Health Check Demo",
        "Uri": "http://localhost:5000/hc"
      }
    ],
    "EvaluationTimeOnSeconds": 10,
    "MinimumSecondsBetweenFailureNotifications": 60
  }
檢查內容這裡沿用上一篇介紹 healthCheck 的內容項目,檢查 mssql、Redis、Memory 與第三方 url 檢康狀況,這裡就不在多加說明,想了解細節可以參考上一篇文章內容說明
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
    services.AddHealthChecksUI();

    services.AddHealthChecks()
        .AddMemoryHealthCheck("Memory Health Check")
        .AddSqlServer(Configuration["ConnectionStrings:DefaultConnection"], name: "SQL Server HealthCheck")
        .AddRedis(Configuration["ConnectionStrings:RedisConn"], name: "Redis HealthCheck")
        .AddUrlGroup(new Uri("https://marcus116.blogspot.com"), "ThirdParty API HealthCheck",HealthStatus.Degraded);
    //省略
}
以上設定完畢後,執行專案並在瀏覽器輸入 /hc-ui,就可以看到將檢查的字串內容變為圖像顯示

Docker Image
作者也有提供 Docker health check image 給需要的人使用,可以透過 docker pull 取得 health check image 並使用 docker run 來執行
PS C:\> docker pull xabarilcoding/healthchecksui
Using default tag: latest
latest: Pulling from xabarilcoding/healthchecksui
27833a3ba0a5: Pull complete
25dbf7dc93e5: Pull complete
0ed9cb15d3b8: Pull complete
874ea13b7488: Pull complete
73d82f2e96ee: Pull complete
f856979cdc5c: Pull complete
Digest: sha256:fe18f7655f05872cd8b2644ce7d4603b1d03ae4e0b24fa59f4d960cdc7bde6c4
Status: Downloaded newer image for xabarilcoding/healthchecksui:latest
PS C:\> docker run --name healthcheck-ui -p 5000:80 -d xabarilcoding/healthchecksui:latest
詳細可以參考 HealthChecks UI Docker Image

感想
使用 health Check UI 幫助我們可以更快的了解需要檢查應用程式的健康狀態資訊,如果您的應用程式有不止一台,可以在 health Check Ui 設定檔節點新增多筆檢查來源與名稱來自訂不同的檢查行為,舉例來說 MSDN 有列出如果 Health Check 顯示多筆,希望這篇介紹可以有幫助到有需要的朋友,謝謝

參考
Health checks in ASP.NET Core
ASP.NET Core MVC - 2.2 Health Checks


2023年2月6日 星期一

[NETCore] 如何設定 ASP.NET Core 健康檢查(Health Check)功能

前言
過去當應用程式開發完成之後,另外一項重要的事情就是建立上線後的監控機制,尤其是重要性高的服務更是不可或缺的事情,有遇過公司是在站台底下放置一個檔案內容文字是OK,在透過工具固定時間去確認網站底下這檔案是否有正常回傳,就可代表網站是否存活者,各種不同的實作方式都可以達到此目的; ASP.NET Core 2.2 開始提供 Health Check 中介層 (Middleware),透過 HTTP 方式可以即時取得應用程式的健康狀況,在使用與設定上容易上手,此篇介紹在 ASP.NET Core 中如何使用及設定 Health Check 的方式,即時的取得應用程式健康狀況若有問題或是錯誤的地方歡迎網路的高手大大給予指導

基本使用
Health Check 為 ASP.NET Core 2.2 內建,因此使用前不需要透過 nuget 下載任何 package 套件,只需要簡單的註冊服務就可以使用,首先在  Sartup.cs  中的 ConfigureServices 方法中加上 AddHealthChecks 註冊 Health Checks 服務,接著在 configure 中新增 UseHealthChecks middleware 並指定位置,即可完成基本的 Health Chcek 健康檢查的應用,代碼如下
public void ConfigureServices(IServiceCollection services)
{
    services.AddHealthChecks();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseHealthChecks("/health");

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Navigate to /health to see the health status.");
    });
} 
執行應用程式,透過瀏覽器開啟 /health 就可以看到回傳的結果

HealthCheckOptions
上面介紹了最基本的使用方式,實務上常常會需要得到更多資訊,有時候可能會需要自定義 Health Check 回傳的 HTTP Status 狀態,或是輸出不同的格式情境時,就可以使用  HealthCheclOptions  來自訂不同的檢查行為,以下範例是自訂檢查輸出為 Json,並輸出檢查細節的所有內容資訊
app.UseHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = async (context, report) =>
    {
        var json = JsonConvert.SerializeObject(report);
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(json);
    }
});
回傳結果為目前應用程式狀態是正常 (Status=2=Health),以及 Health Check 所花費的時間
{
    "Entries": {},
    "Status": 2,
    "TotalDuration": "00:00:00.0000048"
}
當然如果只想要內容跟結果,可以調整 HealthCheckOptions 內容輸出指定需要的資訊,舉例來說我只想看 Status 跟 error 內容,代碼如下
app.UseHealthChecks("/health",
    new HealthCheckOptions
    {
        ResponseWriter = async (context, report) =>
        {
            var result = JsonConvert.SerializeObject(
                new
                {
                    status = report.Status.ToString(),
                    errors = report.Entries.Select(e => new { key = e.Key, value = Enum.GetName(typeof(HealthStatus), e.Value.Status) })
                });
            context.Response.ContentType = MediaTypeNames.Application.Json;
            await context.Response.WriteAsync(result);
        }
    });
回傳結果如下
{
    "Status": 2,
    "error": []
}

檢查依賴項目健康狀態
應用程式在開發中會使用到資料庫或是不同服務,BeatPulse 提供多個檢查項目的 API,透過這些 API 可以檢查依賴內容的健康狀況,舉例來說可以檢查 SQL、Redis、Network、Kafka、Aws.S3 等不同依賴於應用程式所使用的服務,如果要使用時後僅需要下載對應 package 和使用擴充方法就健康資訊。
舉例來說我的 ASP.NET Core API 有資料庫是使用 MS SQL,Cache 是使用 Redis,另外還有使用到第三方 API,以上可以在 Health Check 中設定,首先須先下載用到的 package
Install-Package AspNetCore.HealthChecks.SqlServer
Install-Package AspNetCore.HealthChecks.Redis
Install-Package AspNetCore.HealthChecks.Uris
接著在ConfigureServices 方法中加上 AddSqlServer、AddRedis、AddUrlGroup 等擴充方法,並指定其連線字串及網址內容 
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddHealthChecks()
        .AddSqlServer(Configuration["ConnectionStrings:DefaultConnection"], name: "SQL Server HealthCheck")
        .AddRedis(Configuration["ConnectionStrings:RedisConn"], name: "Redis HealthCheck")
        .AddUrlGroup(new Uri("https://marcus116.blogspot.com"), "ThirdParty API HealthCheck", HealthStatus.Degraded);
}
執行應用程式並開啟 health 頁面,可以看到檢查 MSSQL、Redis 以及外部服務 Domain 的結果
在每個 Health Check 方法也支援參數指定其標籤、名稱、失敗時的狀態等資訊,以下為 SQL Server 檢查,如果有需要的話也可以建立多個 SQL Server 健康檢查
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddHealthChecks()
                .AddSqlServer(
                    connectionString: Configuration["ConnectionStrings:DefaultConnection"],
                    healthQuery: "select 1",
                    name: "MSSQL Check",
                    failureStatus: HealthStatus.Degraded,
                    tags: new string[] {"database", "sqlServer"});
}
更多細節及說明可以參考 GitHub 上的官網內容 : 傳送門 

自訂健康檢查
假設在 AspNetCore.Diagnostics.HealthChecks 都不符合您的需求,可以透過實作  IHealthCheck  介面來自己定義需要健檢的項目內容,舉例來說可以檢查應用程式記憶體 (Memory) 的使用量,如果使用超過設定值時就會報告狀態為不健康有可能需要降級的動作,下列代碼為參考 MSDN 定義的 MemoryHealthCheck 方法與 MemoryCheckOptions 類別,定義 Memory 上限水位為 1G
public class MemoryHealthCheck : IHealthCheck
{
    private readonly IOptionsMonitor<MemoryCheckOptions> _options;

    public MemoryHealthCheck(IOptionsMonitor<MemoryCheckOptions> options)
    {
        _options = options;
    }

    public string Name => "memory_check";

    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        var options = _options.Get(context.Registration.Name);

        // Include GC information in the reported diagnostics.
        var allocated = GC.GetTotalMemory(forceFullCollection: false);
        var data = new Dictionary<string, object>()
        {
            { "AllocatedBytes", allocated },
            { "Gen0Collections", GC.CollectionCount(0) },
            { "Gen1Collections", GC.CollectionCount(1) },
            { "Gen2Collections", GC.CollectionCount(2) },
        };

        var status = (allocated < options.Threshold) ?
            HealthStatus.Healthy : HealthStatus.Unhealthy;

        return Task.FromResult(new HealthCheckResult(
            status,
            description: "Reports degraded status if allocated bytes " +
                         $">= {options.Threshold} bytes.",
            exception: null,
            data: data));
    }
}

public class MemoryCheckOptions
{
    // Failure threshold (in bytes)
    public long Threshold { get; set; } = 1024L * 1024L * 1024L;
}
接著可以透過兩種方式加入檢查

AddMemoryHealthCheck 擴充方法
要來寫  AddMemoryHealthCheck  擴充方法,在 ConfigureServices 才可以用到
public static class GCInfoHealthCheckBuilderExtensions
{
    public static IHealthChecksBuilder AddMemoryHealthCheck(
        this IHealthChecksBuilder builder,
        string name,
        HealthStatus? failureStatus = null,
        IEnumerable<string> tags = null,
        long? thresholdInBytes = null)
    {
        // Register a check of type GCInfo.
        builder.AddCheck<Startup.MemoryHealthCheck>(
            name, failureStatus ?? HealthStatus.Degraded, tags);

        // Configure named options to pass the threshold into the check.
        if (thresholdInBytes.HasValue)
        {
            builder.Services.Configure<Startup.MemoryCheckOptions>(name, options =>
            {
                options.Threshold = thresholdInBytes.Value;
            });
        }

        return builder;
    }
}
下一步就是與之前說明的相同,到 ConfigureServices 加上 AddMemoryHealthCheck 方法,另外還想知道自訂檢查中的 GC 記憶體用量狀況 (GenCollections 0、1、2),因此 Config 內容也調整為輸出全部資訊
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddHealthChecks()
        .AddMemoryHealthCheck("Memory Health Check");
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseHealthChecks("/health",
        new HealthCheckOptions
        {
            ResponseWriter = async (context, report) =>
            {
                var result = JsonConvert.SerializeObject(report);
                context.Response.ContentType = MediaTypeNames.Application.Json;
                await context.Response.WriteAsync(result);
            }
        });

    // 省略 
}
重新執行應用程式,成功輸出其內容資訊

AddCheck<T>
使用  AddCheck  注入的方式加入檢查,如下  
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddHealthChecks()
        .AddCheck<MemoryHealthCheck>("Memory Health Check");

        services.AddSingleton<MemoryHealthCheck>();
    // 省略
}
結果與使用 AddMemoryHealthCheck 相同,這裡就不在重複貼。

感想
介紹了很多 ASP.NET Core 新功能 Health Check 的基本使用,相信對於在 ASP.NET Core 中使用 health check 了解更多,以上算是簡單的使用情境說明,當應用程式不只一台或是要健檢的項目變多的時候,確認 health check 回傳的 status 狀況上就會變得不單純,因此下一篇會介紹 Health Check UI 的設定及如何使用 Publisher 方式推到某一台方便進行監控及管理,希望這篇介紹可以有幫助到有需要的朋友,謝謝

參考
Health checks in ASP.NET Core
AspNetCore.Diagnostics.HealthChecks
ASP.NET Core MVC - 2.2 Health Checks
AspNetCore 2.2 新特性---HealthCheck
How to set up ASP.NET Core 2.2 Health Checks 
Using the Microsoft.AspNetCore.HealthChecks Package
Health Checks in ASP.NET Core

2023年1月28日 星期六

[回顧] 2022 年個人回顧

前言
2022 年對自己來說是個挑戰非常大的一年,自己回想起來可以是工作 10 幾年來最辛苦的一年 (沒有比較沒有傷害(?。總結 2022 年三個重點,分別是 #管理 #學習 #技術分享,以下就針對 2022 年做個簡單的回顧,記錄自己累到不要不要的內容

課程
▍線下課程
  • CSM AHA
  • LeSS (Large Scale Scrum)入門 - 體驗工作坊

▍線上課程

閱讀書籍
在過去幾年已經有帶團隊的經驗,2022 年遇到最大挑戰是當團隊成員人數是之前的數(4~5)倍時該如何管理
當人數變多,過去管理團隊的方式適用嗎 ?
當人數變多溝通問題開始浮現。如何降低團隊溝通成本呢 ? 如何制定適合團隊的流程與 SOP ?
當人數變多信任感很重要,看到問題如何讓大家知道並願意接受(買單) ? 團隊成員有人不合時該怎麼協調 ?
當人數變多要如何授權,如何有效授權給下面主管避免微管理 ? 團隊的切分怎樣才是最有效率的合作方式呢

或許過去在團隊人數少的時候有些方法沒問題,但團隊到了一定規模時就會遇到瓶頸,因此更應該找到新方法像是上課或是看書加強學習自己未知的知識,透過更多外在刺激與借鏡別人成功的經驗,希望突破自己的天花板找到既有問題的解法,避免自己成為團隊的瓶頸阻礙團隊的發展。自己也很幸運地在過去的工作時有認識厲害的技術管理者,在實務上遇到困難時可以第一時間求助與找到可能的根本問題,經過一年的時間自己也對於管理有進一步的認識,看到自己過去可能不成熟的地方與觀念,在實務上也學到很多好的與不好的經驗與教訓。也看到團隊慢慢朝向好的方向前進。
最近去看了灌籃高手電影版,對於團隊這件事也是很有感觸,回憶起安西教練曾經說過的一段話
「球隊不是為你而存在的,相反的你的存在是為球隊啊」


技術分享
身為技術出生的攻城師當然不能忘本,對於技術當然是要持續學習與精進的,否則久了之後很容易落為嘴砲主管,這並不是我自己想走的路線。也因為有這想法,在年初時也給自己設定目標要持續不要臉的投稿技術演討會分享學習心得,可能是運氣不錯今年投稿的都有得到評審的青睞有機會上台分享,超越了去年 2021 的三場技術分享,但對於容易緊張得自己來說就是可怕的一件事,下半年生活幾乎每個月都在準備研討會討影片講稿,白天工作忙到炸裂,晚上顧家裡陪小孩,半夜準備投影片就是生活剪影不唬爛,但為了達到自己設定的目標還是牙一咬撐過去了,其中讓人意外的是 DevopsDays 議程被排在 ABC 大廳,可能是議題命名的還不錯當時受眾比預期的多很多很多很多,最後參加人數近 300 位主辦單位還加椅子才坐的下,幸好最後聽說議程問卷結果是挺滿意的才讓我的不安散去 XDDD
另外 2022 給自己的另一個目標是要持續的在 FB 粉絲團分享有趣的技術議題,針對不錯的議題要加上小小的見解以提高發文的質量,立志成為高級轉貼仔(誤。成效從年初的 40 位追蹤到現在接近 290 位,也算是有小小的成績也希望自己可以堅持下去

驚喜 : MVP
微軟MVP成就達成 ! 感謝保哥推薦才有機會得到這殊榮,也期望自己莫忘初衷技術學習繼續下去 :)

2023 期望
新年一年,一定會有更多的挑戰
  • 睡飽一點 : 2023 多睡一點 (2022 平均睡眠 4.5 小時 XDD
  • 舉辦分享 : 定期舉辦內部分享,讓同仁可以在忙碌之外吸收新的知識
  • 技術分享 : 繼續不要臉投稿研討會技術分享
  • AMA : 與轄下同仁談任何事情(Ask me anything),不管好的壞的多聽聽大家的想法

希望大家 happy coding , enjoy it :)

2023年1月15日 星期日

[.NET] Quartz.NET 排程執行異常 - All triggers of Job class set to ERROR state.

問題 
最近因為新專案需求是定期到資料庫檢查會員的資料,因此使用 ASP.NET Core Hosting 服務 +  Quartz.NET 作為工作排程器使用,在開發完畢要啟動 Job 驗證正確性時意外跳出異常訊息,錯誤訊息為  Quartz.Simpl.RAMJobStore | All triggers of Job GroupName.ClassName set to ERROR state. ,這篇就針對此案例作簡單紀錄與分享若是有不清楚或是錯誤的地方歡迎討論予糾正

解決方法 
首先先看一下案發現場的錯誤 log 如下
上面可以看到當程式啟動時所紀錄的 Log 資訊,像是有紀錄目前所使用的版本為 3.0.7 版,預設 ThreadPool 為 10 條,但一到 QuartzScheduler 啟動之後下一個 Log 就直接紀錄執行異常 ( ERROR STATE ),搜尋相關異常訊息發現以下資訊
It should be extremely rare for this method to throw an exception - basically only the case where there is no way at all to instantiate and prepare the Job for execution. When the exception is thrown, the Scheduler will move all triggers associated with the Job into the state, which will require human intervention (e.g. an application restart after fixing whatever configuration problem led to the issue with instantiating the Job).
可能問題為 Quartz.NET 啟動此 Trigger 當下發生錯誤,有可能是因為沒有辦法實例化或是準備執行 Job 發生異常,當執行拋出異常時會將有關聯的作業關閉,會自動更新異常 TRIGGER_STATE 狀態為 ERROR state,需要人工進行修復,修復完畢後再重新啟用應用程式。因此下一步是了解啟動時發生的錯誤資訊,為了得到相關錯誤訊息,調整既有的代碼將  JobExecutionException  資訊紀錄在 log.Error 中,
class SomeJob : IJob
{
    private readonly ILogger _logger;
    private readonly IMemberService _service;

    public SomeJob(IMemberService service, ILogger logger)
    {
        this._service = service;
        this._logger = logger;
    }
    
    public Task Execute(IJobExecutionContext context)
    {
        try
        {
            // todo something
        }
        catch (Exception e)
        {
            JobExecutionException je = new JobExecutionException(e);
            je.RefireImmediately = true;  //do something with the exception

            _logger.Error(je);
            
            return Task.FromResult(0);
        }

        return Task.FromResult(0);
    }
}
接著重新執行應用程式,並觀察 Log 資訊
果不其然,透過 Log 可以發現是在 Service 的建構子 ctor 注入時發生錯誤,所以才會在排程要啟動時異常
停止此 Job 的執行動作,在往下追發現是其 interface 存取修飾子設定異常 ( 應該要是 public 實際卻是 internal ),修正後在重新執行可以看到下列 Log,
Quartz.Core.QuartzSchedulerThread|Batch acquisition of 1 triggers |url: |action: 
執行正常,宣告結案 !

參考
Recover from trigger ERROR state after Job constructor threw an exception?

Copyright © m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com