之前我們講過通過設定prometheus-operator的CRD ServiceMonitor來達到K8S叢集相關元件和微服務的監控的目的,可以在ServiceMonitor的組態檔中以手動方式通過match lable和想要監控的Service進行匹配(這裡相當於是手動進行服務註冊和服務發現的作用,也可以將這種模式稱為靜態服務發現),以此來完成對想要監控的服務和元件進行監控,但這種方式進行監控設定,只能手工一個一個的增加,如果在k8s叢集規模較大的情況下,或者是叢集後面又增加了節點或者元件資訊,這種方式就會很麻煩也不現實,於是引出了今天的主題-Prometheus動態服務發現機制,下面來讓我們瞭解一下Prometheus是如何動態實現服務發現的。
從上面的介紹大家已經知道,prometheus獲取資料來源target的方式主要有兩種模式,一種是靜態設定,一種是動態服務發現設定,promethues的靜態服務發現static_configs或者是ServiceMonitor 通過標籤匹配Service:每當有一個新的目標範例需要監控,都需要手動修改組態檔設定目標target或者修改ServiceMonitor CRD組態檔.
那麼面對現如今動不動就成百上千臺節點的叢集來說,靜態服務發現這種純手動設定很顯然是不切實際的,然而現在的叢集往往都有一個很重要的功能叫做服務發現,例如在現在常用的微服務SpringCloud架構的中,Eureka元件作為服務註冊和發現的中心,在Kubernetes這類容器管理平臺中,Kubernetes也具有服務發現的功能,它們都掌握並管理著所有的容器或者是服務的相關資訊,於是Prometheus通過這個中間的代理人(服務發現和註冊中心)來獲取叢集當中監控目標的資訊,從而巧妙地實現了prometheus的動態服務發現。
prometheus目前支援的動態服務發現有很多種,常用的主要分為以下幾種:
1. promethues基於k8s的服務發現kubernetes_sd_configs
2. promethues基於consul的服務發現consul_sd_config
3. promethues基於Eureka的服務發現eureka_sd_config
還有基於DNS等等的就不一一列舉。
下面主要講解promethues基於的k8s服務發現kubernetes_sd_configs
目前,在Kubernetes下,Prometheus 通過與 Kubernetes API 整合主要支援5種服務發現模式又叫角色role:Node、Service、Pod、Endpoints、Ingress。不同的服務發現模式適用於不同的場景,例如:node適用於與主機相關的監控資源,如節點中執行的Kubernetes 元件狀態、節點上執行的容器狀態等;service 和 ingress 適用於通過黑盒監控的場景,如對服務的可用性以及服務質量的監控;endpoints 和 pod 均可用於獲取 Pod 範例的監控資料,如監控使用者或者管理員部署的支援 Prometheus 的應用。
1. node
node角色可以發現叢集中每個node節點的地址埠,預設為Kubelet的HTTP埠。目標地址預設為Kubernetes節點物件的第一個現有地址,地址型別順序為NodeInternalIP、NodeExternalIP、NodeLegacyHostIP和NodeHostName。
作用:監控K8S的node節點的伺服器相關的指標資料。
2. service
service角色可以發現每個service的ip和port,將其作為target。這對於黑盒監控(blackbox)很有用。
3. pod
pod角色可以發現所有pod並將其中的pod ip作為target。如果有多個埠或者多個容器,將生成多個target(例如:80,443這兩個埠,pod ip為10.0.244.22,則將10.0.244.22:80,10.0.244.22:443分別作為抓取的target)。
如果容器沒有指定的埠,則會為每個容器建立一個無埠target,以便通過relabel手動新增埠。
4. endpoints
endpoints角色可以從ep(endpoints)列表中發現所有targets。
- 如果ep是屬於service的話,則會附加service角色的所有標籤
- 對於ep的後端節點是pod,則會附加pod角色的所有標籤(即上邊介紹的pod角色可用標籤)
比如我麼手動建立一個ep,這個ep關聯到一個pod,則prometheus的標籤中會包含這個pod角色的所有標籤
5. ingress
ingress角色發現ingress的每個路徑的target。這通常對黑盒監控很有用。該地址將設定為ingress中指定的host。
為解決服務發現的問題,kube-prometheus 為我們提供了一個額外的抓取設定來解決這個問題,我們可以通過新增額外的設定來進行服務發現進行自動監控。我們可以在 kube-prometheus 當中去自動發現並監控具有 prometheus.io/scrape=true 這個 annotations 的 Service。
其中通過 kubernetes_sd_configs 支援監控其各種資源。kubernetes SD 設定允許從 kubernetes REST API 接受蒐集指標,且總是和叢集保持同步狀態,任何一種 role 型別都能夠設定來發現我們想要的物件。
規則設定使用 yaml 格式,下面是檔案中一級設定項。自動發現 k8s Metrics 介面是通過 scrape_configs 來實現的:
#全域性設定
global:
#規則設定主要是設定報警規則
rule_files:
#抓取設定,主要設定抓取使用者端相關
scrape_configs:
#報警設定
alerting:
#用於遠端儲存寫設定
remote_write:
#用於遠端讀設定
remote_read:
舉例說明:
# Kubernetes的API SERVER會暴露API服務,Promethues整合了對Kubernetes的自動發現,它有5種模式:Node、Service
# 、Pod、Endpoints、ingress,下面是Prometheus官方給出的對Kubernetes服務發現的範例。這裡你會看到大量的relabel_configs,
# 其實你就是把所有的relabel_configs去掉一樣可以對kubernetes做服務發現。relabel_configs僅僅是對採集過來的指標做二次處理,比如
# 要什麼不要什麼以及替換什麼等等。而以__meta_開頭的這些後設資料標籤都是範例中包含的,而relabel則是動態的修改、覆蓋、新增刪除這些標籤
# 或者這些標籤對應的值。而且以__開頭的標籤通常是系統內部使用的,因此這些標籤不會被寫入樣本資料中,如果我們要收集這些東西那麼則要進行
# relabel操作。當然reabel操作也不僅限於操作__開頭的標籤。
#
# action的行為:
# replace:預設行為,不設定action的話就採用這種行為,它會根據regex來去匹配source_labels標籤上的值,並將並將匹配到的值寫入target_label中
# labelmap:它會根據regex去匹配標籤名稱,並將匹配到的內容作為新標籤的名稱,其值作為新標籤的值
# keep:僅收集匹配到regex的源標籤,而會丟棄沒有匹配到的所有標籤,用於選擇
# drop:丟棄匹配到regex的源標籤,而會收集沒有匹配到的所有標籤,用於排除
# labeldrop:使用regex匹配標籤,符合regex規則的標籤將從target範例中移除,其實也就是不收集不儲存
# labelkeep:使用regex匹配標籤,僅收集符合regex規則的標籤,不符合的不收集
global:
# 間隔時間
scrape_interval: 30s
# 超時時間
scrape_timeout: 10s
# 另一個獨立的規則週期,對告警規則做定期計算
evaluation_interval: 30s
# 外部系統標籤
external_labels:
prometheus: monitoring/k8s
prometheus_replica: prometheus-k8s-1
# 抓取伺服器端點,整個這個任務都是用來發現node-exporter和kube-state-metrics-service的,這裡用的是endpoints角色,這是通過這兩者的service來發現
# 的後端endpoints。另外需要說明的是如果滿足採集條件,那麼在service、POD中定義的labels也會被採集進去
scrape_configs:
# 定義job名稱,是一個拉取單元
- job_name: "kubernetes-endpoints"
# 發現endpoints,它是從列出的伺服器端點發現目標,這個endpoints來自於Kubernetes中的service,每一個service都有對應的endpoints,這裡是一個列表
# 可以是一個IP:PORT也可以是多個,這些IP:PORT就是service通過標籤選擇器選擇的POD的IP和埠。所以endpoints角色就是用來發現server對應的pod的IP的
# kubernetes會有一個預設的service,通過找到這個service的endpoints就找到了api server的IP:PORT,那endpoints有很多,我怎麼知道哪個是api server呢
# 這個就靠source_labels指定的標籤名稱了。
kubernetes_sd_configs:
# 角色為 endpoints
- role: endpoints
relabel_configs:
# 重新打標僅抓取到的具有 "prometheus.io/scrape: true" 的annotation的端點,意思是說如果某個service具有prometheus.io/scrape = true annotation宣告則抓取
# annotation本身也是鍵值結構,所以這裡的源標籤設定為鍵,而regex設定值,當值匹配到regex設定的內容時則執行keep動作也就是保留,其餘則丟棄.
# node-exporter這個POD的service裡面就有一個叫做prometheus.io/scrape = true的annotations所以就找到了node-exporter這個POD
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
# 動作 刪除 regex 與串聯不匹配的目標 source_labels
action: keep
# 通過正式表示式匹配 true
regex: true
# 重新設定scheme
# 匹配源標籤__meta_kubernetes_service_annotation_prometheus_io_scheme也就是prometheus.io/scheme annotation
# 如果源標籤的值匹配到regex則把值替換為__scheme__對應的值
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (https?)
# 匹配來自 pod annotationname prometheus.io/path 欄位
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
# 獲取POD的 annotation 中定義的"prometheus.io/path: XXX"定義的值,這個值就是你的程式暴露符合prometheus規範的metrics的地址
# 如果你的metrics的地址不是 /metrics 的話,通過這個標籤說,那麼這裡就會把這個值賦值給 __metrics_path__這個變數,因為prometheus
# 是通過這個變數獲取路徑然後進行拼接出來一個完整的URL,並通過這個URL來獲取metrics值的,因為prometheus預設使用的就是 http(s)://X.X.X.X/metrics
# 這樣一個路徑來獲取的。
action: replace
# 匹配目標指標路徑
target_label: __metrics_path__
# 匹配全路徑
regex: (.+)
# 匹配出 Pod ip地址和 Port
- source_labels:
[__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: ([^:]+)(?::d+)?;(d+)
replacement: $1:$2
# 下面主要是為了給樣本新增額外資訊
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
# 元標籤 服務物件的名稱空間
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
# service 物件的名稱
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_name
# pod物件的名稱
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
新增 prometheus 在 Kubernetes 下的自動服務發現 prometheus-additional.yaml
- job_name: 'dev-kubernetes-endpoints'
scrape_interval: 10s
scrape_timeout: 10s
metrics_path: (.*)/actuator/prometheus
scheme: http
relabel_configs:
- action: keep
regex: true
source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_scrape
- action: replace
regex: (.+)
source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_path
target_label: __metrics_path__
- action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
source_labels:
- __address__
- __meta_kubernetes_pod_annotation_prometheus_io_port
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- action: replace
source_labels:
- __meta_kubernetes_namespace
target_label: kubernetes_namespace
- action: replace
source_labels:
- __meta_kubernetes_pod_name
target_label: kubernetes_pod_name
kubernetes_sd_configs:
- role: pod
kubeconfig_file: ""
follow_redirects: true
namespaces:
names: []
將上面檔案直接儲存為 prometheus-additional.yaml,然後通過這個檔案建立一個對應的 Secret 物件:
$ kubectl create secret generic additional-configs --from-file=prometheus-additional.yaml -n monitoring
secret "additional-configs" created
然後我們需要在宣告 prometheus 的資源物件檔案中通過 additionalScrapeConfigs 屬性新增上這個額外的設定:
「prometheus-prometheus.yaml」:
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
labels:
app.kubernetes.io/component: prometheus
app.kubernetes.io/name: prometheus
app.kubernetes.io/part-of: kube-prometheus
app.kubernetes.io/version: 2.29.1
prometheus: k8s
name: k8s
namespace: monitoring
spec:
retention: 7d
alerting:
alertmanagers:
- apiVersion: v2
name: alertmanager-main
namespace: monitoring
port: web
enableFeatures: []
externalLabels: {}
image: quay.io/prometheus/prometheus:v2.29.1
nodeSelector:
kubernetes.io/os: linux
podMetadata:
labels:
app.kubernetes.io/component: prometheus
app.kubernetes.io/name: prometheus
app.kubernetes.io/part-of: kube-prometheus
app.kubernetes.io/version: 2.29.1
podMonitorNamespaceSelector: {}
podMonitorSelector: {}
probeNamespaceSelector: {}
probeSelector: {}
replicas: 2
resources:
requests:
memory: 400Mi
ruleNamespaceSelector: {}
ruleSelector: {}
securityContext:
fsGroup: 2000
runAsNonRoot: true
runAsUser: 1000
serviceAccountName: prometheus-k8s
serviceMonitorNamespaceSelector: {}
serviceMonitorSelector: {}
version: 2.29.1
additionalScrapeConfigs: #以下為新增的設定項
name: prometheus-additional-configs
key: prometheus-additional-config.yaml
新增完成後,直接更新 prometheus 這個 CRD 資源物件即可:
kubectl apply -f prometheus-prometheus.yaml
過一段時間,重新整理 promethues 上的 config,將會檢視設定已經生效。
自動發現規則設定好後如何讓prometheus抓取pod內的metrics指標呢,抓取的路徑埠等資訊如何指定呢,這就要在應用deployments中的spec.template.metadata.annotations中指定了。設定如下:
annotations:
prometheus.io/path: /actuator/prometheus
prometheus.io/port: "7070"
prometheus.io/scheme: http
prometheus.io/scrape: "true"
定義好後prometheus即可抓取pod內的metrics指標資料了,在prometheus的targets頁面即可看到job名稱為 dev-kubernetes-endpoints 的target。
我們切換到 targets 頁面下面卻並沒有發現對應的監控任務,檢視 Prometheus 的 Pod 紀錄檔,發現很多錯誤紀錄檔出現,都是 xxx is forbidden,這說明是 RBAC 許可權的問題。
通過 prometheus 資源物件的設定可以知道 Prometheus 繫結了一個名為 prometheus-k8s 的 ServiceAccount 物件,而這個物件繫結的是一個名為 prometheus-k8s 的 ClusterRole:
建立 prometheus-clusterRole.yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus-k8s
rules:
- apiGroups:
- ""
resources:
- nodes/metrics
verbs:
- get
- nonResourceURLs:
- /metrics
verbs:
- get
上面的許可權規則中我們可以看到明顯沒有對 Service 或者 Pod 的 list 許可權,所以報錯了,要解決這個問題,我們只需要新增上需要的許可權即可:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/component: prometheus
app.kubernetes.io/name: prometheus
app.kubernetes.io/part-of: kube-prometheus
app.kubernetes.io/version: 2.29.1
name: prometheus-k8s
rules:
- apiGroups:
- ""
resources:
- nodes
- services
- endpoints
- pods
- nodes/proxy
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes/metrics
verbs:
- get
- nonResourceURLs:
- /metrics
- /actuator/prometheus
verbs:
- get
更新上面的 ClusterRole 這個資源物件,然後重建下 Prometheus 的所有 Pod,正常就可以看到 targets 頁面下面有 dev-kubernetes-endpoints 這個監控任務了。
這裡抓取目標是因為 Service 中都有 prometheus.io/scrape=true 這個 annotation。至此,一個自動發現endpoint的設定就完成了,其他資源(service、pod、ingress、node同樣也可以通過自動發現的方式實現。