30個(gè)高可用Prometheus架構(gòu)實(shí)踐中的踩坑集錦
監(jiān)控系統(tǒng)的歷史悠久,是一個(gè)很成熟的方向,而 Prometheus 作為新生代的開源監(jiān)控系統(tǒng),慢慢成為了云原生體系的事實(shí)標(biāo)準(zhǔn),也證明了其設(shè)計(jì)很受歡迎。本文主要分享在 Prometheus 實(shí)踐中遇到的一些問題和思考,如果你對(duì) Kubernetes 監(jiān)控體系或 Prometheus 的設(shè)計(jì)還不太了解,可以先看下容器監(jiān)控系列[1]。
幾點(diǎn)原則
- 監(jiān)控是基礎(chǔ)設(shè)施,目的是為了解決問題,不要只朝著大而全去做,尤其是不必要的指標(biāo)采集,浪費(fèi)人力和存儲(chǔ)資源(To B商業(yè)產(chǎn)品例外)。
- 需要處理的告警才發(fā)出來,發(fā)出來的告警必須得到處理。
- 簡單的架構(gòu)就是最好的架構(gòu),業(yè)務(wù)系統(tǒng)都掛了,監(jiān)控也不能掛。Google SRE 里面也說避免使用 Magic 系統(tǒng),例如機(jī)器學(xué)習(xí)報(bào)警閾值、自動(dòng)修復(fù)之類。這一點(diǎn)見仁見智吧,感覺很多公司都在搞智能 AI 運(yùn)維。
Prometheus 的局限
- Prometheus 是基于 Metric 的監(jiān)控,不適用于日志(Logs)、事件(Event)、調(diào)用鏈(Tracing)。
- Prometheus 默認(rèn)是 Pull 模型,合理規(guī)劃你的網(wǎng)絡(luò),盡量不要轉(zhuǎn)發(fā)。
- 對(duì)于集群化和水平擴(kuò)展,官方和社區(qū)都沒有銀彈,需要合理選擇 Federate、Cortex、Thanos 等方案。
- 監(jiān)控系統(tǒng)一般情況下可用性大于一致性,容忍部分副本數(shù)據(jù)丟失,保證查詢請求成功。這個(gè)后面說 Thanos 去重的時(shí)候會(huì)提到。
- Prometheus 不一定保證數(shù)據(jù)準(zhǔn)確,這里的不準(zhǔn)確一是指 rate、histogram_quantile 等函數(shù)會(huì)做統(tǒng)計(jì)和推斷,產(chǎn)生一些反直覺的結(jié)果,這個(gè)后面會(huì)詳細(xì)展開。二來查詢范圍過長要做降采樣,勢必會(huì)造成數(shù)據(jù)精度丟失,不過這是時(shí)序數(shù)據(jù)的特點(diǎn),也是不同于日志系統(tǒng)的地方。
Kubernetes 集群中常用的 Exporter
Prometheus 屬于 CNCF 項(xiàng)目,擁有完整的開源生態(tài),與 Zabbix 這種傳統(tǒng) Agent 監(jiān)控不同,它提供了豐富的 Exporter 來滿足你的各種需求。你可以在這里[2]看到官方、非官方的 Exporter。如果還是沒滿足你的需求,你還可以自己編寫 Exporter,簡單方便、自由開放,這是優(yōu)點(diǎn)。
但是過于開放就會(huì)帶來選型、試錯(cuò)成本。之前只需要在 Zabbix Agent 里面幾行配置就能完成的事,現(xiàn)在你會(huì)需要很多 Exporter 搭配才能完成。還要對(duì)所有 Exporter 維護(hù)、監(jiān)控。尤其是升級(jí) Exporter 版本時(shí),很痛苦。非官方 Exporter 還會(huì)有不少 bug。這是使用上的不足,當(dāng)然也是 Prometheus 的設(shè)計(jì)原則。
Kubernetes 生態(tài)的組件都會(huì)提供 /metric 接口以提供自監(jiān)控,這里列下我們正在使用的:
- cAdvisor:集成在 Kubelet 中。
- kubelet:10255 為非認(rèn)證端口,10250 為認(rèn)證端口。
- apiserver:6443 端口,關(guān)心請求數(shù)、延遲等。
- scheduler:10251 端口。
- controller-manager:10252 端口。
- etcd:如 etcd 寫入讀取延遲、存儲(chǔ)容量等。
- Docker:需要開啟 experimental 實(shí)驗(yàn)特性,配置 metrics-addr,如容器創(chuàng)建耗時(shí)等指標(biāo)。
- kube-proxy:默認(rèn) 127 暴露,10249 端口。外部采集時(shí)可以修改為 0.0.0.0 監(jiān)聽,會(huì)暴露:寫入 iptables 規(guī)則的耗時(shí)等指標(biāo)。
- kube-state-metrics:Kubernetes 官方項(xiàng)目,采集 Pod、Deployment 等資源的元信息。
- node-exporter:Prometheus 官方項(xiàng)目,采集機(jī)器指標(biāo)如 CPU、內(nèi)存、磁盤。
- blackbox_exporter:Prometheus 官方項(xiàng)目,網(wǎng)絡(luò)探測,DNS、ping、http 監(jiān)控。
- process-exporter:采集進(jìn)程指標(biāo)。
- NVIDIA Exporter:我們有 GPU 任務(wù),需要 GPU 數(shù)據(jù)監(jiān)控。
- node-problem-detector:即 NPD,準(zhǔn)確的說不是 Exporter,但也會(huì)監(jiān)測機(jī)器狀態(tài),上報(bào)節(jié)點(diǎn)異常打 taint。
- 應(yīng)用層 Exporter:MySQL、Nginx、MQ 等,看業(yè)務(wù)需求。
還有各種場景下的自定義 Exporter,如日志提取,后面會(huì)再做介紹。
Kubernetes 核心組件監(jiān)控與 Grafana 面板
Kubernetes 集群運(yùn)行中需要關(guān)注核心組件的狀態(tài)、性能。如 kubelet、apiserver 等,基于上面提到的 Exporter 的指標(biāo),可以在 Grafana 中繪制如下圖表:
模板可以參考Grafana Dashboards for Kubernetes Administrators[3],根據(jù)運(yùn)行情況不斷調(diào)整報(bào)警閾值。
這里提一下 Grafana 雖然支持了 templates 能力,可以很方便地做多級(jí)下拉框選擇,但是不支持templates 模式下配置報(bào)警規(guī)則,相關(guān) issue[4]。
官方對(duì)這個(gè)功能解釋了一堆,可最新版本仍然沒有支持。借用 issue 的一句話吐槽下:
- It would be grate to add templates support in alerts. Otherwise the feature looks useless a bit.
關(guān)于 Grafana 的基礎(chǔ)用法,可以看這個(gè)文章[5]。
采集組件 All in One
Prometheus 體系中 Exporter 都是獨(dú)立的,每個(gè)組件各司其職,如機(jī)器資源用 Node-Exporter,GPU 有Nvidia Exporter等等。但是 Exporter 越多,運(yùn)維壓力越大,尤其是對(duì) Agent 做資源控制、版本升級(jí)。我們嘗試對(duì)一些 Exporter 進(jìn)行組合,方案有二:
- 通過主進(jìn)程拉起 N 個(gè) Exporter 進(jìn)程,仍然可以跟著社區(qū)版本做更新、bug fix。
- 用Telegraf來支持各種類型的 Input,N 合 1。
另外,Node-Exporter 不支持進(jìn)程監(jiān)控,可以加一個(gè) Process-Exporter,也可以用上邊提到的 Telegraf,使用 procstat 的 input 來采集進(jìn)程指標(biāo)。
合理選擇黃金指標(biāo)
采集的指標(biāo)有很多,我們應(yīng)該關(guān)注哪些?Google 在“SRE Handbook”中提出了“四個(gè)黃金信號(hào)”:延遲、流量、錯(cuò)誤數(shù)、飽和度。實(shí)際操作中可以使用 Use 或 Red 方法作為指導(dǎo),Use 用于資源,Red 用于服務(wù)。
- Use 方法:Utilization、Saturation、Errors。如 Cadvisor 數(shù)據(jù)
- Red 方法:Rate、Errors、Duration。如 Apiserver 性能指標(biāo)
Prometheus 采集中常見的服務(wù)分三種:
- 在線服務(wù):如 Web 服務(wù)、數(shù)據(jù)庫等,一般關(guān)心請求速率,延遲和錯(cuò)誤率即 RED 方法
- 離線服務(wù):如日志處理、消息隊(duì)列等,一般關(guān)注隊(duì)列數(shù)量、進(jìn)行中的數(shù)量,處理速度以及發(fā)生的錯(cuò)誤即 Use 方法
- 批處理任務(wù):和離線任務(wù)很像,但是離線任務(wù)是長期運(yùn)行的,批處理任務(wù)是按計(jì)劃運(yùn)行的,如持續(xù)集成就是批處理任務(wù),對(duì)應(yīng) Kubernetes 中的 Job 或 CronJob, 一般關(guān)注所花的時(shí)間、錯(cuò)誤數(shù)等,因?yàn)檫\(yùn)行周期短,很可能還沒采集到就運(yùn)行結(jié)束了,所以一般使用 Pushgateway,改拉為推。
對(duì) Use 和 Red 的實(shí)際示例可以參考《容器監(jiān)控實(shí)踐——Kubernetes 常用指標(biāo)分析[6]》這篇文章。
Kubernetes 1.16 中 cAdvisor 的指標(biāo)兼容問題
在 Kubernetes 1.16版本,cAdvisor 的指標(biāo)去掉了 pod_Name 和 container_name 的 label,替換為了 Pod 和 Container。如果你之前用這兩個(gè) label 做查詢或者 Grafana 繪圖,需要更改下 SQL 了。因?yàn)槲覀円恢敝С侄鄠€(gè) Kubernetes 版本,就通過 relabel 配置繼續(xù)保留了原來的**_name。
- metric_relabel_configs:
- - source_labels: [container]
- regex: (.+)
- target_label: container_name
- replacement: $1
- action: replace
- - source_labels: [pod]
- regex: (.+)
- target_label: pod_name
- replacement: $1
- action: replace
注意要用 metric_relabel_configs,不是 relabel_configs,采集后做的 replace。
Prometheus 采集外部 Kubernetes 集群、多集群
Prometheus 如果部署在 Kubernetes 集群內(nèi)采集是很方便的,用官方給的 Yaml 就可以,但我們因?yàn)闄?quán)限和網(wǎng)絡(luò)需要部署在集群外,二進(jìn)制運(yùn)行,采集多個(gè) Kubernetes 集群。
以 Pod 方式運(yùn)行在集群內(nèi)是不需要證書的(In-Cluster 模式),但集群外需要聲明 token 之類的證書,并替換 __address__,即使用 Apiserver Proxy 采集,以 cAdvisor 采集為例,Job 配置為:
- - job_name: cluster-cadvisor
- honor_timestamps: true
- scrape_interval: 30s
- scrape_timeout: 10s
- metrics_path: /metrics
- scheme: https
- kubernetes_sd_configs:
- - api_server: https://xx:6443
- role: node
- bearer_token_file: token/cluster.token
- tls_config:
- insecure_skip_verify: true
- bearer_token_file: token/cluster.token
- tls_config:
- insecure_skip_verify: true
- relabel_configs:
- - separator: ;
- regex: __meta_kubernetes_node_label_(.+)
- replacement: $1
- action: labelmap
- - separator: ;
- regex: (.*)
- target_label: __address__
- replacement: xx:6443
- action: replace
- - source_labels: [__meta_kubernetes_node_name]
- separator: ;
- regex: (.+)
- target_label: __metrics_path__
- replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
- action: replace
- metric_relabel_configs:
- - source_labels: [container]
- separator: ;
- regex: (.+)
- target_label: container_name
- replacement: $1
- action: replace
- - source_labels: [pod]
- separator: ;
- regex: (.+)
- target_label: pod_name
- replacement: $1
- action: replace
bearer_token_file 需要提前生成,這個(gè)參考官方文檔即可。記得 base64 解碼。
對(duì)于 cAdvisor 來說,__metrics_path__ 可以轉(zhuǎn)換為 /api/v1/nodes/${1}/proxy/metrics/cadvisor,代表 Apiserver proxy 到 Kubelet。
如果網(wǎng)絡(luò)能通,其實(shí)也可以直接把 Kubelet 的 10255 作為 target,可以直接寫為:${1}:10255/metrics/cadvisor,代表直接請求 Kubelet,規(guī)模大的時(shí)候還減輕了 Apiserver 的壓力,即服務(wù)發(fā)現(xiàn)使用 Apiserver,采集不走 Apiserver。
因?yàn)?cAdvisor 是暴露主機(jī)端口,配置相對(duì)簡單,如果是 kube-state-metric 這種 Deployment,以 Endpoint 形式暴露,寫法應(yīng)該是:
- - job_name: cluster-service-endpoints
- honor_timestamps: true
- scrape_interval: 30s
- scrape_timeout: 10s
- metrics_path: /metrics
- scheme: https
- kubernetes_sd_configs:
- - api_server: https://xxx:6443
- role: endpoints
- bearer_token_file: token/cluster.token
- tls_config:
- insecure_skip_verify: true
- bearer_token_file: token/cluster.token
- tls_config:
- insecure_skip_verify: true
- relabel_configs:
- - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
- separator: ;
- regex: "true"
- replacement: $1
- action: keep
- - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
- separator: ;
- regex: (https?)
- target_label: __scheme__
- replacement: $1
- action: replace
- - separator: ;
- regex: (.*)
- target_label: __address__
- replacement: xxx:6443
- action: replace
- - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_endpoints_name,
- __meta_kubernetes_service_annotation_prometheus_io_port]
- separator: ;
- regex: (.+);(.+);(.*)
- target_label: __metrics_path__
- replacement: /api/v1/namespaces/${1}/services/${2}:${3}/proxy/metrics
- action: replace
- - separator: ;
- regex: __meta_kubernetes_service_label_(.+)
- replacement: $1
- action: labelmap
- - source_labels: [__meta_kubernetes_namespace]
- separator: ;
- regex: (.*)
- target_label: kubernetes_namespace
- replacement: $1
- action: replace
- - source_labels: [__meta_kubernetes_service_name]
- separator: ;
- regex: (.*)
- target_label: kubernetes_name
- replacement: $1
- action: replace
對(duì)于 Endpoint 類型,需要轉(zhuǎn)換 __metrics_path__ 為 /api/v1/namespaces/${1}/services/${2}:${3}/proxy/metrics,需要替換 namespace、svc 名稱端口等。
這里的寫法只適合接口為 /metrics 的 exporter,如果你的 exporter 不是 /metrics 接口,需要替換這個(gè)路徑。或者像我們一樣統(tǒng)一約束都使用這個(gè)地址。
這里的 __meta_kubernetes_service_annotation_prometheus_io_port 來源就是 exporter 部署時(shí)寫的那個(gè) annotation,大多數(shù)文章中只提到 prometheus.io/scrape: 'true',但也可以定義端口、路徑、協(xié)議。以方便在采集時(shí)做替換處理。
其他的一些 relabel 如 kubernetes_namespace 是為了保留原始信息,方便做 PromQL 查詢時(shí)的篩選條件。
如果是多集群,同樣的配置多寫幾遍就可以了,一般一個(gè)集群可以配置三類 Job:
- role:node 的,包括 cAdvisor、 node-exporter、kubelet 的 summary、kube-proxy、Docker 等指標(biāo)
- role:endpoint 的,包括 kube-state-metric 以及其他自定義 Exporter
- 普通采集:包括 etcd、Apiserver 性能指標(biāo)、進(jìn)程指標(biāo)等。
GPU 指標(biāo)的獲取
nvidia-smi 可以查看機(jī)器上的 GPU 資源,而 cAdvisor 其實(shí)暴露了 Metric 來表示容器使用 GPU 情況。
- container_accelerator_duty_cycle
- container_accelerator_memory_total_bytes
- container_accelerator_memory_used_bytes
如果要更詳細(xì)的 GPU 數(shù)據(jù),可以安裝 dcgm exporter,不過 Kubernetes 1.13 才能支持。
更改 Prometheus 的顯示時(shí)區(qū)
Prometheus 為避免時(shí)區(qū)混亂,在所有組件中專門使用 Unix Time 和 Utc 進(jìn)行顯示。不支持在配置文件中設(shè)置時(shí)區(qū),也不能讀取本機(jī) /etc/timezone 時(shí)區(qū)。
其實(shí)這個(gè)限制是不影響使用的:
- 如果做可視化,Grafana 是可以做時(shí)區(qū)轉(zhuǎn)換的。
- 如果是調(diào)接口,拿到了數(shù)據(jù)中的時(shí)間戳,你想怎么處理都可以。
- 如果因?yàn)?Prometheus 自帶的 UI 不是本地時(shí)間,看著不舒服,2.16 版本的新版 Web UI已經(jīng)引入了 Local Timezone 的選項(xiàng),區(qū)別見下圖。
- 如果你仍然想改 Prometheus 代碼來適應(yīng)自己的時(shí)區(qū),可以參考這篇文章[7]。
關(guān)于 timezone 的討論,可以看這個(gè) issue[8]。
如何采集 LB 后面的 RS 的 Metric
假如你有一個(gè)負(fù)載均衡 LB,但網(wǎng)絡(luò)上 Prometheus 只能訪問到 LB 本身,訪問不到后面的 RS,應(yīng)該如何采集 RS 暴露的 Metric?
- RS 的服務(wù)加 Sidecar Proxy,或者本機(jī)增加 Proxy 組件,保證 Prometheus 能訪問到。
- LB 增加 /backend1 和 /backend2請求轉(zhuǎn)發(fā)到兩個(gè)單獨(dú)的后端,再由 Prometheus 訪問 LB 采集。
版本的選擇
Prometheus 當(dāng)前最新版本為 2.16,Prometheus 還在不斷迭代,因此盡量用最新版,1.X版本就不用考慮了。
2.16 版本上有一套實(shí)驗(yàn) UI,可以查看 TSDB 的狀態(tài),包括Top 10的 Label、Metric。
Prometheus 大內(nèi)存問題
隨著規(guī)模變大,Prometheus 需要的 CPU 和內(nèi)存都會(huì)升高,內(nèi)存一般先達(dá)到瓶頸,這個(gè)時(shí)候要么加內(nèi)存,要么集群分片減少單機(jī)指標(biāo)。這里我們先討論單機(jī)版 Prometheus 的內(nèi)存問題。
原因:
- Prometheus 的內(nèi)存消耗主要是因?yàn)槊扛?2 小時(shí)做一個(gè) Block 數(shù)據(jù)落盤,落盤之前所有數(shù)據(jù)都在內(nèi)存里面,因此和采集量有關(guān)。
- 加載歷史數(shù)據(jù)時(shí),是從磁盤到內(nèi)存的,查詢范圍越大,內(nèi)存越大。這里面有一定的優(yōu)化空間。
- 一些不合理的查詢條件也會(huì)加大內(nèi)存,如 Group 或大范圍 Rate。
我的指標(biāo)需要多少內(nèi)存:
- 作者給了一個(gè)計(jì)算器,設(shè)置指標(biāo)量、采集間隔之類的,計(jì)算 Prometheus 需要的理論內(nèi)存值:計(jì)算公式[9]。
以我們的一個(gè) Prometheus Server 為例,本地只保留 2 小時(shí)數(shù)據(jù),95 萬 Series,大概占用的內(nèi)存如下:
有什么優(yōu)化方案:
- Sample 數(shù)量超過了 200 萬,就不要單實(shí)例了,做下分片,然后通過 Victoriametrics,Thanos,Trickster 等方案合并數(shù)據(jù)。
- 評(píng)估哪些 Metric 和 Label 占用較多,去掉沒用的指標(biāo)。2.14 以上可以看 TSDB 狀態(tài)
- 查詢時(shí)盡量避免大范圍查詢,注意時(shí)間范圍和 Step 的比例,慎用 Group。
- 如果需要關(guān)聯(lián)查詢,先想想能不能通過 Relabel 的方式給原始數(shù)據(jù)多加個(gè) Label,一條 SQL 能查出來的何必用 Join,時(shí)序數(shù)據(jù)庫不是關(guān)系數(shù)據(jù)庫。
Prometheus 內(nèi)存占用分析:
- 通過 pprof 分析:https://www.robustperception.io/optimising-prometheus-2-6-0-memory-usage-with-pprof
- 1.X 版本的內(nèi)存:https://www.robustperception.io/how-much-ram-does-my-prometheus-need-for-ingestion
相關(guān) issue:
- https://groups.google.com/forum/#!searchin/prometheus-users/memory%7Csort:date/prometheus-users/q4oiVGU6Bxo/uifpXVw3CwAJ
- https://github.com/prometheus/prometheus/issues/5723
- https://github.com/prometheus/prometheus/issues/1881
Prometheus 容量規(guī)劃
容量規(guī)劃除了上邊說的內(nèi)存,還有磁盤存儲(chǔ)規(guī)劃,這和你的 Prometheus 的架構(gòu)方案有關(guān)。
- 如果是單機(jī) Prometheus,計(jì)算本地磁盤使用量。
- 如果是 Remote-Write,和已有的 TSDB 共用即可。
- 如果是 Thanos 方案,本地磁盤可以忽略(2H),計(jì)算對(duì)象存儲(chǔ)的大小就行。
Prometheus 每 2 小時(shí)將已緩沖在內(nèi)存中的數(shù)據(jù)壓縮到磁盤上的塊中。包括Chunks、Indexes、Tombstones、Metadata,這些占用了一部分存儲(chǔ)空間。一般情況下,Prometheus 中存儲(chǔ)的每一個(gè)樣本大概占用 1-2 字節(jié)大?。?.7Byte)??梢酝ㄟ^ PromQL 來查看每個(gè)樣本平均占用多少空間:
- rate(prometheus_tsdb_compaction_chunk_size_bytes_sum[1h])
- rate(prometheus_tsdb_compaction_chunk_samples_sum[1h])
- instance="0.0.0.0:8890", job="prometheus"} 1.252747585939941
如果大致估算本地磁盤大小,可以通過以下公式:
- 磁盤大小 = 保留時(shí)間 * 每秒獲取樣本數(shù) * 樣本大小
保留時(shí)間(retention_time_seconds)和樣本大?。╞ytes_per_sample)不變的情況下,如果想減少本地磁盤的容量需求,只能通過減少每秒獲取樣本數(shù)(ingested_samples_per_second)的方式。
查看當(dāng)前每秒獲取的樣本數(shù):
- rate(prometheus_tsdb_head_samples_appended_total[1h])
有兩種手段,一是減少時(shí)間序列的數(shù)量,二是增加采集樣本的時(shí)間間隔。考慮到 Prometheus 會(huì)對(duì)時(shí)間序列進(jìn)行壓縮,因此減少時(shí)間序列的數(shù)量效果更明顯。
舉例說明:
- 采集頻率 30s,機(jī)器數(shù)量 1000,Metric 種類 6000,1000600026024 約 200 億,30G 左右磁盤。
- 只采集需要的指標(biāo),如 match[], 或者統(tǒng)計(jì)下最常使用的指標(biāo),性能最差的指標(biāo)。
以上磁盤容量并沒有把 wal 文件算進(jìn)去,wal 文件(Raw Data)在 Prometheus 官方文檔中說明至少會(huì)保存 3 個(gè) Write-Ahead Log Files,每一個(gè)最大為 128M(實(shí)際運(yùn)行發(fā)現(xiàn)數(shù)量會(huì)更多)。
因?yàn)槲覀兪褂昧?Thanos 的方案,所以本地磁盤只保留 2H 熱數(shù)據(jù)。Wal 每 2 小時(shí)生成一份 Block 文件,Block 文件每 2 小時(shí)上傳對(duì)象存儲(chǔ),本地磁盤基本沒有壓力。
關(guān)于 Prometheus 存儲(chǔ)機(jī)制,可以看這篇[10]。
對(duì) Apiserver 的性能影響
如果你的 Prometheus 使用了 kubernetes_sd_config 做服務(wù)發(fā)現(xiàn),請求一般會(huì)經(jīng)過集群的 Apiserver,隨著規(guī)模的變大,需要評(píng)估下對(duì) Apiserver 性能的影響,尤其是 Proxy 失敗的時(shí)候,會(huì)導(dǎo)致 CPU 升高。當(dāng)然了,如果單 Kubernetes 集群規(guī)模太大,一般都是拆分集群,不過隨時(shí)監(jiān)測下 Apiserver 的進(jìn)程變化還是有必要的。
在監(jiān)控 cAdvisor、Docker、Kube-Proxy 的 Metric 時(shí),我們一開始選擇從 Apiserver Proxy 到節(jié)點(diǎn)的對(duì)應(yīng)端口,統(tǒng)一設(shè)置比較方便,但后來還是改為了直接拉取節(jié)點(diǎn),Apiserver 僅做服務(wù)發(fā)現(xiàn)。
Rate 的計(jì)算邏輯
Prometheus 中的 Counter 類型主要是為了 Rate 而存在的,即計(jì)算速率,單純的 Counter 計(jì)數(shù)意義不大,因?yàn)?Counter 一旦重置,總計(jì)數(shù)就沒有意義了。
Rate 會(huì)自動(dòng)處理 Counter 重置的問題,Counter 一般都是一直變大的,例如一個(gè) Exporter 啟動(dòng),然后崩潰了。本來以每秒大約 10 的速率遞增,但僅運(yùn)行了半個(gè)小時(shí),則速率(x_total [1h])將返回大約每秒 5 的結(jié)果。另外,Counter 的任何減少也會(huì)被視為 Counter 重置。例如,如果時(shí)間序列的值為[5,10,4,6],則將其視為[5,10,14,16]。
Rate 值很少是精確的。由于針對(duì)不同目標(biāo)的抓取發(fā)生在不同的時(shí)間,因此隨著時(shí)間的流逝會(huì)發(fā)生抖動(dòng),query_range 計(jì)算時(shí)很少會(huì)與抓取時(shí)間完美匹配,并且抓取有可能失敗。面對(duì)這樣的挑戰(zhàn),Rate 的設(shè)計(jì)必須是健壯的。
Rate 并非想要捕獲每個(gè)增量,因?yàn)橛袝r(shí)候增量會(huì)丟失,例如實(shí)例在抓取間隔中掛掉。如果 Counter 的變化速度很慢,例如每小時(shí)僅增加幾次,則可能會(huì)導(dǎo)致【假象】。比如出現(xiàn)一個(gè) Counter 時(shí)間序列,值為 100,Rate 就不知道這些增量是現(xiàn)在的值,還是目標(biāo)已經(jīng)運(yùn)行了好幾年并且才剛剛開始返回。
建議將 Rate 計(jì)算的范圍向量的時(shí)間至少設(shè)為抓取間隔的四倍。這將確保即使抓取速度緩慢,且發(fā)生了一次抓取故障,您也始終可以使用兩個(gè)樣本。此類問題在實(shí)踐中經(jīng)常出現(xiàn),因此保持這種彈性非常重要。例如,對(duì)于 1 分鐘的抓取間隔,您可以使用 4 分鐘的 Rate 計(jì)算,但是通常將其四舍五入為 5 分鐘。
如果 Rate 的時(shí)間區(qū)間內(nèi)有數(shù)據(jù)缺失,他會(huì)基于趨勢進(jìn)行推測,比如:
詳細(xì)的內(nèi)容可以看下這個(gè)視頻[11]。
反直覺的 P95 統(tǒng)計(jì)
histogram_quantile 是 Prometheus 常用的一個(gè)函數(shù),比如經(jīng)常把某個(gè)服務(wù)的 P95 響應(yīng)時(shí)間來衡量服務(wù)質(zhì)量。不過它到底是什么意思很難解釋得清,特別是面向非技術(shù)的同學(xué),會(huì)遇到很多“靈魂拷問”。
我們常說 P95(P99,P90都可以) 響應(yīng)延遲是 100ms,實(shí)際上是指對(duì)于收集到的所有響應(yīng)延遲,有 5% 的請求大于 100ms,95% 的請求小于 100ms。Prometheus 里面的 histogram_quantile 函數(shù)接收的是 0-1 之間的小數(shù),將這個(gè)小數(shù)乘以 100 就能很容易得到對(duì)應(yīng)的百分位數(shù),比如 0.95 就對(duì)應(yīng)著 P95,而且還可以高于百分位數(shù)的精度,比如 0.9999。
當(dāng)你用 histogram_quantile 畫出響應(yīng)時(shí)間的趨勢圖時(shí),可能會(huì)被問:為什么 P95 大于或小于我的平均值?
正如中位數(shù)可能比平均數(shù)大也可能比平均數(shù)小,P99 比平均值小也是完全有可能的。通常情況下 P99 幾乎總是比平均值要大的,但是如果數(shù)據(jù)分布比較極端,最大的 1% 可能大得離譜從而拉高了平均值。一種可能的例子:
- 1, 1, ... 1, 901 // 共 100 條數(shù)據(jù),平均值=10,P99=1
服務(wù) X 由順序的 A,B 兩個(gè)步驟完成,其中 X 的 P99 耗時(shí) 100ms,A 過程 P99 耗時(shí) 50ms,那么推測 B 過程的 P99 耗時(shí)情況是?
直覺上來看,因?yàn)橛?X=A+B,所以答案可能是 50ms,或者至少應(yīng)該要小于 50ms。實(shí)際上 B 是可以大于 50ms 的,只要 A 和 B 最大的 1% 不恰好遇到,B 完全可以有很大的 P99:
- A = 1, 1, ... 1, 1, 1, 50, 50 // 共 100 條數(shù)據(jù),P99=50
- B = 1, 1, ... 1, 1, 1, 99, 99 // 共 100 條數(shù)據(jù),P99=99
- X = 2, 2, ... 1, 51, 51, 100, 100 // 共 100 條數(shù)據(jù),P99=100
如果讓 A 過程最大的 1% 接近 100ms,我們也能構(gòu)造出 P99 很小的 B:
- A = 50, 50, ... 50, 50, 99 // 共 100 條數(shù)據(jù),P99=50
- B = 1, 1, ... 1, 1, 50 // 共 100 條數(shù)據(jù),P99=1
- X = 51, 51, ... 51, 100, 100 // 共 100 條數(shù)據(jù),P99=100
所以我們從題目唯一能確定的只有 B 的 P99 應(yīng)該不能超過 100ms,A 的 P99 耗時(shí) 50ms 這個(gè)條件其實(shí)沒啥用。
類似的疑問很多,因此對(duì)于 histogram_quantile 函數(shù),可能會(huì)產(chǎn)生反直覺的一些結(jié)果,最好的處理辦法是不斷試驗(yàn)調(diào)整你的 Bucket 的值,保證更多的請求時(shí)間落在更細(xì)致的區(qū)間內(nèi),這樣的請求時(shí)間才有統(tǒng)計(jì)意義。
慢查詢問題
PromQL 的基礎(chǔ)知識(shí)看這篇文章[12]。
Prometheus 提供了自定義的 PromQL 作為查詢語句,在 Graph 上調(diào)試的時(shí)候,會(huì)告訴你這條 SQL 的返回時(shí)間,如果太慢你就要注意了,可能是你的用法出現(xiàn)了問題。
評(píng)估 Prometheus 的整體響應(yīng)時(shí)間,可以用這個(gè)默認(rèn)指標(biāo):
- prometheus_engine_query_duration_seconds{}
一般情況下響應(yīng)過慢都是 PromQL 使用不當(dāng)導(dǎo)致,或者指標(biāo)規(guī)劃有問題,如:
- 大量使用 join 來組合指標(biāo)或者增加 label,如將 kube-state-metric 中的一些 meta label 和 node-exporter 中的節(jié)點(diǎn)屬性 label 加入到 cAdvisor容器數(shù)據(jù)里,像統(tǒng)計(jì) Pod 內(nèi)存使用率并按照所屬節(jié)點(diǎn)的機(jī)器類型分類,或按照所屬 RSS 歸類。
- 范圍查詢時(shí),大的時(shí)間范圍 step 值卻很小,導(dǎo)致查詢到的數(shù)量過大。
- rate 會(huì)自動(dòng)處理 counter 重置的問題,最好由 PromQL 完成,不要自己拿出來全部元數(shù)據(jù)在程序中自己做 rate 計(jì)算。
- 在使用 rate 時(shí),range duration 要大于等于 step,否則會(huì)丟失部分?jǐn)?shù)據(jù)。
- Prometheus 是有基本預(yù)測功能的,如 deriv 和 predict_linear(更準(zhǔn)確)可以根據(jù)已有數(shù)據(jù)預(yù)測未來趨勢。
- 如果比較復(fù)雜且耗時(shí)的 SQL,可以使用 record rule 減少指標(biāo)數(shù)量,并使查詢效率更高,但不要什么指標(biāo)都加 record,一半以上的 metric 其實(shí)不太會(huì)查詢到。同時(shí) label 中的值不要加到 record rule 的 name 中。
高基數(shù)問題 Cardinality
高基數(shù)是數(shù)據(jù)庫避不開的一個(gè)話題,對(duì)于 MySQL 這種 DB 來講,基數(shù)是指特定列或字段中包含的唯一值的數(shù)量。基數(shù)越低,列中重復(fù)的元素越多。對(duì)于時(shí)序數(shù)據(jù)庫而言,就是 tags、label 這種標(biāo)簽值的數(shù)量多少。
比如 Prometheus 中如果有一個(gè)指標(biāo) http_request_count{method="get",path="/abc",originIP="1.1.1.1"} 表示訪問量,method 表示請求方法,originIP 是客戶端 IP,method 的枚舉值是有限的,但 originIP 卻是無限的,加上其他 label 的排列組合就無窮大了,也沒有任何關(guān)聯(lián)特征,因此這種高基數(shù)不適合作為 Metric 的 label,真要的提取 originIP,應(yīng)該用日志的方式,而不是 Metric 監(jiān)控。
時(shí)序數(shù)據(jù)庫會(huì)為這些 Label 建立索引,以提高查詢性能,以便你可以快速找到與所有指定標(biāo)簽匹配的值。如果值的數(shù)量過多,索引是沒有意義的,尤其是做 P95 等計(jì)算的時(shí)候,要掃描大量 Series 數(shù)據(jù)。
官方文檔中對(duì)于 Label 的建議:
CAUTION: Remember that every unique combination of key-value label pairs represents a new time series, which can dramatically increase the amount of data stored. Do not use labels to store dimensions with high cardinality (many different label values), such as user IDs, email addresses, or other unbounded sets of values.
如何查看當(dāng)前的 Label 分布情況呢,可以使用 Prometheus 提供的 TSDB 工具。可以使用命令行查看,也可以在 2.16 版本以上的 Prometheus Graph 查看。
- [work@xxx bin]$ ./tsdb analyze ../data/prometheus/
- Block ID: 01E41588AJNGM31SPGHYA3XSXG
- Duration: 2h0m0s
- Series: 955372
- Label names: 301
- Postings (unique label pairs): 30757
- Postings entries (total label pairs): 10842822
- ....
top10 高基數(shù)的 metric:
- Highest cardinality metric names:
- 87176 apiserver_request_latencies_bucket
- 59968 apiserver_response_sizes_bucket
- 39862 apiserver_request_duration_seconds_bucket
- 37555 container_tasks_state
- ....
高基數(shù)的 label:
- Highest cardinality labels:
- 4271 resource_version
- 3670 id
- 3414 name
- 1857 container_id
- 1824 __name__
- 1297 uid
- 1276 pod
- ...
找到最大的 Metric 或 Job
top10的 Metric 數(shù)量:按 Metric 名字分。
- topk(10, count by (__name__)({__name__=~".+"}))
- apiserver_request_latencies_bucket{} 62544
- apiserver_response_sizes_bucket{} 44600
top10的 Job 數(shù)量:按 Job 名字分。
- topk(10, count by (__name__, job)({__name__=~".+"}))
- {job="master-scrape"} 525667
- {job="xxx-kubernetes-cadvisor"} 50817
- {job="yyy-kubernetes-cadvisor"} 44261
Prometheus 重啟慢與熱加載
Prometheus 重啟的時(shí)候需要把 Wal 中的內(nèi)容 Load 到內(nèi)存里,保留時(shí)間越久、Wal 文件越大,重啟的實(shí)際越長,這個(gè)是 Prometheus 的機(jī)制,沒得辦法,因此能 Reload 的就不要重啟,重啟一定會(huì)導(dǎo)致短時(shí)間的不可用,而這個(gè)時(shí)候Prometheus高可用就很重要了。
Prometheus 也曾經(jīng)對(duì)啟動(dòng)時(shí)間做過優(yōu)化,在 2.6 版本中對(duì)于 Wal 的 Load 速度就做過速度的優(yōu)化,希望重啟的時(shí)間不超過 1 分鐘。
Prometheus 提供了熱加載能力,不過需要開啟 web.enable-lifecycle 配置,更改完配置后,curl 下 reload 接口即可。prometheus-operator 中更改了配置會(huì)默認(rèn)觸發(fā) reload,如果你沒有使用 Operator,又希望可以監(jiān)聽 ConfigMap 配置變化來 reload 服務(wù),可以試下這個(gè)簡單的腳本。
- #!/bin/sh
- FILE=$1
- URL=$2
- HASH=$(md5sum $(readlink -f $FILE))
- while true; do
- NEW_HASH=$(md5sum $(readlink -f $FILE))
- if [ "$HASH" != "$NEW_HASH" ]; then
- HASH="$NEW_HASH"
- echo "[$(date +%s)] Trigger refresh"
- curl -sSL -X POST "$2" > /dev/null
- fi
- sleep 5
- done
使用時(shí)和 Prometheus 掛載同一個(gè) ConfigMap,傳入如下參數(shù)即可:
- args:
- - /etc/prometheus/prometheus.yml
- - http://prometheus.kube-system.svc.cluster.local:9090/-/reload
- args:
- - /etc/alertmanager/alertmanager.yml
- - http://prometheus.kube-system.svc.cluster.local:9093/-/reload
你的應(yīng)用需要暴露多少指標(biāo)
當(dāng)你開發(fā)自己的服務(wù)的時(shí)候,你可能會(huì)把一些數(shù)據(jù)暴露 Metric 出去,比如特定請求數(shù)、Goroutine 數(shù)等,指標(biāo)數(shù)量多少合適呢?
雖然指標(biāo)數(shù)量和你的應(yīng)用規(guī)模相關(guān),但也有一些建議(Brian Brazil),比如簡單的服務(wù),如緩存等,類似 Pushgateway,大約 120 個(gè)指標(biāo),Prometheus 本身暴露了 700 左右的指標(biāo),如果你的應(yīng)用很大,也盡量不要超過 10000 個(gè)指標(biāo),需要合理控制你的 Label。
node-exporter 的問題
- node-exporter 不支持進(jìn)程監(jiān)控,這個(gè)前面已經(jīng)提到了。
- node-exporter 只支持 Unix 系統(tǒng),Windows 機(jī)器請使用 wmi_exporter。因此以 yaml 形式不是 node-exporter 的時(shí)候,node-selector 要表明 OS 類型。
- 因?yàn)?node_exporter 是比較老的組件,有一些最佳實(shí)踐并沒有 merge 進(jìn)去,比如符合 Prometheus 命名規(guī)范,因此建議使用較新的 0.16 和 0.17 版本。
一些指標(biāo)名字的變化:
- * node_cpu -> node_cpu_seconds_total
- * node_memory_MemTotal -> node_memory_MemTotal_bytes
- * node_memory_MemFree -> node_memory_MemFree_bytes
- * node_filesystem_avail -> node_filesystem_avail_bytes
- * node_filesystem_size -> node_filesystem_size_bytes
- * node_disk_io_time_ms -> node_disk_io_time_seconds_total
- * node_disk_reads_completed -> node_disk_reads_completed_total
- * node_disk_sectors_written -> node_disk_written_bytes_total
- * node_time -> node_time_seconds
- * node_boot_time -> node_boot_time_seconds
- * node_intr -> node_intr_total
如果你之前用的舊版本 Exporter,在繪制 Grafana 的時(shí)候指標(biāo)名稱就會(huì)有差別,解決方法有兩種:
- 一是在機(jī)器上啟動(dòng)兩個(gè)版本的 node-exporter,都讓 Prometheus 去采集。
- 二是使用指標(biāo)轉(zhuǎn)換器,他會(huì)將舊指標(biāo)名稱轉(zhuǎn)換為新指標(biāo)。
kube-state-metric 的問題
kube-state-metric 的使用和原理可以先看下這篇[13]。
除了文章中提到的作用,kube-state-metric 還有一個(gè)很重要的使用場景,就是和 cAdvisor 指標(biāo)組合,原始的 cAdvisor 中只有 Pod 信息,不知道屬于哪個(gè) Deployment 或者 sts,但是和 kube-state-metric 中的 kube_pod_info 做 join 查詢之后就可以顯示出來,kube-state-metric 的元數(shù)據(jù)指標(biāo),在擴(kuò)展 cAdvisor 的 label 中起到了很多作用,prometheus-operator 的很多 record rule 就使用了 kube-state-metric 做組合查詢。
kube-state-metric 中也可以展示 Pod 的 label 信息,可以在拿到 cAdvisor 數(shù)據(jù)后更方便地做 group by,如按照 Pod 的運(yùn)行環(huán)境分類。但是 kube-state-metric 不暴露 Pod 的 annotation,原因是下面會(huì)提到的高基數(shù)問題,即 annotation 的內(nèi)容太多,不適合作為指標(biāo)暴露。
relabel_configs 與 metric_relabel_configs
relabel_config 發(fā)生在采集之前,metric_relabel_configs 發(fā)生在采集之后,合理搭配可以滿足很多場景的配置。
如:
- metric_relabel_configs:
- - separator: ;
- regex: instance
- replacement: $1
- action: labeldrop
- - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_endpoints_name,
- __meta_kubernetes_service_annotation_prometheus_io_port]
- separator: ;
- regex: (.+);(.+);(.*)
- target_label: __metrics_path__
- replacement: /api/v1/namespaces/${1}/services/${2}:${3}/proxy/metrics
- action: replace
Prometheus 的預(yù)測能力
場景 1:你的磁盤剩余空間一直在減少,并且降低的速度比較均勻,你希望知道大概多久之后達(dá)到閾值,并希望在某一個(gè)時(shí)刻報(bào)警出來。
場景 2:你的 Pod 內(nèi)存使用率一直升高,你希望知道大概多久之后會(huì)到達(dá) Limit 值,并在一定時(shí)刻報(bào)警出來,在被殺掉之前上去排查。
Prometheus 的 Deriv 和 Predict_Linear 方法可以滿足這類需求, Promtheus 提供了基礎(chǔ)的預(yù)測能力,基于當(dāng)前的變化速度,推測一段時(shí)間后的值。
以 mem_free 為例,最近一小時(shí)的 free 值一直在下降。
- mem_free 僅為舉例,實(shí)際內(nèi)存可用以 mem_available 為準(zhǔn)
Deriv 函數(shù)可以顯示指標(biāo)在一段時(shí)間的變化速度:
predict_linear 方法是預(yù)測基于這種速度,最后可以達(dá)到的值:
- predict_linear(mem_free{instanceIP="100.75.155.55"}[1h], 2*3600)/1024/1024
你可以基于設(shè)置合理的報(bào)警規(guī)則,如小于 10 時(shí)報(bào)警:
- bash
- rule: predict_linear(mem_free{instanceIP="100.75.155.55"}[1h], 2*3600)/1024/1024 <10
predict_linear 與 Deriv 的關(guān)系:含義上約等于如下表達(dá)式,不過 predict_linear 稍微準(zhǔn)確一些。
- deriv(mem_free{instanceIP="100.75.155.55"}[1h]) * 2 * 3600
- +
- mem_free{instanceIP="100.75.155.55"}[1h]
如果你要基于 Metric 做模型預(yù)測,可以參考下 forecast-prometheus[14]。
alertmanager 的上層封裝
Prometheus 部署之后很少會(huì)改動(dòng),尤其是做了服務(wù)發(fā)現(xiàn),就不需要頻繁新增 target。但報(bào)警的配置是很頻繁的,如修改閾值、修改報(bào)警人等。alertmanager 擁有豐富的報(bào)警能力如分組、抑制等,但如果你要想把它給業(yè)務(wù)部門使用,就要做一層封裝了,也就是報(bào)警配置臺(tái)。用戶喜歡表單操作,而非晦澀的 yaml,同時(shí)他們也并不愿意去理解 PromQL。而且大多數(shù)公司內(nèi)已經(jīng)有現(xiàn)成的監(jiān)控平臺(tái),也只有一份短信或郵件網(wǎng)關(guān),所以最好能使用 webhook 直接集成。
例如:機(jī)器磁盤使用量超過 90% 就報(bào)警,rule 應(yīng)該寫為:disk_used/disk_total > 0.9。
如果不加 label 篩選,這條報(bào)警會(huì)對(duì)所有機(jī)器生效,但如果你想去掉其中幾臺(tái)機(jī)器,就得在 disk_used 和 disk_total 后面加上 {instance != ""}。這些操作在 PromQL 中是很簡單的,但是如果放在表單里操作,就得和內(nèi)部的 CMDB 做聯(lián)動(dòng)篩選了。
對(duì)于一些簡單的需求,我們使用了 Grafana 的報(bào)警能力,所見即所得,直接在圖表下面配置告警即可,報(bào)警閾值和狀態(tài)很清晰。不過 Grafana 的報(bào)警能力很弱,只是實(shí)驗(yàn)功能,可以作為調(diào)試使用。
對(duì)于常見的 Pod 或應(yīng)用監(jiān)控,我們做了一些表單化,如下圖所示:提取了 CPU、內(nèi)存、磁盤 IO 等常見的指標(biāo)作為選擇項(xiàng),方便配置。
使用 webhook 擴(kuò)展報(bào)警能力,改造 alertmanager, 在 send message 時(shí)做加密和認(rèn)證,對(duì)接內(nèi)部已有報(bào)警能力,并聯(lián)動(dòng)用戶體系,做限流和權(quán)限控制。
調(diào)用 alertmanager api 查詢報(bào)警事件,進(jìn)行展示和統(tǒng)計(jì)。
對(duì)于用戶來說,封裝 alertmanager yaml 會(huì)變的易用,但也會(huì)限制其能力,在增加報(bào)警配置時(shí),研發(fā)和運(yùn)維需要有一定的配合。如新寫了一份自定義的 exporter,要將需要的指標(biāo)供用戶選擇,并調(diào)整好展示和報(bào)警用的 PromQL。還有報(bào)警模板、原生 PromQL 暴露、用戶分組等,需要視用戶需求做權(quán)衡。
錯(cuò)誤的高可用設(shè)計(jì)
有些人提出過這種類型的方案,想提高其擴(kuò)展性和可用性。
應(yīng)用程序?qū)?Metric 推到到消息隊(duì)列如 Kafaka,然后經(jīng)過 Exposer 消費(fèi)中轉(zhuǎn),再被 Prometheus 拉取。產(chǎn)生這種方案的原因一般是有歷史包袱、復(fù)用現(xiàn)有組件、想通過 MQ 來提高擴(kuò)展性。
這種方案有幾個(gè)問題:
- 增加了 Queue 組件,多了一層依賴,如果 App 與 Queue 之間連接失敗,難道要在 App 本地緩存監(jiān)控?cái)?shù)據(jù)?
- 抓取時(shí)間可能會(huì)不同步,延遲的數(shù)據(jù)將會(huì)被標(biāo)記為陳舊數(shù)據(jù),當(dāng)然你可以通過添加時(shí)間戳來標(biāo)識(shí),但就失去了對(duì)陳舊數(shù)據(jù)的處理邏輯。
- 擴(kuò)展性問題:Prometheus 適合大量小目標(biāo),而不是一個(gè)大目標(biāo),如果你把所有數(shù)據(jù)都放在了 Exposer 中,那么 Prometheus 的單個(gè) Job 拉取就會(huì)成為 CPU 瓶頸。這個(gè)和 Pushgateway 有些類似,沒有特別必要的場景,都不是官方建議的方式。
4. 缺少了服務(wù)發(fā)現(xiàn)和拉取控制,Prom 只知道一個(gè) Exposer,不知道具體是哪些 Target,不知道他們的 UP 時(shí)間,無法使用 Scrape_* 等指標(biāo)做查詢,也無法用 scrape_limit 做限制。
如果你的架構(gòu)和 Prometheus 的設(shè)計(jì)理念相悖,可能要重新設(shè)計(jì)一下方案了,否則擴(kuò)展性和可靠性反而會(huì)降低。
prometheus-operator 的場景
如果你是在 Kubernetes 集群內(nèi)部署 Prometheus,那大概率會(huì)用到 prometheus-operator,他對(duì) Prometheus 的配置做了 CRD 封裝,讓用戶更方便的擴(kuò)展 Prometheus 實(shí)例,同時(shí) prometheus-operator 還提供了豐富的 Grafana 模板,包括上面提到的 Master 組件監(jiān)控的 Grafana 視圖,Operator 啟動(dòng)之后就可以直接使用,免去了配置面板的煩惱。
Operator 的優(yōu)點(diǎn)很多,就不一一列舉了,只提一下 Operator 的局限:
- 因?yàn)槭?Operator,所以依賴 Kubernetes 集群,如果你需要二進(jìn)制部署你的 Prometheus,如集群外部署,就很難用上 prometheus-operator 了,如多集群場景。當(dāng)然你也可以在 Kubernetes 集群中部署 Operator 去監(jiān)控其他的 Kubernetes 集群,但這里面坑不少,需要修改一些配置。
- Operator 屏蔽了太多細(xì)節(jié),這個(gè)對(duì)用戶是好事,但對(duì)于理解 Prometheus 架構(gòu)就有些 gap 了,比如碰到一些用戶一鍵安裝了 Operator,但 Grafana 圖表異常后完全不知道如何排查,record rule 和服務(wù)發(fā)現(xiàn)還不了解的情況下就直接配置,建議在使用 Operator 之前,最好熟悉 Prometheus 的基礎(chǔ)用法。
- Operator 方便了 Prometheus 的擴(kuò)展和配置,對(duì)于 alertmanager 和 exporter 可以很方便的做到多實(shí)例高可用,但是沒有解決 Prometheus 的高可用問題,因?yàn)闊o法處理數(shù)據(jù)不一致,Operator 目前的定位也還不是這個(gè)方向,和 Thanos、Cortex 等方案的定位是不同的,下面會(huì)詳細(xì)解釋。
高可用方案
Prometheus 高可用有幾種方案:
- 基本 HA:即兩套 Prometheus 采集完全一樣的數(shù)據(jù),外邊掛負(fù)載均衡。
- HA + 遠(yuǎn)程存儲(chǔ):除了基礎(chǔ)的多副本 Prometheus,還通過 Remote Write 寫入到遠(yuǎn)程存儲(chǔ),解決存儲(chǔ)持久化問題。
- 聯(lián)邦集群:即 Federation,按照功能進(jìn)行分區(qū),不同的 Shard 采集不同的數(shù)據(jù),由 Global 節(jié)點(diǎn)來統(tǒng)一存放,解決監(jiān)控?cái)?shù)據(jù)規(guī)模的問題。
- 使用 Thanos 或者 Victoriametrics,來解決全局查詢、多副本數(shù)據(jù) Join 問題。
就算使用官方建議的多副本 + 聯(lián)邦,仍然會(huì)遇到一些問題:
- 官方建議數(shù)據(jù)做 Shard,然后通過Federation來實(shí)現(xiàn)高可用,
- 但是邊緣節(jié)點(diǎn)和Global節(jié)點(diǎn)依然是單點(diǎn),需要自行決定是否每一層都要使用雙節(jié)點(diǎn)重復(fù)采集進(jìn)行?;?。 也就是仍然會(huì)有單機(jī)瓶頸。
- 另外部分敏感報(bào)警盡量不要通過 Global 節(jié)點(diǎn)觸發(fā),畢竟從 Shard 節(jié)點(diǎn)到 Global 節(jié)點(diǎn)傳輸鏈路的穩(wěn)定性會(huì)影響數(shù)據(jù)到達(dá)的效率,進(jìn)而導(dǎo)致報(bào)警實(shí)效降低。
- 例如服務(wù) Updown 狀態(tài),API 請求異常這類報(bào)警我們都放在 Shard 節(jié)點(diǎn)進(jìn)行報(bào)警。
本質(zhì)原因是,Prometheus 的本地存儲(chǔ)沒有數(shù)據(jù)同步能力,要在保證可用性的前提下,再保持?jǐn)?shù)據(jù)一致性是比較困難的,基礎(chǔ)的 HA Proxy 滿足不了要求,比如:
- 集群的后端有 A 和 B 兩個(gè)實(shí)例,A 和 B 之間沒有數(shù)據(jù)同步。A 宕機(jī)一段時(shí)間,丟失了一部分?jǐn)?shù)據(jù),如果負(fù)載均衡正常輪詢,請求打到 A 上時(shí),數(shù)據(jù)就會(huì)異常。
- 如果 A 和 B 的啟動(dòng)時(shí)間不同,時(shí)鐘不同,那么采集同樣的數(shù)據(jù)時(shí)間戳也不同,就不是多副本同樣數(shù)據(jù)的概念了。
- 就算用了遠(yuǎn)程存儲(chǔ),A 和 B 不能推送到同一個(gè) TSDB,如果每人推送自己的 TSDB,數(shù)據(jù)查詢走哪邊就是問題了。
因此解決方案是在存儲(chǔ)、查詢兩個(gè)角度上保證數(shù)據(jù)的一致:
- 存儲(chǔ)角度:如果使用 Remote Write 遠(yuǎn)程存儲(chǔ), A 和 B 后面可以都加一個(gè) Adapter,Adapter 做選主邏輯,只有一份數(shù)據(jù)能推送到 TSDB,這樣可以保證一個(gè)異常,另一個(gè)也能推送成功,數(shù)據(jù)不丟,同時(shí)遠(yuǎn)程存儲(chǔ)只有一份,是共享數(shù)據(jù)。方案可以參考這篇文章[15]。
- 查詢角度:上邊的方案實(shí)現(xiàn)很復(fù)雜且有一定風(fēng)險(xiǎn),因此現(xiàn)在的大多數(shù)方案在查詢層面做文章,比如 Thanos 或者 Victoriametrics,仍然是兩份數(shù)據(jù),但是查詢時(shí)做數(shù)據(jù)去重和 Join。只是 Thanos 是通過 Sidecar 把數(shù)據(jù)放在對(duì)象存儲(chǔ),Victoriametrics 是把數(shù)據(jù) Remote Write 到自己的 Server 實(shí)例,但查詢層 Thanos-Query 和 Victor 的 Promxy 的邏輯基本一致。
我們采用了 Thanos 來支持多地域監(jiān)控?cái)?shù)據(jù),具體方案可以看這篇文章[16]。
容器日志與事件
本文主要是 Prometheus 監(jiān)控內(nèi)容,這里只簡單介紹下 Kubernetes 中的日志、事件處理方案,以及和 Prometheus 的搭配。
日志處理:
- 日志采集與推送:一般是 Fluentd/Fluent-Bit/Filebeat 等采集推送到 ES、對(duì)象存儲(chǔ)、Kafka,日志就該交給專業(yè)的 EFK 來做,分為容器標(biāo)準(zhǔn)輸出、容器內(nèi)日志。
- 日志解析轉(zhuǎn) metric:可以提取一些日志轉(zhuǎn)為 Prometheus 格式的指標(biāo),如解析特定字符串出現(xiàn)次數(shù),解析 Nginx 日志得到 QPS 、請求延遲等。常用方案是 mtail 或者 grok。
日志采集方案:
- Sidecar 方式:和業(yè)務(wù)容器共享日志目錄,由 sidecar 完成日志推送,一般用于多租戶場景。
- DaemonSet 方式:機(jī)器上運(yùn)行采集進(jìn)程,統(tǒng)一推送出去。
需要注意的點(diǎn):對(duì)于容器標(biāo)準(zhǔn)輸出,默認(rèn)日志路徑是 /var/lib/docker/containers/xxx, kubelet 會(huì)將改日志軟鏈到/var/log/pods,同時(shí)還有一份 /var/log/containers 是對(duì) /var/log/pods 的軟鏈。不過不同的 Kubernetes 版本,日志的目錄格式有所變化,采集時(shí)根據(jù)版本做區(qū)分:
- 1.15 及以下:/var/log/pods/{pod_uid}/
- 1.15 以上:var/log/pods/{pod_name+namespace+rs+uuid}/
事件:在這里特指 Kubernetes Events,Events 在排查集群問題時(shí)也很關(guān)鍵,不過默認(rèn)情況下只保留 1h,因此需要對(duì) Events 做持久化。一般 Events 處理方式有兩種:
- 使用 kube-eventer 之類的組件采集 Events 并推送到 ES
- 使用 event_exporter 之類的組件將 Events 轉(zhuǎn)化為 Prometheus Metric,同類型的還有谷歌云的 stackdriver 下的 event-exporter。