1. 程式人生 > 其它 >Prometheus實現k8s叢集的服務監控

Prometheus實現k8s叢集的服務監控

Prometheus實現k8s叢集的服務監控

Prometheus 是一個開源監控系統,它本身已經成為了雲原生中指標監控的事實標準 。

k8s叢集監控體系演變史

第一版本:Cadvisor+InfluxDB+Grafana

只能從主機維度進行採集,沒有Namespace、Pod等維度的匯聚功能

第二版本: Heapster+InfluxDB+Grafana

heapster負責呼叫各node中的cadvisor介面,對資料進行彙總,然後導到InfluxDB , 可以從cluster,node,pod的各個層面提供詳細的資源使用情況。

第三版本:Metrics-Server + Prometheus

k8s對監控介面進行了標準化,主要分了三類:

  • Resource Metrics

    對應的介面是 metrics.k8s.io,主要的實現就是 metrics-server,它提供的是資源的監控,比較常見的是節點級別、pod 級別、namespace 級別、class 級別。這類的監控指標都可以通過 metrics.k8s.io 這個介面獲取到

  • Custom Metrics

    對應的介面是 custom.metrics.k8s.io,主要的實現是 Prometheus, 它提供的是資源監控和自定義監控,資源監控和上面的資源監控其實是有覆蓋關係的。

    自定義監控指的是:比如應用上面想暴露一個類似像線上人數,或者說呼叫後面的這個資料庫的 MySQL 的慢查詢。這些其實都是可以在應用層做自己的定義的,然後並通過標準的 Prometheus 的 client,暴露出相應的 metrics,然後再被 Prometheus 進行採集

  • External Metrics

    對應的介面是 external.metrics.k8s.io。主要的實現廠商就是各個雲廠商的 provider,通過這個 provider 可以通過雲資源的監控指標

Prometheus架構
  • Prometheus Server ,監控、告警平臺核心,抓取目標端監控資料,生成聚合資料,儲存時間序列資料
  • exporter,由被監控的物件提供,提供API暴漏監控物件的指標,供prometheus 抓取
    • node-exporter
    • blackbox-exporter
    • redis-exporter
    • mysql-exporter
    • custom-exporter
    • ...
  • pushgateway,提供一個閘道器地址,外部資料可以推送到該閘道器,prometheus也會從該閘道器拉取資料
  • Alertmanager,接收Prometheus傳送的告警並對於告警進行一系列的處理後傳送給指定的目標
  • Grafana:配置資料來源,圖示方式展示資料
Prometheus安裝

基於go開發, https://github.com/prometheus/prometheus

若使用docker部署直接啟動映象即可:

$ docker run --name prometheus -d -p 127.0.0.1:9090:9090 prom/prometheus

我們想製作Prometheus的yaml檔案,可以先啟動容器進去看一下預設的啟動命令:

$ docker run -d --name tmp -p 127.0.0.1:9090:9090 prom/prometheus:v2.19.2
$ docker exec -ti tmp sh
#/ ps aux
#/ cat /etc/prometheus/prometheus.yml
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).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# 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'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']
    
    

本例中,使用k8s來部署,所需的資源清單如下:

# 建立新的名稱空間 monitor,儲存prometheus相關資源
$ cat prometheus-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: monitor

# 需要準備配置檔案,因此使用configmap的形式儲存
$ cat prometheus-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitor
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      evaluation_interval: 15s
    scrape_configs:
    - job_name: 'prometheus'
      static_configs:
      - targets: ['localhost:9090']


# prometheus的資原始檔
# 出現Prometheus資料儲存許可權問題,因為Prometheus內部使用nobody啟動程序,掛載資料目錄後許可權為root,因此使用initContainer進行目錄許可權修復:
$ cat prometheus-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: monitor
  labels:
    app: prometheus
spec:
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      serviceAccountName: prometheus
      nodeSelector:
        app: prometheus
      initContainers:
      - name: "change-permission-of-directory"
        image: busybox
        command: ["/bin/sh"]
        args: ["-c", "chown -R 65534:65534 /prometheus"]
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: "/etc/prometheus"
          name: config-volume
        - mountPath: "/prometheus"
          name: data
      containers:
      - image: prom/prometheus:v2.19.2
        name: prometheus
        args:
        - "--config.file=/etc/prometheus/prometheus.yml"
        - "--storage.tsdb.path=/prometheus"  # 指定tsdb資料路徑
        - "--web.enable-lifecycle"  # 支援熱更新,直接執行localhost:9090/-/reload立即生效
        - "--web.console.libraries=/usr/share/prometheus/console_libraries"
        - "--web.console.templates=/usr/share/prometheus/consoles"
        ports:
        - containerPort: 9090
          name: http
        volumeMounts:
        - mountPath: "/etc/prometheus"
          name: config-volume
        - mountPath: "/prometheus"
          name: data
        resources:
          requests:
            cpu: 100m
            memory: 512Mi
          limits:
            cpu: 100m
            memory: 512Mi
      volumes:
      - name: data
        hostPath:
          path: /data/prometheus/
      - configMap:
          name: prometheus-config
        name: config-volume
        
# rbac,prometheus會呼叫k8s api做服務發現進行抓取指標
$ cat prometheus-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: prometheus
  namespace: monitor
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  - services
  - endpoints
  - pods
  - nodes/proxy
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - "extensions"
  resources:
    - ingresses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - configmaps
  - nodes/metrics
  verbs:
  - get
- nonResourceURLs:
  - /metrics
  verbs:
  - get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: prometheus
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: prometheus
subjects:
- kind: ServiceAccount
  name: prometheus
  namespace: monitor


# 提供Service,為Ingress使用
$ cat prometheus-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: prometheus
  namespace: monitor
  labels:
    app: prometheus
spec:
  selector:
    app: prometheus
  type: ClusterIP
  ports:
    - name: web
      port: 9090
      targetPort: http

$ cat prometheus-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: prometheus
  namespace: monitor
spec:
  rules:
  - host: prometheus.luffy.com
    http:
      paths:
      - path: /
        backend:
          serviceName: prometheus
          servicePort: 9090

部署上述資源:

# 名稱空間
$ kubectl create prometheus-namespace.yaml
  
# 給node打上label
$ kubectl label node k8s-slave1 app=prometheus

#部署configmap
$ kubectl create -f prometheus-configmap.yaml

# rbac
$ kubectl create -f prometheus-rbac.yaml

# deployment
$ kubectl create -f prometheus-deployment.yaml

# service
$ kubectl create -f prometheus-svc.yaml

# ingress
$ kubectl create -f prometheus-ingress.yaml

# 訪問測試
$ kubectl -n monitor get ingress


理解時間序列資料庫(TSDB)

# http://localhost:9090/metrics
$ kubectl -n monitor get po -o wide
prometheus-dcb499cbf-fxttx   1/1     Running   0          13h   10.244.1.132   k8s-slave1 

$ curl http://10.244.1.132:9090/metrics
...
# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.
# TYPE promhttp_metric_handler_requests_total counter
promhttp_metric_handler_requests_total{code="200"} 149
promhttp_metric_handler_requests_total{code="500"} 0
promhttp_metric_handler_requests_total{code="503"} 0

tsdb(Time Series Database)

其中#號開頭的兩行分別為:

  • HELP開頭說明該行為指標的幫助資訊,通常解釋指標的含義
  • TYPE開頭是指明瞭指標的型別
    • counter 計數器
    • guage 測量器
    • histogram 柱狀圖
    • summary 取樣點分點陣圖統計

其中非#開頭的每一行表示當前採集到的一個監控樣本:

  • promhttp_metric_handler_requests_total表明了當前指標的名稱
  • 大括號中的標籤則反映了當前樣本的一些特徵和維度
  • 浮點數則是該監控樣本的具體值。

每次採集到的資料都會被Prometheus以time-series(時間序列)的方式儲存到記憶體中,定期重新整理到硬碟。如下所示,可以將time-series理解為一個以時間為X軸的數字矩陣:

  ^
  │   . . . . . . . . . . . . . . . . .   . .   node_cpu{cpu="cpu0",mode="idle"}
  │     . . . . . . . . . . . . . . . . . . .   node_cpu{cpu="cpu0",mode="system"}
  │     . . . . . . . . . .   . . . . . . . .   node_load1{}
  │     . . . . . . . . . . . . . . . .   . .  
  v
    <------------------ 時間 ---------------->

在time-series中的每一個點稱為一個樣本(sample),樣本由以下三部分組成:

  • 指標(metric):metric name和描述當前樣本特徵的labelsets;
  • 時間戳(timestamp):一個精確到毫秒的時間戳;
  • 樣本值(value): 一個float64的浮點型資料表示當前樣本的值。

在形式上,所有的指標(Metric)都通過如下格式標示:

<metric name>{<label name>=<label value>, ...}
  • 指標的名稱(metric name)可以反映被監控樣本的含義(比如,http_request_total - 表示當前系統接收到的HTTP請求總量)。
  • 標籤(label)反映了當前樣本的特徵維度,通過這些維度Prometheus可以對樣本資料進行過濾,聚合等。

Prometheus:定期去Tragets列表拉取監控資料,儲存到TSDB中,並且提供指標查詢、分析的語句和介面。

新增監控目標

無論是業務應用還是k8s系統元件,只要提供了metrics api,並且該api返回的資料格式滿足標準的Prometheus資料格式要求即可。

其實,很多元件已經為了適配Prometheus採集指標,添加了對應的/metrics api,比如

CoreDNS:

$ kubectl -n kube-system get po -owide|grep coredns
coredns-58cc8c89f4-nshx2             1/1     Running   6          22d   10.244.0.20  
coredns-58cc8c89f4-t9h2r             1/1     Running   7          22d   10.244.0.21

$ curl 10.244.0.20:9153/metrics

修改target配置:

$ cat prometheus-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitor
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      scrape_timeout: 15s
    scrape_configs:
    - job_name: 'prometheus'
      static_configs:
      - targets: ['localhost:9090']
    - job_name: 'coredns'
      static_configs:
      - targets: ['10.96.0.10:9153']
      
$ kubectl apply -f prometheus-configmap.yaml

# 重建pod生效
$ kubectl -n monitor delete po prometheus-dcb499cbf-fxttx
常用監控物件的指標採集

對於叢集的監控一般我們需要考慮以下幾個方面:

  • 內部系統元件的狀態:比如 kube-apiserver、kube-scheduler、kube-controller-manager、kubedns/coredns 等元件的詳細執行狀態
  • Kubernetes 節點的監控:比如節點的 cpu、load、disk、memory 等指標
  • 業務容器指標的監控(容器CPU、記憶體、磁碟等)
  • 編排級的 metrics:比如 Deployment 的狀態、資源請求、排程和 API 延遲等資料指標
監控kube-apiserver

apiserver自身也提供了/metrics 的api來提供監控資料,

$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   23d

$ curl -k  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6InhXcmtaSG5ZODF1TVJ6dUcycnRLT2c4U3ZncVdoVjlLaVRxNG1wZ0pqVmcifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi1xNXBueiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImViZDg2ODZjLWZkYzAtNDRlZC04NmZlLTY5ZmE0ZTE1YjBmMCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlcm5ldGVzLWRhc2hib2FyZDphZG1pbiJ9.iEIVMWg2mHPD88GQ2i4uc_60K4o17e39tN0VI_Q_s3TrRS8hmpi0pkEaN88igEKZm95Qf1qcN9J5W5eqOmcK2SN83Dd9dyGAGxuNAdEwi0i73weFHHsjDqokl9_4RGbHT5lRY46BbIGADIphcTeVbCggI6T_V9zBbtl8dcmsd-lD_6c6uC2INtPyIfz1FplynkjEVLapp_45aXZ9IMy76ljNSA8Uc061Uys6PD3IXsUD5JJfdm7lAt0F7rn9SdX1q10F2lIHYCMcCcfEpLr4Vkymxb4IU4RCR8BsMOPIO_yfRVeYZkG4gU2C47KwxpLsJRrTUcUXJktSEPdeYYXf9w" https://192.168.136.10:6443/metrics

可以通過手動配置如下job來試下對apiserver服務的監控,

$ cat prometheus-configmap.yaml
...
    - job_name: 'kubernetes-apiserver'
      static_configs:
      - targets: ['10.96.0.1']
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        insecure_skip_verify: true
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
監控叢集節點基礎指標

node_exporter https://github.com/prometheus/node_exporter

分析:

  • 每個節點都需要監控,因此可以使用DaemonSet型別來管理node_exporter
  • 新增節點的容忍配置
  • 掛載宿主機中的系統檔案資訊
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitor
  labels:
    app: node-exporter
spec:
  selector:
    matchLabels:
      app: node-exporter
  template:
    metadata:
      labels:
        app: node-exporter
    spec:
      hostPID: true
      hostIPC: true
      hostNetwork: true
      nodeSelector:
        kubernetes.io/os: linux
      containers:
      - name: node-exporter
        image: prom/node-exporter:v1.0.1
        args:
        - --web.listen-address=$(HOSTIP):9100
        - --path.procfs=/host/proc
        - --path.sysfs=/host/sys
        - --path.rootfs=/host/root
        - --collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker/.+)($|/)
        - --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$
        ports:
        - containerPort: 9100
        env:
        - name: HOSTIP
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        resources:
          requests:
            cpu: 150m
            memory: 180Mi
          limits:
            cpu: 150m
            memory: 180Mi
        securityContext:
          runAsNonRoot: true
          runAsUser: 65534
        volumeMounts:
        - name: proc
          mountPath: /host/proc
        - name: sys
          mountPath: /host/sys
        - name: root
          mountPath: /host/root
          mountPropagation: HostToContainer
          readOnly: true
      tolerations:
      - operator: "Exists"
      volumes:
      - name: proc
        hostPath:
          path: /proc
      - name: dev
        hostPath:
          path: /dev
      - name: sys
        hostPath:
          path: /sys
      - name: root
        hostPath:
          path: /

建立node-exporter服務

$ kubectl create -f node-exporter.yaml

$ kubectl -n monitor get po

問題來了,如何新增到Prometheus的target中?

  • 配置一個Service,後端掛載node-exporter的服務,把Service的地址配置到target中
    • 帶來新的問題,target中無法直觀的看到各節點node-exporter的狀態
  • 把每個node-exporter的服務都新增到target列表中
    • 帶來新的問題,叢集節點的增刪,都需要手動維護列表
    • target列表維護量隨著叢集規模增加
Prometheus的服務發現與Relabeling

之前已經給Prometheus配置了RBAC,有讀取node的許可權,因此Prometheus可以去呼叫Kubernetes API獲取node資訊,所以Prometheus通過與 Kubernetes API 整合,提供了內建的服務發現分別是:NodeServicePodEndpointsIngress

配置job即可:

    - job_name: 'kubernetes-sd-node-exporter'
      kubernetes_sd_configs:
        - role: node

重建檢視效果:

$ kubectl apply -f prometheus-configmap.yaml
$ kubectl -n monitor delete po prometheus-dcb499cbf-6cwlg


預設訪問的地址是http://node-ip/10250/metrics,10250是kubelet API的服務埠,說明Prometheus的node型別的服務發現模式,預設是和kubelet的10250繫結的,而我們是期望使用node-exporter作為採集的指標來源,因此需要把訪問的endpoint替換成http://node-ip:9100/metrics。


在真正抓取資料前,Prometheus提供了relabeling的能力。怎麼理解?

檢視Target的Label列,可以發現,每個target對應會有很多Before Relabeling的標籤,這些__開頭的label是系統內部使用,不會儲存到樣本的資料裡,但是,我們在檢視資料的時候,可以發現,每個資料都有兩個預設的label,即:

prometheus_notifications_dropped_total{instance="localhost:9090",job="prometheus"}	

instance的值其實則取自於__address__

這種發生在採集樣本資料之前,對Target例項的標籤進行重寫的機制在Prometheus被稱為Relabeling。

因此,利用relabeling的能力,只需要將__address__替換成node_exporter的服務地址即可。

    - job_name: 'kubernetes-sd-node-exporter'
      kubernetes_sd_configs:
        - role: node
      relabel_configs:
      - source_labels: [__address__]
        regex: '(.*):10250'
        replacement: '${1}:9100'
        target_label: __address__
        action: replace

再次更新Prometheus服務後,檢視targets列表及node-exporter提供的指標,node_load1

cadvisor監控指標的採集

cAdvisor 的指標訪問路徑為 https://10.96.0.1/api/v1/nodes/<node_name>/proxy/metrics

https://10.96.0.1/api/v1/nodes/k8s-master/proxy/metrics
https://10.96.0.1/api/v1/nodes/k8s-slave1/proxy/metrics
https://10.96.0.1/api/v1/nodes/k8s-slave2/proxy/metrics

分析:

  • 每個節點都需要做替換,可以利用Prometheus服務發現中 node這種role
    - job_name: 'kubernetes-sd-cadvisor'
      kubernetes_sd_configs:
        - role: node

預設新增的target列表為:__address__ __metrics_path__

http://192.168.136.10:10250/metrics
http://192.168.136.11:10250/metrics
http://192.168.136.12:10250/metrics
  • 抓取的地址是相同的,可以用10.96.0.1做固定值進行替換__address__

        - job_name: 'kubernetes-sd-cadvisor'
          kubernetes_sd_configs:
            - role: node
          relabel_configs:
          - target_label: __address__
            replacement: 10.96.0.1
            action: replace
    

    目前為止,替換後的樣子:

    http://10.96.0.1/metrics
    http://10.96.0.1/metrics
    http://10.96.0.1/metrics
    
  • 需要把找到node-name,來做動態替換__metrics_path__

        - job_name: 'kubernetes-sd-cadvisor'
          kubernetes_sd_configs:
            - role: node
          relabel_configs:
          - target_label: __address__
            replacement: 10.96.0.1
            action: replace
          - source_labels: [__meta_kubernetes_node_name]
            regex: (.+)
            target_label: __metrics_path__
            replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
    

    目前為止,替換後的樣子:

    http://10.96.0.1/api/v1/nodes/k8s-master/proxy/metrics
    http://10.96.0.1/api/v1/nodes/k8s-slave1/proxy/metrics
    http://10.96.0.1/api/v1/nodes/k8s-slave2/proxy/metrics
    
  • 加上api-server的認證資訊

        - job_name: 'kubernetes-sd-cadvisor'
          kubernetes_sd_configs:
            - role: node
          scheme: https
          tls_config:
            ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
            insecure_skip_verify: true
          bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
          relabel_configs:
          - target_label: __address__
            replacement: 10.96.0.1
          - source_labels: [__meta_kubernetes_node_name]
            regex: (.+)
            target_label: __metrics_path__
            replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
    

重新應用配置,然後重建Prometheus的pod。檢視targets列表,檢視cadvisor指標,比如container_cpu_system_seconds_total

綜上,利用node型別,可以實現對daemonset型別服務的目標自動發現以及監控資料抓取。

叢集Service服務的監控指標採集

比如叢集中存在100個業務應用,每個業務應用都需要被Prometheus監控。

每個服務是不是都需要手動新增配置?有沒有更好的方式?

    - job_name: 'kubernetes-sd-endpoints'
      kubernetes_sd_configs:
        - role: endpoints

新增到Prometheus配置中進行測試:

$ kubectl apply -f prometheus-configmap.yaml
$ kubectl -n monitor delete po prometheus-dcb499cbf-4h9qj

此使的Target列表中,kubernetes-sd-endpoints下出現了N多條資料,

可以發現,實際上endpoint這個型別,目標是去抓取整個叢集中所有的名稱空間的Endpoint列表,然後使用預設的/metrics進行資料抓取,我們可以通過檢視叢集中的所有ep列表來做對比:

$ kubectl get endpoints --all-namespaces

但是實際上並不是每個服務都已經實現了/metrics監控的,也不是每個實現了/metrics介面的服務都需要註冊到Prometheus中,因此,我們需要一種方式對需要採集的服務實現自主可控。這就需要利用relabeling中的keep功能。

我們知道,relabel的作用物件是target的Before Relabling標籤,比如說,假如通過如下定義:

- job_name: 'kubernetes-sd-endpoints'
  kubernetes_sd_configs:
  - role: endpoints
  relabel_configs:
  - source_labels: [__keep_this_service__]
    action: keep
    regex: “true”

那麼就可以實現target的Before Relabling中若存在__keep_this_service__,且值為true的話,則會加入到kubernetes-endpoints這個target中,否則就會被刪除。

因此可以為我們期望被採集的服務,加上對應的Prometheus的label即可。

問題來了,怎麼加?

檢視coredns的metrics型別Before Relabling中的值,可以發現,存在如下型別的Prometheus的標籤:

__meta_kubernetes_service_annotation_prometheus_io_scrape="true"
__meta_kubernetes_service_annotation_prometheus_io_port="9153"

這些內容是如何生成的呢,檢視coredns對應的服務屬性:

$ kubectl -n kube-system get service kube-dns -oyaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/port: "9153"
    prometheus.io/scrape: "true"
  creationTimestamp: "2020-06-28T17:05:35Z"
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: KubeDNS
  name: kube-dns
  namespace: kube-system
  ...

發現存在annotations宣告,因此,可以聯想到二者存在對應關係,Service的定義中的annotations裡的特殊字元會被轉換成Prometheus中的label中的下劃線。

我們即可以使用如下配置,來定義服務是否要被抓取監控資料。

- job_name: 'kubernetes-sd-endpoints'
  kubernetes_sd_configs:
  - role: endpoints
  relabel_configs:
  - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
    action: keep
    regex: true

這樣的話,我們只需要為服務定義上如下的宣告,即可實現Prometheus自動採集資料

  annotations:
	prometheus.io/scrape: "true"

有些時候,我們業務應用提供監控資料的path地址並不一定是/metrics,如何實現相容?

同樣的思路,我們知道,Prometheus會預設使用Before Relabling中的__metrics_path作為採集路徑,因此,我們再自定義一個annotation,prometheus.io/path

  annotations:
	prometheus.io/scrape: "true"
	prometheus.io/path: "/path/to/metrics"

這樣,Prometheus端會自動生成如下標籤:

__meta_kubernetes_service_annotation_prometheus_io_path="/path/to/metrics"

我們只需要在relabel_configs中用該標籤的值,去重寫__metrics_path__的值即可。因此:

- job_name: 'kubernetes-sd-endpoints'
  kubernetes_sd_configs:
  - role: endpoints
  relabel_configs:
  - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
    action: keep
    regex: true
  - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
    action: replace
    target_label: __metrics_path__
    regex: (.+)

有些時候,業務服務的metrics是獨立的埠,比如coredns,業務埠是53,監控指標採集埠是9153,這種情況,如何處理?

很自然的,我們會想到通過自定義annotation來處理,

  annotations:
	prometheus.io/scrape: "true"
	prometheus.io/path: "/path/to/metrics"
	prometheus.io/port: "9153"

如何去替換?

我們知道Prometheus預設使用Before Relabeling中的__address__進行作為服務指標採集的地址,但是該地址的格式通常是這樣的

__address__="10.244.0.20:53"
__address__="10.244.0.21"

我們的目標是將如下兩部分拼接在一起:

  • 10.244.0.20
  • prometheus.io/port定義的值,即__meta_kubernetes_service_annotation_prometheus_io_port的值

因此,需要使用正則規則取出上述兩部分:

  - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
    action: replace
    target_label: __address__
    regex: ([^:]+)(?::\d+)?;(\d+)
    replacement: $1:$2

需要注意的幾點:

  • __address__中的:53有可能不存在,因此,使用()?的匹配方式進行
  • 表示式中,三段()我們只需要第一和第三段,不需要中間括號部分的內容,因此使用?:的方式來做非獲取匹配,即可以匹配內容,但是不會被記錄到$1,$2這種變數中
  • 多個source_labels中間預設使用;號分割,因此匹配的時候需要注意新增;

此外,還可以將before relabeling 中的更多常用的欄位取出來新增到目標的label中,比如:

  - source_labels: [__meta_kubernetes_namespace]
    action: replace
    target_label: kubernetes_namespace
  - source_labels: [__meta_kubernetes_service_name]
    action: replace
    target_label: kubernetes_name
  - source_labels: [__meta_kubernetes_pod_name]
    action: replace
    target_label: kubernetes_pod_name    

因此,目前的relabel的配置如下:

    - job_name: 'kubernetes-sd-endpoints'
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
      - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
        action: replace
        target_label: __address__
        regex: ([^:]+)(?::\d+)?;(\d+)
        replacement: $1:$2    
      - source_labels: [__meta_kubernetes_namespace]
        action: replace
        target_label: kubernetes_namespace
      - source_labels: [__meta_kubernetes_service_name]
        action: replace
        target_label: kubernetes_name
      - source_labels: [__meta_kubernetes_pod_name]
        action: replace
        target_label: kubernetes_pod_name   

驗證一下:

更新configmap並重啟Prometheus服務,檢視target列表。

kube-state-metrics監控

已經有了cadvisor,容器執行的指標已經可以獲取到,但是下面這種情況卻無能為力:

  • 我排程了多少個replicas?現在可用的有幾個?
  • 多少個Pod是running/stopped/terminated狀態?
  • Pod重啟了多少次?

而這些則是kube-state-metrics提供的內容,它基於client-go開發,輪詢Kubernetes API,並將Kubernetes的結構化資訊轉換為metrics。因此,需要藉助於kube-state-metrics來實現。

指標類別包括:

  • CronJob Metrics
  • DaemonSet Metrics
  • Deployment Metrics
  • Job Metrics
  • LimitRange Metrics
  • Node Metrics
  • PersistentVolume Metrics
  • PersistentVolumeClaim Metrics
  • Pod Metrics
    • kube_pod_info
    • kube_pod_owner
    • kube_pod_status_phase
    • kube_pod_status_ready
    • kube_pod_status_scheduled
    • kube_pod_container_status_waiting
    • kube_pod_container_status_terminated_reason
    • ...
  • Pod Disruption Budget Metrics
  • ReplicaSet Metrics
  • ReplicationController Metrics
  • ResourceQuota Metrics
  • Service Metrics
  • StatefulSet Metrics
  • Namespace Metrics
  • Horizontal Pod Autoscaler Metrics
  • Endpoint Metrics
  • Secret Metrics
  • ConfigMap Metrics

部署: https://github.com/kubernetes/kube-state-metrics#kubernetes-deployment

$ wget https://github.com/kubernetes/kube-state-metrics/archive/v1.9.7.tar.gz

$ tar zxf v1.9.7.tar.gz
$ cp -r  kube-state-metrics-1.9.7/examples/standard/ .

$ ll standard/
total 20
-rw-r--r-- 1 root root  377 Jul 24 06:12 cluster-role-binding.yaml
-rw-r--r-- 1 root root 1651 Jul 24 06:12 cluster-role.yaml
-rw-r--r-- 1 root root 1069 Jul 24 06:12 deployment.yaml
-rw-r--r-- 1 root root  193 Jul 24 06:12 service-account.yaml
-rw-r--r-- 1 root root  406 Jul 24 06:12 service.yaml

# 替換namespace為monitor
$ sed -i 's/namespace: kube-system/namespace: monitor/g' standard/*

$ kubectl create -f standard/
clusterrolebinding.rbac.authorization.k8s.io/kube-state-metrics created
clusterrole.rbac.authorization.k8s.io/kube-state-metrics created
deployment.apps/kube-state-metrics created
serviceaccount/kube-state-metrics created
service/kube-state-metrics created

如何新增到Prometheus監控target中?

$ cat standard/service.yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "8080"
  labels:
    app.kubernetes.io/name: kube-state-metrics
    app.kubernetes.io/version: v1.9.7
  name: kube-state-metrics
  namespace: monitor
spec:
  clusterIP: None
  ports:
  - name: http-metrics
    port: 8080
    targetPort: http-metrics
  - name: telemetry
    port: 8081
    targetPort: telemetry
  selector:
    app.kubernetes.io/name: kube-state-metrics
    
$ kubectl apply -f standard/service.yaml

檢視target列表,觀察是否存在kube-state-metrics的target。

kube_pod_container_status_running

Grafana

視覺化面板,功能齊全的度量儀表盤和圖形編輯器,支援 Graphite、zabbix、InfluxDB、Prometheus、OpenTSDB、Elasticsearch 等作為資料來源,比 Prometheus 自帶的圖表展示功能強大太多,更加靈活,有豐富的外掛,功能更加強大。

安裝

注意點:

  • 使用最新版本的映象 https://github.com/grafana/grafana
  • 通過環境變數設定管理員賬戶密碼
    • GF_SECURITY_ADMIN_USER
    • GF_SECURITY_ADMIN_PASSWORD
  • 通過設定securityContext的方式讓grafana程序使用root啟動
  • 資料掛載到本地
  • 配置ingress暴露訪問入口
$ cat grafana-all.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  namespace: monitor
spec:
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      volumes:
      - name: storage
        hostPath:
          path: /data/grafana/
      nodeSelector:
        app: prometheus
      securityContext:
        runAsUser: 0
      containers:
      - name: grafana
        image: grafana/grafana:7.1.1
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3000
          name: grafana
        env:
        - name: GF_SECURITY_ADMIN_USER
          value: admin
        - name: GF_SECURITY_ADMIN_PASSWORD
          value: admin
        readinessProbe:
          failureThreshold: 10
          httpGet:
            path: /api/health
            port: 3000
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 30
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /api/health
            port: 3000
            scheme: HTTP
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            cpu: 150m
            memory: 512Mi
          requests:
            cpu: 150m
            memory: 512Mi
        volumeMounts:
        - mountPath: /var/lib/grafana
          name: storage
---
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: monitor
spec:
  type: ClusterIP
  ports:
    - port: 3000
  selector:
    app: grafana

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: grafana
  namespace: monitor
spec:
  rules:
  - host: grafana.luffy.com
    http:
      paths:
      - path: /
        backend:
          serviceName: grafana
          servicePort: 3000

配置資料來源:

如何豐富Grafana監控面板:

  • 匯入dashboard
  • 安裝相應的外掛
  • 自定義監控面板
匯入Dashboard的配置

dashboard: https://grafana.com/grafana/dashboards

DevOpsProdigy KubeGraf外掛的使用

除了直接匯入Dashboard,我們還可以通過安裝外掛的方式獲得,Configuration -> Plugins可以檢視已安裝的外掛,通過 官方外掛列表 我們可以獲取更多可用外掛。

Kubernetes相關的外掛:

DevOpsProdigy KubeGraf 是一個非常優秀的 Grafana Kubernetes 外掛,是 Grafana 官方的 Kubernetes 外掛的升級版本,該外掛可以用來視覺化和分析 Kubernetes 叢集的效能,通過各種圖形直觀的展示了 Kubernetes 叢集的主要服務的指標和特徵,還可以用於檢查應用程式的生命週期和錯誤日誌。

# 進入grafana容器內部執行安裝
$ kubectl -n monitor exec -ti grafana-594f447d6c-jmjsw bash
bash-5.0# grafana-cli plugins install devopsprodigy-kubegraf-app 1.4.1
installing devopsprodigy-kubegraf-app @ 1.4.1
from: https://grafana.com/api/plugins/devopsprodigy-kubegraf-app/versions/1.4.1/download
into: /var/lib/grafana/plugins

✔ Installed devopsprodigy-kubegraf-app successfully

Restart grafana after installing plugins . <service grafana-server restart>

bash-5.0# grafana-cli plugins install grafana-piechart-panel
installing grafana-piechart-panel @ 1.5.0
from: https://grafana.com/api/plugins/grafana-piechart-panel/versions/1.5.0/download
into: /var/lib/grafana/plugins

✔ Installed grafana-piechart-panel successfully

Restart grafana after installing plugins . <service grafana-server restart>

# 也可以下載離線包進行安裝

# 重建pod生效
$ kubectl -n monitor delete po grafana-594f447d6c-jmjsw

登入grafana介面,Configuration -> Plugins 中找到安裝的外掛,點選外掛進入外掛詳情頁面,點選 [Enable]按鈕啟用外掛,點選 Set up your first k8s-cluster 建立一個新的 Kubernetes 叢集:

  • Name:luffy-k8s

  • URL:https://kubernetes.default:443

  • Access:使用預設的Server(default)

  • Skip TLS Verify:勾選,跳過證書合法性校驗

  • Auth:勾選TLS Client Auth以及With CA Cert,勾選後會下面有三塊證書內容需要填寫,內容均來自~/.kube/config檔案,需要對檔案中的內容做一次base64 解碼

    • CA Cert:使用config檔案中的certificate-authority-data對應的內容
    • Client Cert:使用config檔案中的client-certificate-data對應的內容
    • Client Key:使用config檔案中的client-key-data對應的內容
    自定義監控面板

通用的監控需求基本上都可以使用第三方的Dashboard來解決,對於業務應用自己實現的指標的監控面板,則需要我們手動進行建立。

除錯Panel:直接輸入Metrics,查詢資料。

如,輸入node_load1來檢視叢集節點最近1分鐘的平均負載,直接儲存即可生成一個panel

如何根據欄位過濾,實現聯動效果?

比如想實現根據叢集節點名稱進行過濾,可以通過如下方式:

  • 設定 -> Variables -> Add Variable,新增一個變數node,

    • Name:node
    • Label:選擇節點
    • Data Source:Prometheus
    • Query:kube_node_info,可以在頁面下方的Preview of values檢視到當前變數的可選值
    • Regex:/.*node=\"(.+?)\".*/
    • Refresh:On Dashboard Load
    • Multi-value:true
    • Include All Options:true
  • 修改Metrics,$node和變數名字保持一致,意思為自動讀取當前設定的節點的名字

    node_load1{instance=~"$node"}
    

再新增一個面板,使用如下的表示式:

100-avg(irate(node_cpu_seconds_total{mode="idle",instance=~"$node"}[5m])) by (instance)*100
Metrics指標型別與PromQL

TSDB的樣本分佈示意圖:

  ^
  │   . . . . . . . . . . . . . . . . .   . .   node_cpu{cpu="cpu0",mode="idle"}
  │     . . . . . . . . . . . . . . . . . . .   node_cpu{cpu="cpu0",mode="system"}
  │     . . . . . . . . . .   . . . . . . . .   node_load1{}
  │     . . . . . . . . . . . . . . . .   . .   node_cpu_seconds_total{...}
  v
    <------------------ 時間 ---------------->

Guage型別:

$ kubectl -n monitor get po -o wide |grep k8s-master
node-exporter-ld6sq    1/1     Running   0          4d3h    192.168.136.10   k8s-master
$ curl -s  192.168.136.10:9100/metrics |grep node_load1
# HELP node_load1 1m load average.
# TYPE node_load1 gauge
node_load1 0.18
# HELP node_load15 15m load average.
# TYPE node_load15 gauge
node_load15 0.37

Gauge型別的指標側重於反應系統的當前狀態。

  • 這類指標的樣本資料可增可減。
  • 常見指標如:node_memory_MemAvailable_bytes(可用記憶體大小)、node_load1(系統平均負載)

Guage型別的資料,通常直接查詢就會有比較直觀的業務含義,比如:

  • node_load5
  • node_memory_MemAvailable_bytes

我們也會對這類資料做簡單的處理,比如:

  • 過濾其中某些節點
  • 對指標進行數學運算

這就是PromQL提供的能力,可以對收集到的資料做聚合、計算等處理。

PromQL( Prometheus Query Language )是Prometheus自定義的一套強大的資料查詢語言,除了使用監控指標作為查詢關鍵字以為,還內建了大量的函式,幫助使用者進一步對時序資料進行處理。

比如:

  • 只顯示k8s-master節點的平均負載

    node_load1{instance="k8s-master"}
    
  • 顯示除了k8s-master節點外的其他節點的平均負載

    node_load1{instance!="k8s-master"}
    
  • 正則匹配

    node_load1{instance=~"k8s-master|k8s-slave1"}
    
  • 叢集各節點系統記憶體使用率

    (node_memory_MemTotal_bytes - node_memory_MemFree_bytes) / node_memory_MemTotal_bytes
    

counter型別:

$ curl -s  192.168.136.10:9100/metrics |grep node_cpu_seconds_total
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 294341.02
node_cpu_seconds_total{cpu="0",mode="iowait"} 120.78
node_cpu_seconds_total{cpu="0",mode="irq"} 0
node_cpu_seconds_total{cpu="0",mode="nice"} 0.13
node_cpu_seconds_total{cpu="0",mode="softirq"} 1263.29

counter型別的指標其工作方式和計數器一樣,只增不減(除非系統發生重置)。常見的監控指標,如http_requests_total,node_cpu_seconds_total都是Counter型別的監控指標。

通常計數器型別的指標,名稱後面都以_total結尾。我們通過理解CPU利用率的PromQL表示式來講解Counter指標型別的使用。

各節點CPU的平均使用率表示式:

(1- sum(increase(node_cpu_seconds_total{mode="idle"}[2m])) by (instance) / sum(increase(node_cpu_seconds_total{}[2m])) by (instance)) * 100

分析:

node_cpu_seconds_total的指標含義是統計系統執行以來,CPU資源分配的時間總數,單位為秒,是累加的值。比如,直接執行該指標:

node_cpu_seconds_total
# 顯示的是所有節點、所有CPU核心、在各種工作模式下分配的時間總和

其中mode的值和我們平常在系統中執行top命令看到的CPU顯示的資訊一致:

每個mode對應的含義如下:

  • user(us)
    表示使用者態空間或者說是使用者程序(running user space processes)使用CPU所耗費的時間。這是日常我們部署的應用所在的層面,最常見常用。
  • system(sy)
    表示核心態層級使用CPU所耗費的時間。分配記憶體、IO操作、建立子程序……都是核心操作。這也表明,當IO操作頻繁時,System引數會很高。
  • steal(st)
    當執行在虛擬化環境中,花費在其它 OS 中的時間(基於虛擬機器監視器 hypervisor 的排程);可以理解成由於虛擬機器排程器將 cpu 時間用於其它 OS 了,故當前 OS 無法使用 CPU 的時間。
  • softirq(si)
    從系統啟動開始,累計到當前時刻,軟中斷時間
  • irq(hi)
    從系統啟動開始,累計到當前時刻,硬中斷時間
  • nice(ni)
    從系統啟動開始,累計到當前時刻, 低優先順序(低優先順序意味著程序 nice 值小於 0)使用者態的程序所佔用的CPU時間
  • iowait(wa)
    從系統啟動開始,累計到當前時刻,IO等待時間
  • idle(id)
    從系統啟動開始,累計到當前時刻,除IO等待時間以外的其它等待時間,亦即空閒時間

我們通過指標拿到的各核心cpu分配的總時長資料,都是瞬時的資料,如何轉換成 CPU的利用率?

先來考慮如何我們如何計算CPU利用率,假如我的k8s-master節點是4核CPU,我們來考慮如下場景:

  • 過去1分鐘內每個CPU核心處於idle狀態的時長,假如分別為 :
    • cpu0:20s
    • cpu1:30s
    • cpu2:50s
    • cpu3:40s
  • 則四個核心總共可分配的時長是 4*60=240s
  • 實際空閒狀態的總時長為20+30+50+40=140s
  • 那麼我們可以計算出過去1分鐘k8s-master節點的CPU利用率為 (1- 140/240) * 100 = 41.7%

因此,我們只需要使用PromQL取出上述過程中的值即可:

# 過濾出當前時間點idle的時長
node_cpu_seconds_total{mode="idle"}

# 使用[1m]取出1分鐘區間內的樣本值,注意,1m區間要大於prometheus設定的抓取週期,此處會將週期內所以的樣本值取出
node_cpu_seconds_total{mode="idle"}[1m]

# 使用increase方法,獲取該區間內idle狀態的增量值,即1分鐘內,mode="idle"狀態增加的時長
increase(node_cpu_seconds_total{mode="idle"}[1m])

# 由於是多個cpu核心,因此需要做累加,使用sum函式
sum(increase(node_cpu_seconds_total{mode="idle"}[1m]))

# 由於是多臺機器,因此,需要按照instance的值進行分組累加,使用by關鍵字做分組,這樣就獲得了1分鐘內,每個節點上 所有CPU核心idle狀態的增量時長,即前面示例中的”20+30+50+40=140s“
sum(increase(node_cpu_seconds_total{mode="idle"}[1m])) by (instance)

# 去掉mode=idle的過濾條件,即可獲取1分鐘內,所有狀態的cpu獲得的增量總時長,即4*60=240s
sum(increase(node_cpu_seconds_total{}[1m])) by (instance)

# 最終的語句
(1- sum(increase(node_cpu_seconds_total{mode="idle"}[1m])) by (instance) / sum(increase(node_cpu_seconds_total{}[1m])) by (instance)) * 100

除此之外,還會經常看到irate和rate方法的使用:

irate() 是基於最後兩個資料點計算一個時序指標在一個範圍內的每秒遞增率 ,舉個例子:

# 1min內,k8s-master節點的idle狀態的cpu分配時長增量值
increase(node_cpu_seconds_total{instance="k8s-master",mode="idle"}[1m])

{cpu="0",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	56.5
{cpu="1",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	56.04
{cpu="2",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	56.6
{cpu="3",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	56.5

#以第一條資料為例,說明過去的1分鐘,k8s-master節點的第一個CPU核心,有56.5秒的時長是出於idle狀態的

# 1min內,k8s-master節點的idle狀態的cpu分配每秒的速率
irate(node_cpu_seconds_total{instance="k8s-master",mode="idle"}[1m])
{cpu="0",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	0.934
{cpu="1",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	0.932
{cpu="2",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	0.933
{cpu="3",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	0.936
# 該值如何計算的?
# irate會取出樣本中的最後兩個點來作為增長依據,然後做差值計算,並且除以兩個樣本間的資料時長,也就是說,我們設定2m,5m取出來的值是一樣的,因為只會計算最後兩個樣本差。
# 以第一條資料為例,表示用irate計算出來的結果是,過去的兩分鐘內,cpu平均每秒鐘有0.934秒的時間是處於idle狀態的


# rate會1min內第一個和最後一個樣本值為依據,計算方式和irate保持一致
rate(node_cpu_seconds_total{instance="k8s-master",mode="idle"}[1m])
{cpu="0",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	0.933
{cpu="1",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	0.940
{cpu="2",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	0.935
{cpu="3",instance="k8s-master",job="kubernetes-sd-node-exporter",mode="idle"}	0.937

因此rate的值,相對來講更平滑,因為計算的是時間段內的平均,更適合於用作告警。

ConfigMap的配置檔案掛載使用場景

開始之前,我們先來回顧一下,configmap的常用的掛載場景。

場景一:單檔案掛載到空目錄

假如業務應用有一個配置檔案,名為 application-1.conf,如果想將此配置掛載到pod的/etc/application/目錄中。

application-1.conf的內容為:

$ cat application-1.conf
name: "application"
platform: "linux"
purpose: "demo"
company: "luffy"
version: "v2.1.0"

該配置檔案在k8s中可以通過configmap來管理,通常我們有如下兩種方式來管理配置檔案:

  • 通過kubectl命令列來生成configmap

    # 通過檔案直接建立
    $ kubectl -n default create configmap application-config --from-file=application-1.conf
    
    # 會生成配置檔案,檢視內容,configmap的key為檔名字
    $ kubectl -n default get cm application-config -oyaml
    
  • 通過yaml檔案直接建立

    $ cat application-config.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: application-config
      namespace: default
    data:
      application-1.conf: |
        name: "application"
        platform: "linux"
        purpose: "demo"
        company: "luffy"
        version: "v2.1.0"
    
    # 建立configmap
    $ kubectl create -f application-config.yaml
    

準備一個demo-deployment.yaml檔案,掛載上述configmap到/etc/application/

$ cat demo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
  namespace: default
spec:
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      volumes:
      - configMap:
          name: application-config
        name: config
      containers:
      - name: nginx
        image: nginx:alpine
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: "/etc/application"
          name: config

建立並檢視:

$ kubectl create -f demo-deployment.yaml

修改configmap檔案的內容,觀察pod中是否自動感知變化:

$ kubectl edit cm application-config

整個configmap檔案直接掛載到pod中,若configmap變化,pod會自動感知並拉取到pod內部。

但是pod內的程序不會自動重啟,所以很多服務會實現一個內部的reload介面,用來載入最新的配置檔案到程序中。

場景二:多檔案掛載

假如有多個配置檔案,都需要掛載到pod內部,且都在一個目錄中

$ cat application-1.conf
name: "application-1"
platform: "linux"
purpose: "demo"
company: "luffy"
version: "v2.1.0"
$ cat application-2.conf
name: "application-2"
platform: "linux"
purpose: "demo"
company: "luffy"
version: "v2.1.0"

同樣可以使用兩種方式建立:

$ kubectl delete cm application-config

$ kubectl create cm application-config --from-file=application-1.conf --from-file=application-2.conf

$ kubectl get cm application-config -oyaml

觀察Pod已經自動獲取到最新的變化

$ kubectl exec demo-55c649865b-gpkgk ls /etc/application/
application-1.conf
application-2.conf

此時,是掛載到pod內的空目錄中/etc/application,假如想掛載到pod已存在的目錄中,比如:

$  kubectl exec   demo-55c649865b-gpkgk ls /etc/profile.d
color_prompt
locale

更改deployment的掛載目錄:

$ cat demo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
  namespace: default
spec:
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      volumes:
      - configMap:
          name: application-config
        name: config
      containers:
      - name: nginx
        image: nginx:alpine
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: "/etc/profile.d"
          name: config

重建pod

$ kubectl apply -f demo-deployment.yaml

# 檢視pod內的/etc/profile.d目錄,發現已有檔案被覆蓋
$ kubectl exec demo-77d685b9f7-68qz7 ls /etc/profile.d
application-1.conf
application-2.conf
場景三 掛載子路徑

實現多個配置檔案,可以掛載到pod內的不同的目錄中。比如:

  • application-1.conf掛載到/etc/application/
  • application-2.conf掛載到/etc/profile.d

configmap保持不變,修改deployment檔案:

$ cat demo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
  namespace: default
spec:
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      volumes:
      - name: config
        configMap:
          name: application-config
          items:
          - key: application-1.conf
            path: application1
          - key: application-2.conf
            path: application2
      containers:
      - name: nginx
        image: nginx:alpine
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: "/etc/application/application-1.conf"
          name: config
          subPath: application1
        - mountPath: "/etc/profile.d/application-2.conf"
          name: config
          subPath: application2

測試掛載:

$ kubectl apply -f demo-deployment.yaml

$ kubectl exec demo-78489c754-shjhz ls /etc/application
application-1.conf

$ kubectl exec demo-78489c754-shjhz ls /etc/profile.d/
application-2.conf
color_prompt
locale

使用subPath掛載到Pod內部的檔案,不會自動感知原有ConfigMap的變更

Alertmanager

Alertmanager是一個獨立的告警模組。

  • 接收Prometheus等客戶端發來的警報
  • 通過分組、刪除重複等處理,並將它們通過路由傳送給正確的接收器;
  • 告警方式可以按照不同的規則傳送給不同的模組負責人。Alertmanager支援Email, Slack,等告警方式, 也可以通過webhook接入釘釘等國內IM工具。

如果叢集主機的記憶體使用率超過80%,且該現象持續了2分鐘?想實現這樣的監控告警,如何做?

從上圖可得知設定警報和通知的主要步驟是:

  • 安裝和配置 Alertmanager

  • 配置Prometheus與Alertmanager對話

  • 在Prometheus中建立警報規則

安裝

Alertmanager, https://github.com/prometheus/alertmanager#install

./alertmanager --config.file=config.yml

alertmanager.yml配置檔案格式:

$ cat alertmanager-config.yml
apiVersion: v1
data:
  config.yml: |
    global:
      # 當alertmanager持續多長時間未接收到告警後標記告警狀態為 resolved
      resolve_timeout: 5m
      # 配置郵件傳送資訊
      smtp_smarthost: 'smtp.163.com:25'
      smtp_from: '[email protected]'
      smtp_auth_username: '[email protected]'
      smtp_auth_password: 'qzpm10'
      smtp_require_tls: false
    # 所有報警資訊進入後的根路由,用來設定報警的分發策略
    route:
      # 接收到的報警資訊裡面有許多alertname=NodeLoadHigh 這樣的標籤的報警資訊將會批量被聚合到一個分組裡面
      group_by: ['alertname']
      # 當一個新的報警分組被建立後,需要等待至少 group_wait 時間來初始化通知,如果在等待時間內當前group接收到了新的告警,這些告警將會合併為一個通知向receiver傳送
      group_wait: 30s

      # 相同的group傳送告警通知的時間間隔
      group_interval: 30s
      # 如果一個報警資訊已經發送成功了,等待 repeat_interval 時間來重新發送
      repeat_interval: 10m

      # 預設的receiver:如果一個報警沒有被一個route匹配,則傳送給預設的接收器
      receiver: default

      # 上面所有的屬性都由所有子路由繼承,並且可以在每個子路由上進行覆蓋。
      routes:
      - {}
    # 配置告警接收者的資訊
    receivers:
    - name: 'default'
      email_configs:
      - to: '[email protected]'
        send_resolved: true  # 接受告警恢復的通知
kind: ConfigMap
metadata:
  name: alertmanager
  namespace: monitor

主要配置的作用:

  • global: 全域性配置,包括報警解決後的超時時間、SMTP 相關配置、各種渠道通知的 API 地址等等。
  • route: 用來設定報警的分發策略,它是一個樹狀結構,按照深度優先從左向右的順序進行匹配。
  • receivers: 配置告警訊息接受者資訊,例如常用的 email、wechat、slack、webhook 等訊息通知方式。

配置檔案:

$ kubectl create -f  alertmanager-config.yml

其他資源清單檔案:

$ cat alertmanager-all.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: alertmanager
  namespace: monitor
  labels:
    app: alertmanager
spec:
  selector:
    matchLabels:
      app: alertmanager
  template:
    metadata:
      labels:
        app: alertmanager
    spec:
      volumes:
      - name: config
        configMap:
          name: alertmanager
      containers:
      - name: alertmanager
        image: prom/alertmanager:v0.21.0
        imagePullPolicy: IfNotPresent
        args:
        - "--config.file=/etc/alertmanager/config.yml"
        - "--log.level=debug"
        ports:
        - containerPort: 9093
          name: http
        volumeMounts:
        - mountPath: "/etc/alertmanager"
          name: config
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            cpu: 100m
            memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
  name: alertmanager
  namespace: monitor
spec:
  type: ClusterIP
  ports:
    - port: 9093
  selector:
    app: alertmanager

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: alertmanager
  namespace: monitor
spec:
  rules:
  - host: alertmanager.luffy.com
    http:
      paths:
      - path: /
        backend:
          serviceName: alertmanager
          servicePort: 9093
配置Prometheus與Alertmanager對話

是否告警是由Prometheus進行判斷的,若有告警產生,Prometheus會將告警push到Alertmanager,因此,需要在Prometheus端配置alertmanager的地址:

    alerting:
      alertmanagers:
      - static_configs:
        - targets:
          - alertmanager:9093

因此,修改Prometheus的配置檔案,然後重新載入pod

# 編輯prometheus-configmap.yaml配置,新增alertmanager內容
$ vim prometheus-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitor
data:
  prometheus.yml: |
    global:
      scrape_interval: 30s
      evaluation_interval: 30s
    alerting:
      alertmanagers:
      - static_configs:
        - targets:
          - alertmanager:9093
...
  
  
$ kubectl apply -f prometheus-configmap.yaml

# 現在已經有監控資料了,因此使用prometheus提供的reload的介面,進行服務重啟

# 檢視配置檔案是否已經自動載入到pod中
$ kubectl -n monitor get po -o wide
prometheus-dcb499cbf-pljfn            1/1     Running   0          47h    10.244.1.167  

$ kubectl -n monitor exec -ti prometheus-dcb499cbf-pljfn cat /etc/prometheus/prometheus.yml |grep alertmanager

# 使用軟載入的方式,
$ curl -X POST 10.244.1.167:9090/-/reload
配置報警規則

目前Prometheus與Alertmanager已經連通,接下來我們可以針對收集到的各類指標配置報警規則,一旦滿足報警規則的設定,則Prometheus將報警資訊推送給Alertmanager,進而轉發到我們配置的郵件中。

在哪裡配置?同樣是在prometheus-configmap中:

$ vim prometheus-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitor
data:
  prometheus.yml: |
    global:
      scrape_interval: 30s
      evaluation_interval: 30s
    alerting:
      alertmanagers:
      - static_configs:
        - targets:
          - alertmanager:9093
    # Load rules once and periodically evaluate them according to the global  'evaluation_interval'.
    rule_files:
      - /etc/prometheus/alert_rules.yml
      # - "first_rules.yml"
      # - "second_rules.yml"
    scrape_configs:
    - job_name: 'prometheus'
      static_configs:
      - targets: ['localhost:9090']
...

rules.yml我們同樣使用configmap的方式掛載到prometheus容器內部,因此只需要在已有的configmap中加一個數據專案

$ vim prometheus-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitor
data:
  prometheus.yml: |
    global:
      scrape_interval: 30s
      evaluation_interval: 30s
    alerting:
      alertmanagers:
      - static_configs:
        - targets:
          - alertmanager:9093
    # Load rules once and periodically evaluate them according to the global  'evaluation_interval'.
    rule_files:
      - /etc/prometheus/alert_rules.yml
      # - "first_rules.yml"
      # - "second_rules.yml"
    scrape_configs:
    - job_name: 'prometheus'
      static_configs:
      - targets: ['localhost:9090']
... # 省略中間部分
  alert_rules.yml: |
    groups:
    - name: node_metrics
      rules:
      - alert: NodeLoad
        expr: node_load15 < 1
        for: 2m
        annotations:
          summary: "{{$labels.instance}}: Low node load detected"
          description: "{{$labels.instance}}: node load is below 1 (current value is: {{ $value }}"

告警規則的幾個要素:

  • group.name:告警分組的名稱,一個組下可以配置一類告警規則,比如都是物理節點相關的告警
  • alert:告警規則的名稱
  • expr:是用於進行報警規則 PromQL 查詢語句,expr通常是布林表示式,可以讓Prometheus根據計算的指標值做 true or false 的判斷
  • for:評估等待時間(Pending Duration),用於表示只有當觸發條件持續一段時間後才傳送告警,在等待期間新產生的告警狀態為pending,遮蔽掉瞬時的問題,把焦點放在真正有持續影響的問題上
  • labels:自定義標籤,允許使用者指定額外的標籤列表,把它們附加在告警上,可以用於後面做路由判斷,通知到不同的終端,通常被用於新增告警級別的標籤
  • annotations:指定了另一組標籤,它們不被當做告警例項的身份標識,它們經常用於儲存一些額外的資訊,用於報警資訊的展示之類的

規則配置中,支援模板的方式,其中:

  • {{$labels}}可以獲取當前指標的所有標籤,支援{{$labels.instance}}或者{{$labels.job}}這種形式

  • {{ $value }}可以獲取當前計算出的指標值

更新配置並軟重啟,並檢視Prometheus報警規則。

一個報警資訊在生命週期內有下面3種狀態:

  • inactive: 表示當前報警資訊處於非活動狀態,即不滿足報警條件
  • pending: 表示在設定的閾值時間範圍內被激活了,即滿足報警條件,但是還在觀察期內
  • firing: 表示超過設定的閾值時間被激活了,即滿足報警條件,且報警觸發時間超過了觀察期,會發送到Alertmanager端

對於已經 pending 或者 firing 的告警,Prometheus 也會將它們儲存到時間序列ALERTS{}中。當然我們也可以通過表示式去查詢告警例項:

ALERTS{}

檢視Alertmanager日誌:

level=warn ts=2020-07-28T13:43:59.430Z caller=notify.go:674 component=dispatcher receiver=email integration=email[0] msg="Notify attempt failed, will retry later" attempts=1 err="*email.loginAuth auth: 550 User has no permission"

說明告警已經推送到Alertmanager端了,但是郵箱登入的時候報錯,這是因為郵箱預設沒有開啟第三方客戶端登入。因此需要登入163郵箱設定SMTP服務允許客戶端登入。

自定義webhook實現告警訊息的推送

目前官方內建的第三方通知整合包括:郵件、 即時通訊軟體(如Slack、Hipchat)、移動應用訊息推送(如Pushover)和自動化運維工具(例如:Pagerduty、Opsgenie、Victorops)。可以在alertmanager的管理介面中檢視到。

每一個receiver具有一個全域性唯一的名稱,並且對應一個或者多個通知方式:

name: <string>
email_configs:
  [ - <email_config>, ... ]
hipchat_configs:
  [ - <hipchat_config>, ... ]
slack_configs:
  [ - <slack_config>, ... ]
opsgenie_configs:
  [ - <opsgenie_config>, ... ]
webhook_configs:
  [ - <webhook_config>, ... ]

如果想實現告警訊息推送給企業常用的即時聊天工具,如釘釘或者企業微信,如何配置?

Alertmanager的通知方式中還可以支援Webhook,通過這種方式開發者可以實現更多個性化的擴充套件支援。

# 警報接收者
receivers:
#ops 
- name: 'demo-webhook'
  webhook_configs:
  - send_resolved: true
    url: http://demo-webhook/alert/send

當我們配置了上述webhook地址,則當告警路由到demo-webhook時,alertmanager端會向webhook地址推送POST請求:

$ curl -X POST -d"$demoAlerts"  http://demo-webhook/alert/send
$ echo $demoAlerts
{
  "version": "4",
  "groupKey": <string>, alerts (e.g. to deduplicate) ,
  "status": "<resolved|firing>", 
  "receiver": <string>, 
  "groupLabels": <object>, 
  "commonLabels": <object>, 
  "commonAnnotations": <object>, 
  "externalURL": <string>, // backlink to the Alertmanager. 
  "alerts": 
   [{ 
     "labels": <object>, 
      "annotations": <object>, 
      "startsAt": "<rfc3339>", 
      "endsAt": "<rfc3339>" 
   }] 
} 

因此,假如我們想把報警訊息自動推送到釘釘群聊,只需要:

  • 實現一個webhook,部署到k8s叢集
    • 接收POST請求,將Alertmanager傳過來的資料做解析,呼叫dingtalk的API,實現訊息推送
  • 配置alertmanager的receiver為webhook地址

如何給釘釘群聊傳送訊息? 釘釘機器人

釘釘群聊機器人設定:

每個群聊機器人在建立的時候都會生成唯一的一個訪問地址:

https://oapi.dingtalk.com/robot/send?access_token=e54f616718798e32d1e2ff1af5b095c37501878f816bdab2daf66d390633843a

這樣,我們就可以使用如下方式來模擬給群聊機器人傳送請求,實現訊息的推送:

curl 'https://oapi.dingtalk.com/robot/send?access_token=e54f616718798e32d1e2ff1af5b095c37501878f816bdab2daf66d390633843a' \
   -H 'Content-Type: application/json' \
   -d '{"msgtype": "text","text": {"content": "我就是我, 是不一樣的煙火"}}'

https://gitee.com/agagin/prometheus-webhook-dingtalk

映象地址:timonwong/prometheus-webhook-dingtalk:master

二進位制執行:

$ ./prometheus-webhook-dingtalk --config.file=config.yml

假如使用如下配置:

targets:
  webhook_dev:
    url: https://oapi.dingtalk.com/robot/send?access_token=e54f616718798e32d1e2ff1af5b095c37501878f816bdab2daf66d390633843a
  webhook_ops:
    url: https://oapi.dingtalk.com/robot/send?access_token=d4e7b72eab6d1b2245bc0869d674f627dc187577a3ad485d9c1d131b7d67b15b

則prometheus-webhook-dingtalk啟動後會自動支援如下API的POST訪問:

http://locahost:8060/dingtalk/webhook_dev/send
http://localhost:8060/dingtalk/webhook_ops/send

這樣可以使用一個prometheus-webhook-dingtalk來實現多個釘釘群的webhook地址

部署prometheus-webhook-dingtalk,從Dockerfile可以得知需要注意的點:

  • 預設使用配置檔案/etc/prometheus-webhook-dingtalk/config.yml,可以通過configmap掛載
  • 該目錄下還有模板檔案,因此需要使用subpath的方式掛載
  • 部署Service,作為Alertmanager的預設訪問,服務埠預設8060

配置檔案:

$ cat webhook-dingtalk-configmap.yaml
apiVersion: v1
data:
  config.yml: |
    targets:
      webhook_dev:
        url: https://oapi.dingtalk.com/robot/send?access_token=e54f616718798e32d1e2ff1af5b095c37501878f816bdab2daf66d390633843a
kind: ConfigMap
metadata:
  name: webhook-dingtalk-config
  namespace: monitor

Deployment和Service

$ cat webhook-dingtalk-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webhook-dingtalk
  namespace: monitor
spec:
  selector:
    matchLabels:
      app: webhook-dingtalk
  template:
    metadata:
      labels:
        app: webhook-dingtalk
    spec:
      containers:
      - name: webhook-dingtalk
        image: timonwong/prometheus-webhook-dingtalk:master
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: "/etc/prometheus-webhook-dingtalk/config.yml"
          name: config
          subPath: config.yml
        ports:
        - containerPort: 8060
          name: http
        resources:
          requests:
            cpu: 50m
            memory: 100Mi
          limits:
            cpu: 50m
            memory: 100Mi
      volumes:
      - name: config
        configMap:
          name: webhook-dingtalk-config
          items:
          - key: config.yml
            path: config.yml
---
apiVersion: v1
kind: Service
metadata:
  name: webhook-dingtalk
  namespace: monitor
spec:
  selector:
    app: webhook-dingtalk
  ports:
  - name: hook
    port: 8060
    targetPort: http

建立:

$ kubectl create -f webhook-dingtalk-configmap.yaml
$ kubectl create -f webhook-dingtalk-deploy.yaml

# 檢視日誌,可以得知當前的可用webhook日誌
$ kubectl -n monitor logs -f webhook-dingtalk-f7f5589c9-qglkd
...
file=/etc/prometheus-webhook-dingtalk/config.yml msg="Completed loading of configuration file"
level=info ts=2020-07-30T14:05:40.963Z caller=main.go:117 component=configuration msg="Loading templates" templates=
ts=2020-07-30T14:05:40.963Z caller=main.go:133 component=configuration msg="Webhook urls for prometheus alertmanager" urls="http://localhost:8060/dingtalk/webhook_dev/send http://localhost:8060/dingtalk/webhook_ops/send"
level=info ts=2020-07-30T14:05:40.963Z caller=web.go:210 component=web msg="Start listening for connections" address=:8060

修改Alertmanager路由及webhook配置:

$ cat alertmanager-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: alertmanager
  namespace: monitor
data:
  config.yml: |-
    global:
      # 當alertmanager持續多長時間未接收到告警後標記告警狀態為 resolved
      resolve_timeout: 5m
      # 配置郵件傳送資訊
      smtp_smarthost: 'smtp.163.com:25'
      smtp_from: '[email protected]'
      smtp_auth_username: '[email protected]'
      # 注意這裡不是郵箱密碼,是郵箱開啟第三方客戶端登入後的授權碼
      smtp_auth_password: 'GXIWNXKMMEVMNHAJ'
      smtp_require_tls: false
    # 所有報警資訊進入後的根路由,用來設定報警的分發策略
    route:
      # 按照告警名稱分組
      group_by: ['alertname']
      # 當一個新的報警分組被建立後,需要等待至少 group_wait 時間來初始化通知,這種方式可以確保您能有足夠的時間為同一分組來獲取多個警報,然後一起觸發這個報警資訊。
      group_wait: 30s

      # 相同的group之間傳送告警通知的時間間隔
      group_interval: 30s

      # 如果一個報警資訊已經發送成功了,等待 repeat_interval 時間來重新發送他們,不同型別告警傳送頻率需要具體配置
      repeat_interval: 10m

      # 預設的receiver:如果一個報警沒有被一個route匹配,則傳送給預設的接收器
      receiver: default

      # 路由樹,預設繼承global中的配置,並且可以在每個子路由上進行覆蓋。
      routes:
      - {}
    receivers:
    - name: 'default'
      email_configs:
      - to: '[email protected]'
        send_resolved: true  # 接受告警恢復的通知
      webhook_configs:
      - send_resolved: true
        url: http://webhook-dingtalk:8060/dingtalk/webhook_dev/send

驗證釘釘訊息是否正常收到。

基於Label的動態告警處理

真實的場景中,我們往往期望可以給告警設定級別,而且可以實現不同的報警級別可以由不同的receiver接收告警訊息。

Alertmanager中路由負責對告警資訊進行分組匹配,並向告警接收器傳送通知。告警接收器可以通過以下形式進行配置:

routes:
- receiver: ops
  group_wait: 10s
  match:
    severity: critical
- receiver: dev
  group_wait: 10s
  match_re:
    severity: normal|middle
receivers:
  - ops
    ...
  - dev
    ...
  - <receiver> ...

因此可以為了更全面的感受報警的邏輯,我們再新增兩個報警規則:

  alert_rules.yml: |
    groups:
    - name: node_metrics
      rules:
      - alert: NodeLoad
        expr: node_load15 < 1
        for: 2m
        labels:
          severity: normal
        annotations:
          summary: "{{$labels.instance}}: Low node load detected"
          description: "{{$labels.instance}}: node load is below 1 (current value is: {{ $value }}"
      - alert: NodeMemoryUsage
        expr: (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) / node_memory_MemTotal_bytes * 100 > 40
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "{{$labels.instance}}: High Memory usage detected"
          description: "{{$labels.instance}}: Memory usage is above 40% (current value is: {{ $value }}"
    - name: targets_status
      rules:
      - alert: TargetStatus
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "{{$labels.instance}}: prometheus target down"
          description: "{{$labels.instance}}: prometheus target down,job is {{$labels.job}}"

我們為不同的報警規則設定了不同的標籤,如severity: critical,針對規則中的label,來配置alertmanager路由規則,實現轉發給不同的接收者。

$ cat alertmanager-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: alertmanager
  namespace: monitor
data:
  config.yml: |-
    global:
      # 當alertmanager持續多長時間未接收到告警後標記告警狀態為 resolved
      resolve_timeout: 5m
      # 配置郵件傳送資訊
      smtp_smarthost: 'smtp.163.com:25'
      smtp_from: '[email protected]'
      smtp_auth_username: '[email protected]'
      # 注意這裡不是郵箱密碼,是郵箱開啟第三方客戶端登入後的授權碼
      smtp_auth_password: 'GXIWNXKMMEVMNHAJ'
      smtp_require_tls: false
    # 所有報警資訊進入後的根路由,用來設定報警的分發策略
    route:
      # 按照告警名稱分組
      group_by: ['alertname']
      # 當一個新的報警分組被建立後,需要等待至少 group_wait 時間來初始化通知,這種方式可以確保您能有足夠的時間為同一分組來獲取多個警報,然後一起觸發這個報警資訊。
      group_wait: 30s

      # 相同的group之間傳送告警通知的時間間隔
      group_interval: 30s

      # 如果一個報警資訊已經發送成功了,等待 repeat_interval 時間來重新發送他們,不同型別告警傳送頻率需要具體配置
      repeat_interval: 10m

      # 預設的receiver:如果一個報警沒有被一個route匹配,則傳送給預設的接收器
      receiver: default

      # 路由樹,預設繼承global中的配置,並且可以在每個子路由上進行覆蓋。
      routes:
      - receiver: critical_alerts
        group_wait: 10s
        match:
          severity: critical
      - receiver: normal_alerts
        group_wait: 10s
        match_re:
          severity: normal|middle
    receivers:
    - name: 'default'
      email_configs:
      - to: '[email protected]'
        send_resolved: true  # 接受告警恢復的通知
    - name: 'critical_alerts'
      webhook_configs:
      - send_resolved: true
        url: http://webhook-dingtalk:8060/dingtalk/webhook_ops/send
    - name: 'normal_alerts'
      webhook_configs:
      - send_resolved: true
        url: http://webhook-dingtalk:8060/dingtalk/webhook_dev/send

再配置一個釘釘機器人,修改webhook-dingtalk的配置,新增webhook_ops的配置:

$ cat webhook-dingtalk-configmap.yaml
apiVersion: v1
data:
  config.yml: |
    targets:
      webhook_dev:
        url: https://oapi.dingtalk.com/robot/send?access_token=e54f616718798e32d1e2ff1af5b095c37501878f816bdab2daf66d390633843a
      webhook_ops:
        url: https://oapi.dingtalk.com/robot/send?access_token=5a68888fbecde75b1832ff024d7374e51f2babd33f1078e5311cdbb8e2c00c3a
kind: ConfigMap
metadata:
  name: webhook-dingtalk-config
  namespace: monitor

設定webhook-dingtalk開啟lifecycle

分別更新Prometheus和Alertmanager配置,檢視報警的傳送。

抑制和靜默

前面我們知道,告警的group(分組)功能通過把多條告警資料聚合,有效的減少告警的頻繁傳送。除此之外,Alertmanager還支援Inhibition(抑制)Silences(靜默),幫助我們抑制或者遮蔽報警。

  • Inhibition 抑制

    抑制是當出現其它告警的時候壓制當前告警的通知,可以有效的防止告警風暴。

    比如當機房出現網路故障時,所有服務都將不可用而產生大量服務不可用告警,但這些警告並不能反映真實問題在哪,真正需要發出的應該是網路故障告警。當出現網路故障告警的時候,應當抑制服務不可用告警的通知。

    在Alertmanager配置檔案中,使用inhibit_rules定義一組告警的抑制規則:

    inhibit_rules:
      [ - <inhibit_rule> ... ]
    

    每一條抑制規則的具體配置如下:

    target_match:
      [ <labelname>: <labelvalue>, ... ]
    target_match_re:
      [ <labelname>: <regex>, ... ]
    
    source_match:
      [ <labelname>: <labelvalue>, ... ]
    source_match_re:
      [ <labelname>: <regex>, ... ]
    
    [ equal: '[' <labelname>, ... ']' ]
    

    當已經發送的告警通知匹配到target_match或者target_match_re規則,當有新的告警規則如果滿足source_match或者定義的匹配規則,並且已傳送的告警與新產生的告警中equal定義的標籤完全相同,則啟動抑制機制,新的告警不會發送。

    例如,定義如下抑制規則:

    - source_match:
        alertname: NodeDown
        severity: critical
      target_match:
        severity: critical
      equal:
        - node
    

    如當叢集中的某一個主機節點異常宕機導致告警NodeDown被觸發,同時在告警規則中定義了告警級別severity=critical。由於主機異常宕機,該主機上部署的所有服務,中介軟體會不可用並觸發報警。根據抑制規則的定義,如果有新的告警級別為severity=critical,並且告警中標籤node的值與NodeDown告警的相同,則說明新的告警是由NodeDown導致的,則啟動抑制機制停止向接收器傳送通知。

    演示:實現如果 NodeMemoryUsage 報警觸發,則抑制NodeLoad指標規則引起的報警。

        inhibit_rules:
        - source_match:
            alertname: NodeMemoryUsage
            severity: critical
          target_match:
            severity: normal
          equal:
            - instance
    
  • Silences: 靜默

    簡單直接的在指定時段關閉告警。靜默通過匹配器(Matcher)來配置,類似於路由樹。警告進入系統的時候會檢查它是否匹配某條靜默規則,如果是則該警告的通知將忽略。 靜默規則在Alertmanager的 Web 介面裡配置。

一條告警產生後,還要經過 Alertmanager 的分組、抑制處理、靜默處理、去重處理和降噪處理最後再發送給接收者。這個過程中可能會因為各種原因會導致告警產生了卻最終沒有進行通知,可以通過下圖瞭解整個告警的生命週期:

https://github.com/liyongxin/prometheus-webhook-wechat

自定義指標實現業務伸縮
Kubernetes Metrics API體系回顧

我們基於CPU和記憶體的HPA,即利用metrics-server及HPA,可以實現業務服務可以根據pod的cpu和記憶體進行彈性伸縮。

k8s對監控介面進行了標準化:

  • Resource Metrics

    對應的介面是 metrics.k8s.io,主要的實現就是 metrics-server

  • Custom Metrics

    對應的介面是 custom.metrics.k8s.io,主要的實現是 Prometheus, 它提供的是資源監控和自定義監控

安裝完metrics-server後,利用kube-aggregator的功能,實現了metrics api的註冊。可以通過如下命令

$ kubectl api-versions
...
metrics.k8s.io/v1beta1

HPA通過使用該API獲取監控的CPU和記憶體資源:

# 查詢nodes節點的cpu和記憶體資料
$ kubectl get --raw="/apis/metrics.k8s.io/v1beta1/nodes"|jq

$ kubectl get --raw="/apis/metrics.k8s.io/v1beta1/pods"|jq

# 若本機沒有安裝jq命令,可以參考如下方式進行安裝
$ wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
$ rpm -ivh epel-release-latest-7.noarch.rpm
$ yum install -y jq

同樣,為了實現通用指標的採集,需要部署Prometheus Adapter,來提供custom.metrics.k8s.io,作為HPA獲取通用指標的入口。

Adapter安裝對接

專案地址為: https://github.com/DirectXMan12/k8s-prometheus-adapter

$ git clone https://github.com/DirectXMan12/k8s-prometheus-adapter.git

# 最新release版本v0.7.0,程式碼切換到v0.7.0分支
$ git checkout v0.7.0

檢視部署說明 https://github.com/DirectXMan12/k8s-prometheus-adapter/tree/v0.7.0/deploy

  1. 映象使用官方提供的v0.7.0最新版 https://hub.docker.com/r/directxman12/k8s-prometheus-adapter/tags

  2. 準備證書

    $ export PURPOSE=serving
    $ openssl req -x509 -sha256 -new -nodes -days 365 -newkey rsa:2048 -keyout ${PURPOSE}.key -out ${PURPOSE}.crt -subj "/CN=ca"
    
    $ kubectl -n monitor create secret generic cm-adapter-serving-certs --from-file=./serving.crt --from-file=./serving.key 
    
    # 檢視證書
    $ kubectl -n monitor describe secret cm-adapter-serving-certs
    
  3. 準備資源清單

    $ mkdir yamls
    $ cp manifests/custom-metrics-apiserver-deployment.yaml yamls/
    $ cp manifests/custom-metrics-apiserver-service.yaml yamls/
    $ cp manifests/custom-metrics-apiservice.yaml yamls/
    
    $ cd yamls
    # 新建rbac檔案
    $ vi custom-metrics-apiserver-rbac.yaml
    kind: ServiceAccount
    apiVersion: v1
    metadata:
      name: custom-metrics-apiserver
      namespace: monitor
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: custom-metrics-resource-cluster-admin
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: cluster-admin
    subjects:
    - kind: ServiceAccount
      name: custom-metrics-apiserver
      namespace: monitor
      
    # 新建配置檔案
    $ vi custom-metrics-configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: adapter-config
      namespace: monitor
    data:
      config.yaml: |
        rules:
        - {}
    
  4. 替換名稱空間

    # 資源清單檔案預設用的名稱空間是custom-metrics,替換為本例中使用的monitor
    $ sed -i 's/namespace: custom-metrics/namespace: monitor/g' yamls/*
    
    
  5. 配置adapter對接的Prometheus地址

    # 由於adapter會和Prometheus互動,因此需要配置對接的Prometheus地址
    # 替換掉28行:manifests/custom-metrics-apiserver-deployment.yaml 中的--prometheus-url
    $ vim manifests/custom-metrics-apiserver-deployment.yaml
    ...
         18     spec:
         19       serviceAccountName: custom-metrics-apiserver
         20       containers:
         21       - name: custom-metrics-apiserver
         22         image: directxman12/k8s-prometheus-adapter-amd64
         23         args:
         24         - --secure-port=6443
         25         - --tls-cert-file=/var/run/serving-cert/serving.crt
         26         - --tls-private-key-file=/var/run/serving-cert/serving.key
         27         - --logtostderr=true
         28         - --prometheus-url=http://prometheus:9090/
         29         - --metrics-relist-interval=1m
         30         - --v=10
         31         - --config=/etc/adapter/config.yaml
    ...
    
  6. 部署服務

    $ kubectl create -f yamls/
    

驗證一下:

$ kubectl api-versions 
custom.metrics.k8s.io/v1beta1

$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 |jq
{                                                    
  "kind": "APIResourceList",                         
  "apiVersion": "v1",                                
  "groupVersion": "custom.metrics.k8s.io/v1beta1",   
  "resources": []                                    
}                                                    
通用指標示例程式部署


為了演示效果,我們新建一個deployment來模擬業務應用。

$ cat custom-metrics-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-metrics-demo
spec:
  selector:
    matchLabels:
      app: custom-metrics-demo
  template:
    metadata:
      labels:
        app: custom-metrics-demo
    spec:
      containers:
      - name: custom-metrics-demo
        image: cnych/nginx-vts:v1.0
        resources:
          limits:
            cpu: 50m
          requests:
            cpu: 50m
        ports:
        - containerPort: 80
          name: http

部署:

$ kubectl create -f custom-metrics-demo.yaml

$ kubectl get po -o wide
custom-metrics-demo-95b5bc949-xpppl   1/1     Running   0          65s   10.244.1.194

$ curl 10.244.1.194/status/format/prometheus
...
nginx_vts_server_requests_total{host="*",code="1xx"} 0
nginx_vts_server_requests_total{host="*",code="2xx"} 8
nginx_vts_server_requests_total{host="*",code="3xx"} 0
nginx_vts_server_requests_total{host="*",code="4xx"} 0
nginx_vts_server_requests_total{host="*",code="5xx"} 0
nginx_vts_server_requests_total{host="*",code="total"} 8
...

註冊為Prometheus的target:

$ cat custom-metrics-demo-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: custom-metrics-demo
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "80"
    prometheus.io/path: "/status/format/prometheus"
spec:
  ports:
  - port: 80
    targetPort: 80
    name: http
  selector:
    app: custom-metrics-demo
  type: ClusterIP

自動註冊為Prometheus的採集Targets。

通常web類的應用,會把每秒鐘的請求數作為業務伸縮的指標依據。

實踐:

使用案例應用custom-metrics-demo,如果custom-metrics-demo最近2分鐘內每秒鐘的請求數超過10次,則自動擴充業務應用的副本數。

  • 配置自定義指標

    告訴Adapter去採集轉換哪些指標,Adapter支援轉換的指標,才可以作為HPA的依據

  • 配置HPA規則

    apiVersion: autoscaling/v2beta1
    kind: HorizontalPodAutoscaler
    metadata:
      name: nginx-custom-hpa
      namespace: default
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: custom-metrics-demo
      minReplicas: 1
      maxReplicas: 3
      metrics:
      - type: Pods
        pods:
          metricName: nginx_vts_server_requests_per_second
          targetAverageValue: 10
    
Adapter配置自定義指標

前面講CPU的平均使用率的採集,其實是通過node_cpu_seconds_total指標計算得到的。

  ^
  │   . . . . . . . . . . . . . . . . .   . .   node_cpu{cpu="cpu0",mode="idle"}
  │     . . . . . . . . . . . . . . . . . . .   node_cpu{cpu="cpu0",mode="system"}
  │     . . . . . . . . . .   . . . . . . . .   node_load1{}
  │     . . . . . . . . . . . . . . . .   . .   node_cpu_seconds_total{...}
  v
    <------------------ 時間 ---------------->

同樣,如果想獲得每個業務應用最近2分鐘內每秒的訪問次數,也是根據總數來做計算,因此,需要使用業務自定義指標nginx_vts_server_requests_total,配合rate方法即可獲取每秒鐘的請求數。

rate(nginx_vts_server_requests_total[2m])

# 如查詢有多條資料,需做匯聚,需要使用sum
sum(rate(nginx_vts_server_requests_total[2m])) by(kubernetes_pod_name)
  1. 自定義指標可以配置多個,因此,需要將規則使用陣列來配置

    rules:
    - {}
    
  2. 告訴Adapter,哪些自定義指標可以使用

    rules:
    - seriesQuery: 'nginx_vts_server_requests_total{host="*",code="total"}'
    
    

    seriesQuery是PromQL語句,和直接用nginx_vts_server_requests_total查詢到的結果一樣,凡是seriesQuery可以查詢到的指標,都可以用作自定義指標

  3. 告訴Adapter,指標中的標籤和k8s中的資源物件的關聯關係

    rules:
    - seriesQuery: 'nginx_vts_server_requests_total{host="*",code="total"}'
      resources:
        overrides:
          kubernetes_namespace: {resource: "namespace"}
          kubernetes_pod_name: {resource: "pod"}
    

    我們查詢到的可用指標格式為:

    nginx_vts_server_requests_total{code="1xx",host="*",instance="10.244.1.194:80",job="kubernetes-sd-endpoints",kubernetes_name="custom-metrics-demo",kubernetes_namespace="default",kubernetes_pod_name="custom-metrics-demo-95b5bc949-xpppl"}	
    

    由於HPA在呼叫Adapter介面的時候,告訴Adapter的是查詢哪個名稱空間下的哪個Pod的指標,因此,Adapter在去查詢的時候,需要做一層適配轉換(因為並不是每個prometheus查詢到的結果中都是叫做kubernetes_namespacekubernetes_pod_name

  4. 指定自定義的指標名稱,供HPA配置使用

    rules:
    - seriesQuery: 'nginx_vts_server_requests_total{host="*",code="total"}'
      resources:
        overrides:
          kubernetes_namespace: {resource: "namespace"}
          kubernetes_pod_name: {resource: "pods"}
      name:
        matches: "^(.*)_total"
        as: "${1}_per_second"
    

    因為Adapter轉換完之後的指標含義為:每秒鐘的請求數。因此提供指標名稱,該配置根據正則表示式做了匹配替換,轉換完後的指標名稱為:nginx_vts_server_requests_per_second,HPA規則中可以直接配置該名稱。

  5. 告訴Adapter如何獲取最終的自定義指標值

    rules:
    - seriesQuery: 'nginx_vts_server_requests_total{host="*",code="total"}'
      resources:
        overrides:
          kubernetes_namespace: {resource: "namespace"}
          kubernetes_pod_name: {resource: "pod"}
      name:
        matches: "^(.*)_total"
        as: "${1}_per_second"
      metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
    

    我們最終期望的寫法可能是這樣:

    sum(rate(nginx_vts_server_requests_total{host="*",code="total",kubernetes_namespace="default"}[2m])) by (kubernetes_pod_name)
    

    但是Adapter提供了更簡單的寫法:

    sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)
    
    • Series: 指標名稱
    • LabelMatchers: 指標查詢的label
    • GroupBy: 結果分組,針對HPA過來的查詢,都會匹配成kubernetes_pod_name

更新Adapter的配置:

$ vi custom-metrics-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: adapter-config
  namespace: monitor
data:
  config.yaml: |
    rules:
    - seriesQuery: 'nginx_vts_server_requests_total{host="*",code="total"}'
      seriesFilters: []
      resources:
        overrides:
          kubernetes_namespace: {resource: "namespace"}
          kubernetes_pod_name: {resource: "pod"}
      name:
        matches: "^(.*)_total"
        as: "${1}_per_second"
      metricsQuery: (sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>))

需要更新configmap並重啟adapter服務:

$ kubectl apply -f custom-metrics-configmap.yaml

$ kubectl -n monitor delete po custom-metrics-apiserver-c689ff947-zp8gq

再次檢視可用的指標資料:

$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 |jq
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "custom.metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "namespaces/nginx_vts_server_requests_per_second",
      "singularName": "",
      "namespaced": false,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]
    },
    {
      "name": "pods/nginx_vts_server_requests_per_second",
      "singularName": "",
      "namespaced": true,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]
    }
  ]
}

我們發現有兩個可用的resources,引用官方的一段解釋:

Notice that we get an entry for both "pods" and "namespaces" -- the adapter exposes the metric on each resource that we've associated the metric with (and all namespaced resources must be associated with a namespace), and will fill in the <<.GroupBy>> section with the appropriate label depending on which we ask for.

We can now connect to $KUBERNETES/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/nginx_vts_server_requests_per_second, and we should see
$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/nginx_vts_server_requests_per_second" | jq 
{
  "kind": "MetricValueList",
  "apiVersion": "custom.metrics.k8s.io/v1beta1",
  "metadata": {
    "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/%2A/nginx_vts_server_requests_per_second"
  },
  "items": [
    {
      "describedObject": {
        "kind": "Pod",
        "namespace": "default",
        "name": "custom-metrics-demo-95b5bc949-xpppl",
        "apiVersion": "/v1"
      },
      "metricName": "nginx_vts_server_requests_per_second",
      "timestamp": "2020-08-02T04:07:06Z",
      "value": "133m",
      "selector": null
    }
  ]
}

其中133m等於0.133,即當前指標查詢每秒鐘請求數為0.133次

https://github.com/DirectXMan12/k8s-prometheus-adapter/blob/master/docs/config-walkthrough.md

https://github.com/DirectXMan12/k8s-prometheus-adapter/blob/master/docs/config.md

配置HPA實現自定義指標的業務伸縮
$ cat hpa-custom-metrics.yaml
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-custom-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: custom-metrics-demo
  minReplicas: 1
  maxReplicas: 3
  metrics:
  - type: Pods
    pods:
      metricName: nginx_vts_server_requests_per_second
      targetAverageValue: 10

$ kubectl create -f hpa-custom-metrics.yaml

$ kubectl get hpa

注意metricName為自定義的指標名稱。

使用ab命令壓測custom-metrics-demo服務,觀察hpa的變化:

$ kubectl get svc -o wide
custom-metrics-demo   ClusterIP   10.104.110.245   <none>        80/TCP    16h

$ ab -n1000 -c 5 http://10.104.110.245:80/

觀察hpa變化:

$ kubectl describe hpa nginx-custom-hpa

檢視adapter日誌:

$ kubectl -n monitor logs --tail=100 -f custom-metrics-apiserver-c689ff947-m5vlr
...
I0802 04:43:58.404559       1 httplog.go:90] GET /apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/%2A/nginx_vts_server_requests_per_second?labelSelector=app%3Dcustom-metrics-demo: (20.713209ms) 200 [kube-controller-manager/v1.16.0 (linux/amd64) kubernetes/2bd9643/system:serviceaccount:kube-system:horizontal-pod-autoscaler 192.168.136.10:60626]

prometheus-operator

追加:Adapter查詢資料和直接查詢Prometheus資料不一致(相差4倍)的問題。

$ vi custom-metrics-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: adapter-config
  namespace: monitor
data:
  config.yaml: |
    rules:
    - seriesQuery: 'nginx_vts_server_requests_total{host="*",code="total"}'
      seriesFilters: []
      resources:
        overrides:
          kubernetes_namespace: {resource: "namespace"}
          kubernetes_pod_name: {resource: "pod"}
      name:
        matches: "^(.*)_total"
        as: "${1}_per_second"
      metricsQuery: (sum(rate(<<.Series>>{<<.LabelMatchers>>,host="*",code="total"}[1m])) by (<<.GroupBy>>))

查詢驗證:

$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/nginx_vts_server_requests_per_second" | jq 
{
  "kind": "MetricValueList",
  "apiVersion": "custom.metrics.k8s.io/v1beta1",
  "metadata": {
    "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/%2A/nginx_vts_server_requests_per_second"
  },
  "items": [
    {
      "describedObject": {
        "kind": "Pod",
        "namespace": "default",
        "name": "custom-metrics-demo-95b5bc949-xpppl",
        "apiVersion": "/v1"
      },
      "metricName": "nginx_vts_server_requests_per_second",
      "timestamp": "2020-08-02T04:07:06Z",
      "value": "133m",
      "selector": null
    }
  ]
}