Prometheus作為容器監控領域的事實標准,隨着以Kubernetes為核心的雲原生熱潮的興起,已經得到了廣泛的應用部署。靈活的服務發現機制是Prometheus和Kubernetes兩者得以連接的基礎,本文將對這部分內容進行介紹,從而讓讀者了解Prometheus如何對Kubernetes集群本身以及對運行其上的各種應用進行有效地監控。
1. Prometheus概述
在正式進入主題之前,對Prometheus進行全面的了解是必要的。如下圖所示,Prometheus Server是Prometheus生態中的核心組件。它會通過靜態配置或者動態的服務發現,找到一系列的抓取對象,我們稱之為target,Prometheus Server會定期從這些target中拉取時序數據,后文將對這部分內容進行詳細敘述。對於不斷抓取到的時序數據,Prometheus Server會進行聚合並默認存儲到本地的時序數據庫TSDB中。通過Prometehus Server的HTTP接口以及內置的查詢語言PromQL可以對TSDB中保存的時序數據進行查詢並通過Grafana等工具進行展示。同時,我們可以定義一系列的告警規則,Prometheus Server會定期查詢TSDB以判斷是否觸發告警,若是則將報警消息推送至Alert Manager。Alert Manager會對告警消息進行聚合,去重等一系列復雜操作,在必要的時候將告警消息通過Email等方式通知用戶。
2. 靜態配置
當需要抓取的對象固定且數目較少時,將其直接寫入Prometehus的配置文件是最好的選擇。事實上,Prometheus Server作為一個應用程序,它同樣對自己的運行數據進行了收集並以標准的方式對外暴露。因此,我們完全可以用Prometheus監控Prometheus。此時,Prometheus的配置文件如下,已知Prometheus Server運行在本地的9090端口:
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). # 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' # scheme: http # metrics_path: /metric static_configs: - targets: ['127.0.0.1:9090']
上述配置文件的scrape_configs
中包含一個名為prometheus
的job
。在Prometheus中一個job
代表多個配置類似的target的集合,例如它們都可以通過http
或者https
協議進行訪問,訪問路徑都為/metrics
等等。在上述prometheus
這個job
中,僅僅包含一個地址為127.0.0.1:9090
的target。簡單地說,Prometheus Server會每隔scrape_interval
就對target的URLhttp://127.0.0.1:9090
進行訪問獲取時序數據。
3. Prometheus抓取機制
在Prometheus內部,target的發現和抓取分別是由Scrape Discovery Manager和Scrape Manager獨立實現的。兩者通過如下所示的channel進行連接:
chan map[string][]*targetgroup.Group
可以看到,上述管道傳輸的是map,其中的每個key和value對應的就是一個job名以及它所包含的一系列target group。事實上,在Prometheus內部一個target是用一系列的labels來描述的,每個label就是一個鍵值對,而一個target group就是共享某些labels的target集合。在下文中我們會了解到,一個Pod往往就和一個target group相對應。
每當從上述channel獲取最新的target列表之后,Scrape Manager會進行重載。為每個job創建一個Scrape Pool,再為job中的每個target創建一個Scrape Loop。Scrape Pool會根據job的配置,創建一個http client供其中的各個Scrape Loop使用,而Scrape Loop則利用該http client具體完成對目標target的時序數據抓取工作。整體結構如下所示:
- Scrape Manager
- Scrape Pool 1
- Scrape Loop for target 1
- Scrape Loop for target 2
- [...]
- Scrape Loop for target n
- Scrape Pool 2
- [...]
- [...]
- Scrape Pool n
- [...]
- Scrape Pool 1
在Scrape Discovery Manager端同樣會根據job進行划分。每個job中可以包含多種的服務發現,基於File,基於Kubernetes,基於Consul等等。對於各種服務發現方式,我們統一用provider對象進行封裝,類似地,provider通過channel:
chan<- []*targetgroup.Group
定期向Scrape Discovery Manager推送最新的一系列target group。Scrape Discovery Manager則會將各個provider推送的target group進行聚合並向Scrape Manager推送。
事實上,對於靜態配置的target,我們可以將它看作一種特殊的服務發現機制並且同樣用provider進行封裝,不同的是它只會用channel推送一次用戶在配置文件中寫好的target group,之后就會將channel關閉。
4. Kubernetes下的服務發現
在Kubernetes環境下,要做到對系統本身,特別是運行其上的各種應用的完整監控,靜態配置的方式顯然是無法滿足需求的。因此,基於Kubernetes本身的服務發現能力,Prometheus實現了對於Kubernetes集群,包括系統組件,Pod,Service,Ingress等各個維度的動態監控。本文將以針對Pod的服務發現作為例子,對於其他維度,其實現機制是類似的。
若要實現對於集群中的Pod的動態監控,則Prometheus的配置文件中需要加入以下job:
- job_name: "kubernetes-pods" kubernetes_sd_configs: - role: pod # api_server: <host> # basic_auth: XXX # tls_config: XXX relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: true - source_labels: [__meta_kubernetes_namespace] action: replace target_label: kubernetes_namespace - source_labels: [__meta_kubernetes_pod_name] action: replace target_label: kubernetes_pod_name
其中的kubernetes_sd_configs
表示基於Kubernetes進行服務發現,而其中的role
字段為pod
,表示針對Kubernetes中的pod資源對象,進行具體的服務發現操作。role
字段其余的合法值包括node
, service
, endpoints
, ingress
。
之后Prometheus會創建Kubernetes Client對role
中指定的資源對象進行監聽。一般Prometheus部署在Kubernetes集群中的話,Prometheus可以直接利用指定的Service Account對Kubernetes API進行訪問。若Prometheus在Kubernetes集群之外,則kubernetes_sd_configs
還需指定監控集群的API Server的URL以及相關的認證信息,從而能夠創建對應集群的Client。
和Kubernetes內置的各種Controller類似,當目標資源對象為Pod時,Prometheus會對Kubernetes中的Pod進行List & Watch。每當集群中新增一個Pod,Prometheus就會對其進行處理,根據其配置創建一個target group。之后,Prometheus會遍歷Pod中的每個Container:若Container沒有暴露端口,則將一個Container作為一個target並將該target的__address__
直接設置為Pod的IP地址;若Container暴露了一個或多個端口,則將每個端口設置為一個target且target的__address
設置為Pod IP加對應端口的組合。如上文所述,一個target group中的targets會共享某些labels,當target group對應的是一個pod時,Prometheus會將Pod的基本信息作為共享labels。例如:__meta_kubernetes_namespace
對應Pod所在的namespace,__meta_kubernetes_pod_name
對應pod name,以及pod的ip,labels和annotations都會作為共享labels寫入。
最終,這些自發現的target groups都將通過管道傳遞給Scrape Manager,由它來創建對於各個target的抓取實例並實現數據的抓取。
5. Targets的過濾機制
從上文可知,若配置了對於Kubernetes中Pod資源對象的服務發現,Prometheus會默認將每個Pod IP + Pod Port的組合作為一個taget。顯然,並不是每個Pod都會暴露符合Prometheus標准的監控數據,即使暴露了,也不太可能在它的每個容器的每個端口進行暴露。因此,通過上述服務發現機制獲取到的大部分targets都將是無效的。已知targets本質上是一系列的labels,因此我們可以根據labels是否符合某些規則實現對targets的過濾,在Prometheus中,這一機制叫做relabel。
Prometheus配置文件中的relabel_configs
就包含了對於relabel規則的描述。我們可以同時定義多條relabels規則,它們會在Scrape Manager創建target實例並抓取之前,依次對targets實現過濾和修改。例如,上一節中的配置文件包含了三條relabel規則。第一條規則表示,若targets的labels中存在__meta_kubernetes_pod_annotation_prometheus_io_scrape
且該label的值為true
,則對應的target保留。這條規則就要求我們在定義Pod的配置時,若期望該Pod的監控數據被Prometheus抓取,則需要為它添加prometheus.io/scrape:true
這樣一個annotation,其余Pod生成的target都將被丟棄。
另外,我們還可以利用relabel實現對targets中labels的修改。一般來說,以__
作為前綴的labels都只在內部使用,在relabels之后會統一刪除。若想要保留其中的某些labels,我們可以如第二,三條規則所示,將名為__meta_kubernetes_namespace
和__meta_kubernetes_pod_name
的labels重命名為kubernetes_namespace
和kubernetes_pod_name
。這樣一來,這兩個label就能得到保留並出現在target最終的labels中,而對應的target抓取的所有指標都將額外添加kubernetes_namespace
和kubernetes_pod_name
這兩個labels。
這里我們只是對Prometheus的relabel機制進行了簡單的介紹,事實上可以利用多條relabel規則的組合實現對於targets的復雜的過濾和修改,在此不再贅述。另外,Scrape Manager會在對target進行relabel之前,根據配置文件額外添加job
,__metrics_path__
和__scheme__
這三個label,而在relabel之后,若target中沒有instance
這個label,則會向其添加instance
這個label,值為__address__
這個label的值。
6. 總結
Prometheus基於Kubernetes等目標監控系統自身的服務發現能力提供了抓取對象自發現機制,由此帶來的巨大靈活性是它能夠成為雲原生時代監控領域事實標准的重要原因之一。通過上文的敘述,我們可以知道,一個抓取對象,即target,在Prometheus是用一系列labels來描述的,全局的配置文件描述了某些targets的公共配置,而Kubernetes等系統的服務發現能力則為Prometheus提供了targets的大部分配置信息。最終,我們可以通過relabel機制對targets對象進行修改過濾,從而實現對於有效target的抓取。而對於target的抓取,本質上是通過target中的__scheme__
,__address__
和__metrcis_path__
這幾個label,拼湊出一個URL,比如http://10.32.0.2/metrics
,對該URL發起一個GET請求,獲取監控數據。