歡迎光臨
每天分享高質量文章

聊聊 Prometheus Operator

前言

 

我對 Prometheus 是又愛又恨。
  • 一方面吧,它生態特別好:作為 Kubernetes 監控的事實標準,(幾乎)所有 Kubernetes 相關元件都暴露了 Prometheus 的指標介面,甚至在 Kubernetes 生態之外,絕大部分傳統中介軟體(比如 MySQL、Kafka、Redis、ES)也有社群提供的 Prometheus Exporter。我們已經可以去掉 Kubernetes 這個定語,直接說 Prometheus 是開源監控方案的”頭號種子選手”了;

  • 另一方面吧,都 2019 年了,一個基礎設施領域的“頭號種子”選手居然還不支援分散式、不支援資料匯入/匯出、甚至不支援透過 API 修改監控標的和報警規則,這是不是也挺匪夷所思的?

不過 Prometheus 的維護者們也有充足的理由:Prometheus does one thing, and it does it well1。那其實也無可厚非,Prometheus 最核心的“指標監控”確實做得出色,只是當我們要考慮 Scale、考慮 Long-Term Storage、考慮平臺化(sth. as a Service)的時候,自己就得做一些擴充套件與整合了。“搞搞 Prometheus”這個主題可能會針對這些方面做一些討論,拋磚引玉(不過要是棄坑的話這就是第一篇也是最後一篇了ε=ε=ε=ε=┌(; ̄▽ ̄)┘)。
Prometheus Operator

 

這篇文章的主角是 Prometheus Operator,由於 Prometheus 本身沒有提供管理配置的 API 介面(尤其是管理監控標的和管理警報規則),也沒有提供好用的多實體管理手段,因此這一塊往往要自己寫一些程式碼或指令碼。但假如你還沒有寫這些程式碼,那就可以先看一下 Prometheus Operator,它很好地解決了 Prometheus 不好管理的問題。
什麼是 Operator?Operator = Controller + CRD。假如你不瞭解什麼是 Controller 和 CRD,可以看一個 Kubernetes 本身的例子:我們提交一個 Deployment 物件來宣告期望狀態,比如 3 個副本;而 Kubernetes 的 Controller 會不斷地幹活(跑控制迴圈)來達成期望狀態,比如看到只有 2 個副本就建立一個,看到有 4 個副本了就刪除一個。在這裡,Deployment 是 Kubernetes 本身的 API 物件。那假如我們想自己設計一些 API 物件來完成需求呢?Kubernetes 本身提供了 CRD(Custom Resource Definition),允許我們定義新的 API 物件。但在定義完之後,Kubernetes 本身當然不可能知道這些 API 物件的期望狀態該如何到達。這時,我們就要寫對應的 Controller 去實現這個邏輯。而這種自定義 API 物件 + 自己寫 Controller 去解決問題的樣式,就是 Operator Pattern。
概覽

 

假如你有一個測試用的 Kubernetes 叢集,可以直接按照這裡把 Prometheus Operator 以及基於 Operator 的一大坨物件全都部署上去,部署完之後就可以用 kubectl get prometheus,kubectl get servicemonitor 來摸索新增的 API 物件了(不部署也沒關係,咱們紙上談兵)。新的物件有四種:
  • Alertmanager:定義一個 Alertmanager 叢集;

  • ServiceMonitor:定義一組 Pod 的指標應該如何採集;

  • PrometheusRule:定義一組 Prometheus 規則;

  • Prometheus:定義一個 Prometheus “叢集”,同時定義這個叢集要使用哪些 ServiceMonitor 和 PrometheusRule。

看幾個簡化版的 yaml 定義就很清楚了:
  1. kind: Alertmanager
  2. metadata:
  3. name: main
  4. spec:
  5. baseImage: quay.io/prometheus/alertmanager
  6. replicas: 3
  7. version: v0.16.0

 

  • ➊ 一個 Alertmanager 物件

  • ➋ 定義該 Alertmanager 叢集的節點數為 3

  1. kind: Prometheus
  2. metadata: # 略
  3. spec:
  4. alerting:
  5. alertmanagers:
  6. - name: alertmanager-main
  7. namespace: monitoring
  8. port: web
  9. baseImage: quay.io/prometheus/prometheus
  10. replicas: 2
  11. ruleSelector:
  12. matchLabels:
  13. prometheus: k8s
  14. role: alert-rules
  15. serviceMonitorNamespaceSelector: {}
  16. serviceMonitorSelector:
  17. matchLabels:
  18. k8s-app: node-exporter
  19. query:
  20. maxConcurrency: 100
  21. version: v2.5.0

 

  • ➊ 定義該 Prometheus 對接的 Alertmanager 叢集名字為 main,在 monitoring 這個 namespace 中;

  • ➋ 定義該 Proemtheus “叢集”有兩個副本,說是叢集,其實 Prometheus 自身不帶叢集功能,這裡只是起兩個完全一樣的 Prometheus 來避免單點故障;

  • ➌ 定義這個 Prometheus 需要使用帶有 prometheus=k8s 且 role=alert-rules 標簽的 PrometheusRule;

  • ➍ 定義這些 Prometheus 在哪些 namespace 裡尋找 ServiceMonitor,不宣告則預設選擇 Prometheus 物件本身所處的 Namespace;

  • ➎ 定義這個 Prometheus 需要使用帶有 k8s-app=node-exporter 標簽的 ServiceMonitor,不宣告則會全部選中;

  • ➏ 定義 Prometheus 的最大併發查詢數為 100,幾乎所有配置都可以透過 Prometheus 物件進行宣告(包括很重要的 RemoteRead、RemoteWrite),這裡為了簡潔就不全部列出了。

 

  1. kind: ServiceMonitor
  2. metadata:
  3. labels:
  4. k8s-app: node-exporter
  5. name: node-exporter
  6. namespace: monitoring
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: node-exporter
  11. k8s-app: node-exporter
  12. endpoints:
  13. - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
  14. interval: 30s
  15. targetPort: 9100
  16. scheme: https
  17. jobLabel: k8s-app

 

  • ➊ 這個 ServiceMonitor 物件帶有 k8s-app=node-exporter 標簽,因此會被上面的 Prometheus 選中;

  • ➋ 定義需要監控的 Endpoints,帶有 app=node-exporter 且 k8s-app=node-exporter 標簽的 Endpoints 會被選中;

  • ➌ 定義這些 Endpoints 需要每 30 秒抓取一次;

  • ➍ 定義這些 Endpoints 的指標埠為 9100;

Endpoints 物件是 Kubernetes 對一組地址以及它們的可訪問埠的抽象,通常和 Service 一起出現。
  1. kind: PrometheusRule
  2. metadata:
  3. labels:
  4. prometheus: k8s
  5. role: alert-rules
  6. name: prometheus-k8s-rules
  7. spec:
  8. groups:
  9. - name: k8s.rules
  10. rules:
  11. - alert: KubeletDown
  12. annotations:
  13. message: Kubelet has disappeared from Prometheus target discovery.
  14. expr: |
  15. absent(up{job="kubelet"} == 1)
  16. for: 15m
  17. labels:
  18. severity: critical

 

  • ➊ 定義該 PrometheusRule 的 label,顯然它會被上面定義的 Prometheus 選中;

  • ➋ 定義了一組規則,其中只有一條報警規則,用來報警 kubelet 是不是掛了。

串在一起,它們的關係如下:
看完這四個真實的 yaml,你可能會覺得,這不就是把 Prometheus 的配置打散了,放到了 API 物件裡嗎?和我自己寫 StatefulSet + ConfigMap 有什麼區別呢?
確實,Prometheus 物件和 Alertmanager 物件就是對 StatefulSet 的封裝:實際上在 Operator 的邏輯中,中還是生成了一個 StatefuleSet 交給 Kubernetes 自身的 Controller 去處理了。對於 PrometheusRule 和 ServiceMonitor 物件,Operator 也只是把它們轉化成了 Prometheus 的配置檔案,並掛載到 Prometheus 實體當中而已。
那麼,Operator 的價值究竟在哪呢?

 

Prometheus Operator 的好處都有啥?

 

首先,這些 API 物件全都是用 CRD 定義好 Schema 的,api-server 會幫我們做校驗。
假如我們用 ConfigMap 來存配置,那就沒有任何的校驗。萬一寫錯了(比如 yaml 縮排錯誤):
  • 那麼 Prometheus 做配置熱更新的時候就會失敗,假如配置更新失敗沒有報警,那麼 Game Over;

  • 熱更新失敗有報警,但這時 Prometheus 突然重啟了,於是配置錯誤重啟失敗,Game Over。

而在 Prometheus Operator 中,所有在 Prometheus 物件、ServiceMonitor 物件、PrometheusRule 物件中的配置都是有 Schema 校驗的,校驗失敗 apply 直接出錯,這就大大降低了配置異常的風險。
其次,Prometheus Operator 藉助 Kubernetes 把 Prometheus 服務平臺化了,實現 Prometheus as a Service。
在有了 Prometheus 和 Alertmanager 這樣非常明確的 API 物件之後,使用者就能夠以 Kubernetes 平臺為底座,自助式地建立 Prometheus 服務或 Alertmanager 服務。這一點我們不妨退一步想,假如沒有 Prometheus Operator,我們要怎麼實現這個平臺化呢?那無非就是給使用者一個表單, 限定能填的欄位,比如儲存檔大小、CPU記憶體、Prometheus 版本,然後透過一段邏輯填充成一個 StatefuleSet 的 API 物件再建立到 Kubernetes 上。沒錯,這些邏輯 Prometheus Operator 都幫我們做掉了,而且是用非常 Kubernetes 友好的方式做掉了,我們何必再造輪子呢?
最後,也是最重要的,ServiceMonitor 和 PrometheusRule 這兩個物件解決了 Prometheus 配置難維護這個痛點問題。
要證明這點,我得先亮一段 Prometheus 配置:
  1. - job_name: 'kubernetes-service-endpoints'
  2. kubernetes_sd_configs:
  3. - role: endpoints
  4. relabel_configs:
  5. - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
  6. action: keep
  7. regex: true
  8. - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
  9. action: replace
  10. target_label: __scheme__
  11. regex: (https?)
  12. - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
  13. action: replace
  14. target_label: __metrics_path__
  15. regex: (.+)
  16. - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
  17. action: replace
  18. target_label: __address__
  19. regex: ([^:]+)(?::\d+)?;(\d+)
  20. replacement: $1:$2
  21. - action: labelmap
  22. regex: __meta_kubernetes_service_label_(.+)
  23. - source_labels: [__meta_kubernetes_namespace]
  24. action: replace
  25. target_label: kubernetes_namespace
  26. - source_labels: [__meta_kubernetes_service_name]
  27. action: replace
  28. target_label: kubernetes_name
  29. - source_labels: [__meta_kubernetes_pod_node_name]
  30. action: replace
  31. target_label: kubernetes_node
透過 Prometheus 的 relabel_config 檔案可以知道,上面這段“天書”指定了:
  • 這個 Prometheus 要針對所有 annotation 中帶有 prometheus.io/scrape=true 的 Endpoints 物件,按照 annotation 中的 prometheus.io/port,prometheus.io/scheme,prometheus.io/path 來抓取它們的指標。

Prometheus 平臺化之後,勢必會有不同業務線、不同領域的各種 Prometheus 監控實體,大家都只想抓自己感興趣的指標,於是就需要改動這個配置來做文章,但這個配置在實際維護中有不少問題:
  • 複雜:複雜是萬惡之源;

  • 沒有分離關註點:應用開發者(提供 Pod 的人)必須知道 Prometheus 維護者的配置是怎麼編寫的,才能正確提供 annotation;

  • 沒有 API:更新流程複雜,需要透過 CI 或 Kubernetes ConfigMap 等手段把配置檔案更新到 Pod 內再觸發 webhook 熱更新;

而 ServiceMonitor 物件就很巧妙,它解耦了“監控的需求”和“需求的實現方”。我們透過前面的分析可以知道,ServiceMonitor 裡只需要用 label-selector 這種簡單又通用的方式宣告一個 “監控需求”,也就是哪些 Endpoints 需要收集,怎麼收集就行了。而這個需求本身則會被 Prometheus 按照 label 來選中並且滿足。讓使用者只關心需求,這就是一個非常好的關註點分離。當然了,ServiceMonitor 最後還是會被 Operator 轉化成上面那樣複雜的 Scrape Config,但這個複雜度已經完全被 Operator 遮蔽掉了。
另外,ServiceMonitor 還是一個欄位明確的 API 物件,用 kubectl 就可以檢視或更新它,在上麵包一個 Web UI,讓使用者透過 UI 選擇監控物件也是非常簡單的事情。這麼一來,很多“內部監控系統”的造輪子工程又可以簡化不少。
PrometheusRule 物件也是同樣的道理。再多想一點,基於 PrometheusRule 物件的 Rest API,我們可以很容易地開發一個 Grafana 外掛來幫助應用開發者在 UI 上定義警報規則。這對於 DevOps 流程是非常重要的,我們可不想在一個團隊中永遠只能去找 SRE 新增警報規則。
還有一點,這些新的 API 物件天生就能夠復用 kubectl,RBAC,Validation,Admission Control,ListAndWatch API 這些 Kubernetes 開發生態裡的東西,相比於脫離 Kubernetes 寫一套 “Prometheus 管理平臺”,這正是基於 Operator 樣式基於 Kubernetes 進行擴充套件的優勢所在。
結語

 

其實大家可以看到,Prometheus Operator 乾的事情其實就是平常我們用 CI 指令碼、定時任務或者手工去乾的事情,邏輯上很直接。它的成功在於藉助 Operator 樣式(拆開說就是控制迴圈 + 宣告式 API 這兩個 Kubernetes 的典型設計樣式)封裝了大量的 Prometheus 運維經驗,提供了友好的 Prometheus 管理介面,而這對於平臺化是很重要的。另外,這個例子也可以說明,即使對 Prometheus 這樣運維不算很複雜的系統,Operator 也能起到很好的效果。
原文連結:https://aleiwu.com/post/prometheus-operator/

已同步到看一看
贊(0)

分享創造快樂