微服務系列之服務監控 Prometheus與Grafana

2022-12-20 18:01:59

1.為什麼需要監控服務
  監控服務的所屬伺服器硬體(如cpu,記憶體,磁碟I/O等)指標、服務本身的(如gc頻率、執行緒池大小、鎖爭用情況、請求、響應、自定義業務指標),對於以前的小型單體服務來說,確實沒什麼必要,但對於中大型專案,尤其那些群集部署顯得尤為重要、尤其是現在的微服務架構,服務眾多,而且很多服務都是群集部署,我們更是需要實時知道每一個服務所屬的範例(pod,伺服器)的執行、請求異常、自定義業務監控指標等情況。

2.監控方案
  監控方案因公司而異,沒有固定的套路,不一定複雜就好用,越複雜學習、維護、支出成本就越高,適合自己團隊就好,拋磚引玉我們之前使用過的方案:

  1. 自己的伺服器windows群集,.net core微服務架構,使用Telegraf在每臺機伺服器上進行採集cpu,記憶體,磁碟IO等等硬體指標資料和sqlserver、redis、rabbitmq等指標資訊,使用Prometheus來採集.net core服務的請求相關的資料,無論是硬體指標還是軟體指標,統一傳送到時序資料庫InfulxDB種,使用開源的報表程式Grafana來進行實時報表監控,通過Grafana的Alert條件觸發webhook來將報警的資料資訊組織好格式後,傳送到我們自己寫的一個服務接收,然後通過我們研發的通知訂閱中心來傳送給訂閱者郵件、簡訊、企業微信、桌面等告警提示;

  2. 騰訊雲Linux伺服器,.net core微服務架構,基於k8s編排管理的docker叢集,既然是雲,很多硬體指標都是自帶的監控,使用Prometheus監控叢集下的所有服務的異常請求,Rabbitmq,Redis等等中介軟體指標資訊,這次是基於Prometheus自帶的時許資料庫,使用其altermanger元件進行告警,全部Prometheus設定和自動發現新pod能力,都由牛逼的運維團隊完成。

3.Prometheus
  本文中,我們主要來說說目前非常流行的監控中介軟體Prometheus,基於docker的搭建、設定、基本指標採集、自定義業務指標採集。

  Prometheus是一個開源的現代化的、支援雲原生的系統監控與告警系統,2012年由前谷歌員工開發並做為社群開源專案開發,2015年正式釋出,2016年加入雲原生計算基金會CNCF,熱度僅次於K8S。git地址

  已經很多知名的三方廠商已經基於Prometheus做成了匯入器node exporter,比如linux系統,MYSQL資料庫,Redis等等非常多的中介軟體,有了這些匯入器,我們直接可以使用,非常豐富的監控指標人家已經為我們做好了。等下,我們演示以下使用linux的匯入器來監控linux系統指標。

  上面是官方給出的生態元件圖,Prometheus整個生態圈組成主要包括prometheus server,Exporter,pushgateway,alertmanager,grafana,Web ui介面,Prometheus server由三個部分組成,Retrieval,Storage,PromQL;

  1. Retrieval 負責在活躍的target主機上抓取監控指標資料;
  2. Storage 儲存主要是把採集到的資料儲存到磁碟中;
  3. PromQL是Prometheus提供的查詢語言模組;

  工作流程,Prometheus server定期從活躍的目標主機上通過http pull的方式拉取指標資料,目標主機可以通過prometheus的組態檔進行設定或者通過服務發現方式來發現目標主機;也可以通過pushGateway元件,從目標主機推播該元件,Prometheus server再定時從該元件拉取指標資料;

4.Prometheus基於docker的搭建和監控

  1. 拉取linux系統的匯入器node exporter
docker pull prom/node-exporter
  1. 啟動linux監控容器
docker run --name=node-exporter -p 9100:9100 -itd prom/node-exporter
  1. 建立prometheus組態檔
mkdir /opt/prometheus
cd /opt/prometheus/
vim prometheus.yml
  1. 修改組態檔
global:
  scrape_interval:   60s
  evaluation_interval: 60s

scrape_configs:
  - job_name: linuxNode1 --目標任務名稱
    static_configs:
      - targets: ['ip:9100'] --可以是多臺
        labels:
          instance: linux1    --指標維度

5.啟動prometheus容器

docker run -d -p 9090:9090 -v /opt/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml --name prometheus prom/prometheus:latest


啟動成功後,prometheus的webui也可以使用了,瀏覽器輸入http://IP:9090/targets

5.Grafana
  官方說明,grafana是用於視覺化大型測量資料的開源程式,他提供了強大和優雅的方式去建立、共用、瀏覽資料。用過之後確實強大,可設定性靈活,現成的主流中介軟體基於prometheus的報表模板非常豐富。

  1. 拉取grafana映象
docker pull grafana/grafana
  1. 啟動grafana容器
docker run -d -p 3000:3000 --name=jmeterGrafana grafana/grafana

瀏覽器存取:http://ip:3000(賬號密碼都是:admin)

選擇資料來源

至此,我們的linux伺服器的報表監控好了,接下來,我們來監控.net core服務的基本指標、請求、自定義業務資料。

6..net core整合prometheus

  1. nuget引入prometheus-net.AspNetCore
  2. startup類,管道執行時設定中
 //收集一些服務基本資訊,比如執行緒數,記憶體使用,控制程式碼,3個GC得回收次數統計
            app.UseMetricServer();
  1. 啟動程式存取http://localhost:5000/metrics

  2. 做為API服務,我們當然要收集http請求,請求狀態,耗時,次數這些

 //收集一些服務基本資訊,比如執行緒數,記憶體使用,控制程式碼,3個GC得回收次數統計
 app.UseMetricServer();
 //收集http請求和計數監控,比如總請求數,每次請求得耗時
 app.UseHttpMetrics();
 app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

  1. 我們還想收集一些.net core服務執行時一些更深的指標來定位一些困難問題,不用我們寫,我們只需要再引入一個別人封裝好的包 prometheus-net.DotNetRuntime,就可以得到以下指標:
    • 垃圾回收的收集頻率和時間;
    • 服務佔用堆大小;
    • 物件堆分配的位元組;
    • JIT編譯和JIT CPU消耗率;
    • 執行緒池大小,排程延遲以及增長/縮小的原因;
    • 鎖爭用情況;
      程式碼如下:
//Program下main方法,為了減少效能開銷,可以設定如下
DotNetRuntimeStatsBuilder
               .Customize()
               //每5個事件個採集一個
               .WithContentionStats(sampleRate: SampleEvery.FiveEvents)
               //每10事件採集一個
               .WithJitStats(sampleRate: SampleEvery.TenEvents)
               .WithThreadPoolStats()
               .WithGcStats()
               .StartCollecting();
  1. 再次執行會看到更多的指標了

  2. 這麼多指標,維度的監控在prometheus原生UI上通過過濾可以看到響應的資料和報表,比如我們要看服務執行總記憶體,通過指標key:dotnet_total_memory_bytes,去webui上搜尋:

  1. 原生UI多少差點意思,我們去prometheus報表模板市場去看看,有沒有基於prometheus的.net core執行時的模板,市場傳送門


我們找到一個模板,複製id用上文grafana匯入模板的方法,匯入進去,看看:

嗯,看上去好像不咋地,很多指標顯示不出來,實際專案裡,我們知道了指標資料,都是基於grafana自己設定要看的報表,下文我們繼續來看看,怎麼在.net core中基於promethues自定義一些指標收集,並且利用grafana來設定響應的報表。

7.自定義指標收集與設定報表
  通過上文的metrics指標資料,指標資料其實就是key value結構,只不過key中有label增加維度,並且每一個鍵值對都有響應的指標型別,prometheus的metrics有以下4種主要型別:

  • Counter:計數器,單調遞增,應用啟動之後只會增加不會減少
  • Gauge:儀表,和 Counter 類似,可增可減
  • Histogram:直方圖,柱形圖,Histogram其實是一組資料,主要用於統計資料分佈的情況 —— 統計落在某些值的範圍內的計數,同時也提供了所有值的總和和個數
  • Summary:彙總,摘要,summary 類似於 histogram,也是一組資料。不同的是,它統計的不是區間的個數而是統計分位數。
  1. 接下來,我們在程式碼裡,使用Counter和Gauge這兩種型別,來演示自定義指標收集;
  2. 先定義一個收集中心HUB
public class PrometheusMetricsHub
    {
        /// <summary>
        /// 監控計數器維度統計容器
        /// </summary>
        public Dictionary<string, Counter> Counters { get; set; } = new Dictionary<string, Counter>();
        /// <summary>
        /// 監控儀表盤維度統計容器
        /// </summary>
        public Dictionary<string, Gauge> Gauges { get; set; } = new Dictionary<string, Gauge>();


        /// <summary>
        /// 建立計數器容器
        /// </summary>
        /// <param name="key"></param>
        /// <param name="desc"></param>
        /// <returns></returns>
        public void CreateCounter(string key, string desc)
        {
            if (!this.Counters.ContainsKey(key))
                this.Counters.Add(key, Metrics.CreateCounter(key, desc));
        }

        /// <summary>
        /// 建立儀表盤容器
        /// </summary>
        /// <param name="key"></param>
        /// <param name="desc"></param>
        /// <returns></returns>
        public void CreateGauge(string key, string desc)
        {
            if (!this.Gauges.ContainsKey(key))
                this.Gauges.Add(key, Metrics.CreateGauge(key, desc));
        }

        /// <summary>
        /// 根據維度型別獲取監控範例
        /// </summary>
        /// <typeparam name="TContainer"></typeparam>
        /// <param name="prometheusEnum"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public TContainer GetContainer<TContainer>(PrometheusEnum prometheusEnum, string key) where TContainer : class
        {
            if (prometheusEnum == PrometheusEnum.Counter && this.Counters.ContainsKey(key))
                return this.Counters.GetValueOrDefault(key) as TContainer;
            if (prometheusEnum == PrometheusEnum.Gauge && this.Gauges.ContainsKey(key))
                return this.Gauges.GetValueOrDefault(key) as TContainer;
            return null;

        }
  1. 建立Prometheus註冊容器的擴充套件方法
public static class PrometheusMetricsHubExtenisons
    {
        public static IServiceCollection AddPrometheusMetricsHub(this IServiceCollection services,Action<PrometheusMetricsHub> buildAction)
        {
            var hub = new PrometheusMetricsHub();
            buildAction(hub);
            services.AddSingleton<PrometheusMetricsHub>(hub);
            return services;
        }
    }
  1. 啟動類,使用擴充套件方法,進行對PrometheusHub設定並注入容器
 //註冊監控
            services.AddPrometheusMetricsHub(hub =>
            {
                hub.CreateCounter("demoCounter", "測試計數器");
                hub.CreateGauge("demoGauge", "測試儀表盤");
            });
  1. 建立一箇中介軟體,每一次請求,都使用Prometheus指標容器進行累加統計
 public class PrometheusRequestMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly PrometheusMetricsHub _prometheusMetricsHub;
        public PrometheusRequestMiddleware(RequestDelegate next, PrometheusMetricsHub prometheusMetricsHub)
        {
            _next = next;
            _prometheusMetricsHub = prometheusMetricsHub;
        }
        public async Task InvokeAsync(HttpContext context)
        {

            try
            {
                //增加測試資料
                var counter = _prometheusMetricsHub.GetContainer<Counter>(Models.PrometheusEnum.Counter, "demoCounter");
                var gauge = _prometheusMetricsHub.GetContainer<Gauge>(Models.PrometheusEnum.Gauge, "demoGauge");

                counter.Inc();
                gauge.Inc();
                await _next(context);
                
            }
            finally
            {
                
            }
        }
    }
	 app.UseMiddleware<PrometheusRequestMiddleware>();
	
  1. 啟動,並檢視metrics指標
  2. 設定Grafana報表

由上圖所見,我們自定義的指標監控完成。

8.關於Prometheus高可用
  Prometheus本身不支援群集部署,也就是說本身沒法動態水平擴容,其實其本身的效能非常高,很少有撐不住情況,我們擔心的是硬碟容量問題,假設真的是超級大量的指標資料,怎麼辦呢,下面有幾個方案,也歡迎留言討論:

  1. 取捨方案,放棄一些不重要的指標採集,降低指標資料持久化時間,調整採集速率等等一些邏輯優化;
  2. 服務維度拆分方案,也很容易理解,之前一臺Prometheus去採集監控100個服務,現在用3臺,劃分服務,每臺採集30多個服務;
  3. 分片方案,這種比較極端,某一個服務,pod規模上千甚至更多,這時候,你單臺Promethues想完整採集這些pod的服務,對寬頻、硬碟、CPU要求極高極高,效能估計也不咋地,這時候就要分片處理,從一臺變多臺,分別採集,但是這裡有2個問題:
    1.這麼多pod,肯定是要利用註冊中心,在promethues設定裡通過服務發現來動態設定要監控的服務,consul可以,並且promethues服務發現設定也支援consul,然後在服務註冊的時候通過區分一個維度註冊,好讓對應promethues知道哪些節點是當前這臺需要採集的。
    2.分片監控一個服務,基於Prometheus本身儲存,肯定是沒法集中聚合觀看了,這裡又涉及到統一使用其他資料來源,比如InfluxDB,Redis等等,Promethes也支援設定只採集不儲存,通過 remote write 方式寫入遠端儲存庫。

9.一些問題

  1. Prometheus不是持久化資料的,但是對於大部分指標監控,我們也不需要持久化,這裡有一些產品上的業務指標,要想好是否需要持久化,別某臺服務重啟,之前資料沒了。
  2. 報表問題,專業的BI產品經理才行,維度得把控好才行。
  3. .自定義指標收集時候,最好不要侵入性程式碼,能解耦則解耦。