1. 程式人生 > 其它 >Kubernetes監控-Prometheus

Kubernetes監控-Prometheus

參考原文連結:https://www.qikqiak.com/k8strain/monitor/prometheus/

Prometheus

簡介

Prometheus 最初是 SoundCloud 構建的開源系統監控和報警工具,是一個獨立的開源專案,於2016年加入了 CNCF 基金會,作為繼 Kubernetes 之後的第二個託管專案。Prometheus 相比於其他傳統監控工具主要有以下幾個特點:

  • 具有由 metric 名稱和鍵/值對標識的時間序列資料的多維資料模型
  • 有一個靈活的查詢語言
  • 不依賴分散式儲存,只和本地磁碟有關
  • 通過 HTTP 的服務拉取時間序列資料
  • 也支援推送的方式來新增時間序列資料
  • 還支援通過服務發現或靜態配置發現目標
  • 多種圖形和儀表板支援

Prometheus 由多個元件組成,但是其中有些元件是可選的:

  • Prometheus Server:用於抓取指標、儲存時間序列資料
  • exporter:暴露指標讓任務來抓
  • pushgateway:push 的方式將指標資料推送到該閘道器
  • alertmanager:處理報警的報警元件 adhoc:用於資料查詢

大多數 Prometheus 元件都是用 Go 編寫的,因此很容易構建和部署為靜態的二進位制檔案。下圖是 Prometheus 官方提供的架構及其一些相關的生態系統元件:

整體流程比較簡單,Prometheus 直接接收或者通過中間的 Pushgateway 閘道器被動獲取指標資料,在本地儲存所有的獲取的指標資料,並對這些資料進行一些規則整理,用來生成一些聚合資料或者報警資訊,Grafana 或者其他工具用來視覺化這些資料。

安裝

由於 Prometheus 是 Golang 編寫的程式,所以要安裝的話也非常簡單,只需要將二進位制檔案下載下來直接執行即可,前往地址:https://prometheus.io/download 下載最新版本即可。

Prometheus 是通過一個 YAML 配置檔案來進行啟動的,如果我們使用二進位制的方式來啟動的話,可以使用下面的命令:

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

其中 prometheus.yml 檔案的基本配置如下:

global:
  scrape_interval:     15s
  evaluation_interval: 15s

rule_files:
  # - "first.rules"
  # - "second.rules"

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['localhost:9090']

上面這個配置檔案中包含了3個模組:globalrule_filesscrape_configs

  • global 模組控制 Prometheus Server 的全域性配置:
    • scrape_interval:表示 prometheus 抓取指標資料的頻率,預設是15s,我們可以覆蓋這個值
    • evaluation_interval:用來控制評估規則的頻率,prometheus 使用規則產生新的時間序列資料或者產生警報
  • rule_files:指定了報警規則所在的位置,prometheus 可以根據這個配置載入規則,用於生成新的時間序列資料或者報警資訊,當前我們沒有配置任何報警規則。
  • scrape_configs 用於控制 prometheus 監控哪些資源。

由於 prometheus 通過 HTTP 的方式來暴露的它本身的監控資料,prometheus 也能夠監控本身的健康情況。在預設的配置裡有一個單獨的 job,叫做 prometheus,它採集 prometheus 服務本身的時間序列資料。這個 job 包含了一個單獨的、靜態配置的目標:監聽 localhost 上的 9090 埠。prometheus 預設會通過目標的 /metrics 路徑採集 metrics。所以,預設的 job 通過 URL:http://localhost:9090/metrics 採集 metrics。收集到的時間序列包含 prometheus 服務本身的狀態和效能。

如果我們還有其他的資源需要監控的話,直接配置在 scrape_configs 模組下面就可以了。

示例應用

比如我們在本地啟動一些樣例來讓 Prometheus 採集。Go 客戶端庫包含一個示例,該示例為具有不同延遲分佈的三個服務暴露 RPC 延遲。

首先確保已經安裝了 Go 環境並啟用 go modules,下載 Prometheus 的 Go 客戶端庫並執行這三個示例:

$ git clone https://github.com/prometheus/client_golang.git
$ cd client_golang/examples/random
$ export GO111MODULE=on   
$ export GOPROXY=https://goproxy.cn
$ go build

然後在3個獨立的終端裡面執行3個服務:

$ ./random -listen-address=:8080
$ ./random -listen-address=:8081
$ ./random -listen-address=:8082

這個時候我們可以得到3個不同的監控介面:http://localhost:8080/metrics、http://localhost:8081/metricshttp://localhost:8082/metrics。

現在我們配置 Prometheus 來採集這些新的目標,讓我們將這三個目標分組到一個名為 example-random 的任務。假設前兩個端點(即:http://localhost:8080/metrics、http://localhost:8081/metrics )都是生產級目標應用,第三個端點(即:http://localhost:8082/metrics )為金絲雀例項。

要在 Prometheus 中對此進行建模,我們可以將多組端點新增到單個任務中,為每組目標新增額外的標籤。

在此示例中,我們將 group =“production” 標籤新增到第一組目標,同時將 group=“canary”新增到第二組。將以下配置新增到 prometheus.yml 中的 scrape_configs 部分,然後重新啟動 Prometheus 例項:

scrape_configs:
  - job_name: 'example-random'
    scrape_interval: 5s
    static_configs:
      - targets: ['localhost:8080', 'localhost:8081']
        labels:
          group: 'production'
      - targets: ['localhost:8082']
        labels:
          group: 'canary'

這就是 Prometheus 新增監控配置最基本的配置方式了,非常簡單,只需要提供一個符合 metrics 格式的可訪問的介面配置給 Prometheus 就可以了。

但是由於我們這裡是要執行在 Kubernetes 系統中,所以我們直接用 Docker 映象的方式執行。

為了能夠方便的管理配置檔案,我們這裡將 prometheus.yml 檔案用 ConfigMap 的形式進行管理:

apiVersion: v1
kind: Namespace
metadata:
  name: kube-mon
  
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: kube-mon
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      scrape_timeout: 15s
    scrape_configs:
    - job_name: 'prometheus'
      static_configs:
      - targets: ['localhost:9090']

我們這裡暫時只配置了對 prometheus 本身的監控,直接建立該資源物件,配置檔案建立完成了,以後如果我們有新的資源需要被監控,我們只需要將上面的 ConfigMap 物件更新即可。

由於 prometheus 可以訪問 Kubernetes 的一些資源物件,所以需要配置 rbac 相關認證,這裡我們使用了一個名為 prometheus 的 serviceAccount 物件(prometheus-rbac.yaml):

apiVersion: v1
kind: ServiceAccount
metadata:
  name: prometheus
  namespace: kube-mon
---
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: kube-mon

由於我們要獲取的資源資訊,在每一個 namespace 下面都有可能存在,所以我們這裡使用的是 ClusterRole 的資源物件,值得一提的是我們這裡的許可權規則宣告中有一個 nonResourceURLs 的屬性,是用來對非資源型 metrics 進行操作的許可權宣告,這個在以前我們很少遇到過,然後直接建立上面的資源物件即可:

$ kubectl apply -f prometheus-rbac.yaml 
serviceaccount/prometheus created
clusterrole.rbac.authorization.k8s.io/prometheus created
clusterrolebinding.rbac.authorization.k8s.io/prometheus created

為了防止Pod漂移,這裡準備把Pod固定在一個具有monitor=prometheus標籤的節點上,這裡我們為node1打上標籤:

$ kubectl label node node1 monitor=prometheus

現在我們來建立 prometheus 的 Pod 資源:(prometheus-deploy.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: kube-mon
  labels:
    app: prometheus
spec:
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      serviceAccountName: prometheus
      nodeSelector:
        monitor: prometheus
      containers:
      - image: prom/prometheus:v2.14.0
        name: prometheus
        args:
        - "--config.file=/etc/prometheus/prometheus.yml"
        - "--storage.tsdb.path=/prometheus"  # 指定tsdb資料路徑
        - "--storage.tsdb.retention.time=24h"
        - "--web.enable-admin-api"  # 控制對admin HTTP API的訪問,其中包括刪除時間序列等功能
        - "--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

另外為了 prometheus 的效能和資料持久化我們這裡是直接將通過 hostPath 的方式來進行資料持久化的,通過 --storage.tsdb.path=/prometheus 指定資料目錄,然後將該目錄宣告掛載到 /data/prometheus 這個主機目錄下面。

現在我們就可以新增 promethues 的資源物件了:

$ kubectl apply -f prometheus-deploy.yaml 
deployment.apps/prometheus created

$ kubectl get pod -n kube-mon
NAME                          READY   STATUS              RESTARTS   AGE
prometheus-7bb94b64f7-dkl6c   0/1     ContainerCreating   0          13s

$ kubectl logs -f  prometheus-7bb94b64f7-dkl6c -n kube-mon
level=info ts=2022-01-15T07:29:46.985Z caller=main.go:332 msg="Starting Prometheus" version="(version=2.14.0, branch=HEAD, revision=edeb7a44cbf745f1d8be4ea6f215e79e651bfe19)"
level=info ts=2022-01-15T07:29:46.985Z caller=main.go:333 build_context="(go=go1.13.4, user=root@df2327081015, date=20191111-14:27:12)"
level=info ts=2022-01-15T07:29:46.985Z caller=main.go:334 host_details="(Linux 5.15.5-2.el7.x86_64 #1 SMP Tue Nov 30 07:09:55 UTC 2021 x86_64 prometheus-7bb94b64f7-dkl6c (none))"
level=info ts=2022-01-15T07:29:46.985Z caller=main.go:335 fd_limits="(soft=1048576, hard=1048576)"
level=info ts=2022-01-15T07:29:46.985Z caller=main.go:336 vm_limits="(soft=unlimited, hard=unlimited)"
level=error ts=2022-01-15T07:29:46.985Z caller=query_logger.go:85 component=activeQueryTracker msg="Error opening query log file" file=/prometheus/queries.active err="open /prometheus/queries.active: permission denied"
panic: Unable to create mmap-ed active query log

goroutine 1 [running]:
github.com/prometheus/prometheus/promql.NewActiveQueryTracker(0x7ffce1d23d89, 0xb, 0x14, 0x2b4f400, 0xc000113470, 0x2b4f400)
	/app/promql/query_logger.go:115 +0x48c
main.main()
	/app/cmd/prometheus/main.go:364 +0x5229

建立 Pod 後,我們可以看到並沒有成功執行,出現了 open /prometheus/queries.active: permission denied 這樣的錯誤資訊,這是因為我們的 prometheus 的映象中是使用的 nobody 這個使用者,然後現在我們通過 hostPath 掛載到宿主機上面的目錄的 ownership 卻是 root

$ ls -la
total 0
drwxr-xr-x   3 root root  24 Jan 15 15:35 .
dr-xr-xr-x. 18 root root 256 Jan 15 15:33 ..
drwxr-xr-x   2 root root   6 Jan 15 15:35 prometheus

以當然會出現操作許可權問題了,這個時候我們就可以通過 securityContext 來為 Pod 設定下 volumes 的許可權,通過設定 runAsUser=0 指定執行的使用者為 root:

......
securityContext:
  runAsUser: 0
volumes:
- name: data
  hostPath:
    path: /data/prometheus/
- configMap:
    name: prometheus-config
  name: config-volume

這時候重新更新下prometheus:

$ kubectl get pod -n kube-mon
NAME                          READY   STATUS    RESTARTS   AGE
prometheus-56ff59cf74-vdngs   1/1     Running   0          12s

$ kubectl logs prometheus-56ff59cf74-vdngs -n kube-mon
level=info ts=2022-01-15T07:37:50.588Z caller=main.go:332 msg="Starting Prometheus" version="(version=2.14.0, branch=HEAD, revision=edeb7a44cbf745f1d8be4ea6f215e79e651bfe19)"
level=info ts=2022-01-15T07:37:50.588Z caller=main.go:333 build_context="(go=go1.13.4, user=root@df2327081015, date=20191111-14:27:12)"
level=info ts=2022-01-15T07:37:50.588Z caller=main.go:334 host_details="(Linux 5.15.5-2.el7.x86_64 #1 SMP Tue Nov 30 07:09:55 UTC 2021 x86_64 prometheus-56ff59cf74-vdngs (none))"
level=info ts=2022-01-15T07:37:50.588Z caller=main.go:335 fd_limits="(soft=1048576, hard=1048576)"
level=info ts=2022-01-15T07:37:50.588Z caller=main.go:336 vm_limits="(soft=unlimited, hard=unlimited)"
level=info ts=2022-01-15T07:37:50.682Z caller=main.go:657 msg="Starting TSDB ..."
level=info ts=2022-01-15T07:37:50.682Z caller=web.go:496 component=web msg="Start listening for connections" address=0.0.0.0:9090
level=info ts=2022-01-15T07:37:50.784Z caller=head.go:535 component=tsdb msg="replaying WAL, this may take awhile"
level=info ts=2022-01-15T07:37:50.785Z caller=head.go:583 component=tsdb msg="WAL segment loaded" segment=0 maxSegment=0
level=info ts=2022-01-15T07:37:50.786Z caller=main.go:672 fs_type=XFS_SUPER_MAGIC
level=info ts=2022-01-15T07:37:50.786Z caller=main.go:673 msg="TSDB started"
level=info ts=2022-01-15T07:37:50.786Z caller=main.go:743 msg="Loading configuration file" filename=/etc/prometheus/prometheus.yml
level=info ts=2022-01-15T07:37:50.787Z caller=main.go:771 msg="Completed loading of configuration file" filename=/etc/prometheus/prometheus.yml
level=info ts=2022-01-15T07:37:50.787Z caller=main.go:626 msg="Server is ready to receive web requests."

Pod 建立成功後,為了能夠在外部訪問到 prometheus 的 webui 服務,我們還需要建立一個 Service 物件:(prometheus-svc.yaml)

apiVersion: v1
kind: Service
metadata:
  name: prometheus
  namespace: kube-mon
  labels:
    app: prometheus
spec:
  selector:
    app: prometheus
  type: NodePort
  ports:
    - name: web
      port: 9090
      targetPort: http

當然這時候我們也可以建立一個Ingress物件,通過域名進行訪問:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: prometheus
  namespace: kube-mon
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
    - host: k8s.negan.com  # 將域名對映到 prometheus 服務
      http:
        paths:
          - path: /
            backend:
              serviceName: prometheus  
              servicePort: 9090     

現在我們就可以通過IP:port的方式或者通過域名的方式直接訪問我們的prometheus 的webui服務了:

$ kubectl get svc,ingress -n kube-mon
NAME                 TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
service/prometheus   NodePort   10.101.4.94   <none>        9090:30050/TCP   8m49s

NAME                            CLASS    HOSTS           ADDRESS   PORTS   AGE
ingress.extensions/prometheus   <none>   k8s.negan.com             80      5m25s

現在我們可以檢視當前監控系統中的一些監控目標(Status -> Targets):

由於我們現在還沒有配置任何的報警資訊,所以 Alerts 選單下面現在沒有任何資料,隔一會兒,我們可以去 Graph 選單下面檢視我們抓取的 prometheus 本身的一些監控資料了,其中- insert metrics at cursor -下面就有我們蒐集到的一些監控指標資料:

比如我們這裡就選擇 scrape_duration_seconds 這個指標,然後點選 Execute,就可以看到類似於下面的圖表資料了:

除了簡單的直接使用採集到的一些監控指標資料之外,這個時候也可以使用強大的 PromQL 工具,PromQL 其實就是 prometheus 便於資料聚合展示開發的一套 ad hoc 查詢語言的,你想要查什麼找對應函式取你的資料好了。

應用監控

Prometheus 的資料指標是通過一個公開的 HTTP(S) 資料介面獲取到的,我們不需要單獨安裝監控的 agent,只需要暴露一個 metrics 介面,Prometheus 就會定期去拉取資料;對於一些普通的 HTTP 服務,我們完全可以直接複用這個服務,新增一個 /metrics 介面暴露給 Prometheus;而且獲取到的指標資料格式是非常易懂的,不需要太高的學習成本。

現在很多服務從一開始就內建了一個 /metrics 介面,比如 Kubernetes 的各個元件、istio 服務網格都直接提供了資料指標介面。有一些服務即使沒有原生整合該介面,也完全可以使用一些 exporter 來獲取到指標資料,比如 mysqld_exporternode_exporter,這些 exporter 就有點類似於傳統監控服務中的 agent,作為服務一直存在,用來收集目標服務的指標資料然後直接暴露給 Prometheus。

普通應用

對於普通應用只需要能夠提供一個滿足 prometheus 格式要求的 /metrics 介面就可以讓 Prometheus 來接管監控,比如 Kubernetes 叢集中非常重要的 CoreDNS 外掛,一般預設情況下就開啟了 /metrics 介面:

$ kubectl get cm coredns -n kube-system -o yaml
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2022-01-13T02:57:11Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "197"
  selfLink: /api/v1/namespaces/kube-system/configmaps/coredns
  uid: 18f3bc25-9f66-4faa-b4f4-1e9104fa49ca

上面 ConfigMap 中 prometheus :9153 就是開啟 prometheus 的外掛:

$ kubectl get pods -n kube-system -l k8s-app=kube-dns -o wide
NAME                       READY   STATUS    RESTARTS   AGE    IP            NODE    NOMINATED NODE   READINESS GATES
coredns-7ff77c879f-2wlp9   1/1     Running   2          2d5h   10.244.1.15   node1   <none>           <none>
coredns-7ff77c879f-bpvjp   1/1     Running   2          2d5h   10.244.1.13   node1   <none>           <none>

我們可以先嚐試手動訪問下 /metrics 介面,如果能夠手動訪問到那證明介面是沒有任何問題的:

$ curl http://10.244.1.15:9153/metrics
# HELP coredns_build_info A metric with a constant '1' value labeled by version, revision, and goversion from which CoreDNS was built.
# TYPE coredns_build_info gauge
coredns_build_info{goversion="go1.13.6",revision="da7f65b",version="1.6.7"} 1
# HELP coredns_cache_misses_total The count of cache misses.
# TYPE coredns_cache_misses_total counter
coredns_cache_misses_total{server="dns://:53"} 10
# HELP coredns_dns_request_count_total Counter of DNS requests made per zone, protocol and family.
# TYPE coredns_dns_request_count_total counter
coredns_dns_request_count_total{family="1",proto="udp",server="dns://:53",zone="."} 10
# HELP coredns_dns_request_duration_seconds Histogram of the time (in seconds) each request took.
......

我們可以看到可以正常訪問到,name CoreDNS 的監控資料介面是正常的了,我們就可以將這個 /metrics 介面配置到 prometheus.yml 中去了,直接加到預設的 prometheus 這個 job 下面:(prometheus-cm.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: kube-mon
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.244.1.15:9153","10.244.1.13:9153"]

當然,我們這裡只是一個很簡單的配置,scrape_configs 下面可以支援很多引數,例如:

  • basic_authbearer_token:比如我們提供的 /metrics 介面需要 basic 認證的時候,通過傳統的使用者名稱/密碼或者在請求的 header 中新增對應的 token 都可以支援
  • kubernetes_sd_configsconsul_sd_configs:可以用來自動發現一些應用的監控資料

現在 Prometheus 的配置檔案內容已經更改了,隔一會兒被掛載到 Pod 中的 prometheus.yml 檔案也會更新,由於我們之前的 Prometheus 啟動引數中添加了 --web.enable-lifecycle 引數,所以現在我們只需要執行一個 reload 命令即可讓配置生效:

$ kubectl get pod -n kube-mon -o wide
NAME                          READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
prometheus-56ff59cf74-vdngs   1/1     Running   0          73m   10.244.1.18   node1   <none>           <none>

$ curl -X POST "http://10.244.1.18:9090/-/reload"

這個時候我們再去看 Prometheus 的 Dashboard 中檢視採集的目標資料:

可以看到我們剛剛新增的 coredns 這個任務已經出現了,然後同樣的我們可以切換到 Graph 下面去,我們可以找到一些 CoreDNS 的指標資料,至於這些指標資料代表什麼意義,一般情況下,我們可以去檢視對應的 /metrics 介面,裡面一般情況下都會有對應的註釋。

到這裡我們就在 Prometheus 上配置了第一個 Kubernetes 應用。

使用exporter監控

有一些應用可能沒有自帶 /metrics 介面供 Prometheus 使用,在這種情況下,我們就需要利用 exporter 服務來為 Prometheus 提供指標資料了。Prometheus 官方為許多應用就提供了對應的 exporter 應用,也有許多第三方的實現,我們可以前往官方網站進行檢視:exporters,當然如果你的應用本身也沒有 exporter 實現,那麼就要我們自己想辦法去實現一個 /metrics 介面了,只要你能提供一個合法的 /metrics 介面,Prometheus 就可以監控你的應用。

比如我們這裡通過一個 redis-exporter 的服務來監控 redis 服務,對於這類應用,我們一般會以 sidecar 的形式和主應用部署在同一個 Pod 中,比如我們這裡來部署一個 redis 應用,並用 redis-exporter 的方式來採集監控資料供 Prometheus 使用,如下資源清單檔案:(prometheus-redis.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  namespace: kube-mon
spec:
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9121"
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:4
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 6379
      - name: redis-exporter
        image: oliver006/redis_exporter:latest
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 9121
---
kind: Service
apiVersion: v1
metadata:
  name: redis
  namespace: kube-mon
spec:
  selector:
    app: redis
  ports:
  - name: redis
    port: 6379
    targetPort: 6379
  - name: prom
    port: 9121
    targetPort: 9121

可以看到上面我們在 redis 這個 Pod 中包含了兩個容器,一個就是 redis 本身的主應用,另外一個容器就是 redis_exporter。建立完成之後,我們就可以看到redis的Pod包含兩個容器:

$ kubectl get pod -n kube-mon
NAME                          READY   STATUS    RESTARTS   AGE
prometheus-56ff59cf74-vdngs   1/1     Running   0          8h
redis-7479d9b867-9pqtx        2/2     Running   0          4m53s

$ kubectl get svc -n kube-mon
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
prometheus   NodePort    10.101.4.94     <none>        9090:30050/TCP      7h29m
redis        ClusterIP   10.104.17.136   <none>        6379/TCP,9121/TCP   5m21s

我們可以通過 9121 埠來校驗是否能夠採集到資料:

$ curl 10.104.17.136:9121/metrics
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
go_gc_duration_seconds{quantile="0.75"} 0
go_gc_duration_seconds{quantile="1"} 0
go_gc_duration_seconds_sum 0
go_gc_duration_seconds_count 0
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
.....

同樣的,現在我們只需要更新 Prometheus 的配置檔案:

- job_name: 'redis'
  static_configs:
  - targets: ['redis:9121']

由於我們這裡是通過 Service 去配置的 redis 服務,這裡直接使用servicename,直接配置 Pod IP 也是可以的,因為和 Prometheus 處於同一個 namespace。

配置檔案更後重新載入:

$ curl -X POST "http://10.244.1.18:9090/-/reload"

這個時候我們再去看 Prometheus 的 Dashboard 中檢視採集的目標資料:

可以看到配置的 redis 這個 job 已經生效了。切換到 Graph 下面可以看到很多關於 redis 的指標資料,我們選擇任意一個指標,比如 redis_exporter_scrapes_total,然後點選執行就可以看到對應的資料圖表了:

叢集節點

我們已經知道怎樣用 Promethues 來監控 Kubernetes 叢集中的應用,但是對於 Kubernetes 叢集本身的監控也是非常重要的,我們需要時時刻刻了解叢集的執行狀態。

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

​ 1、Kubernetes 節點的監控:比如節點的 cpu、load、disk、memory 等指標

​ 2、內部系統元件的狀態:比如 kube-scheduler、kube-controller-manager、kubedns/coredns 等元件的詳細執行狀態

​ 3、編排級的 metrics:比如 Deployment 的狀態、資源請求、排程和 API 延遲等資料指標

Kubernetes 叢集的監控方案目前主要有以下幾種方案:

  • Heapster:Heapster 是一個叢集範圍的監控和資料聚合工具,以 Pod 的形式執行在叢集中。 heapster 除了 Kubelet/cAdvisor 之外,我們還可以向 Heapster 新增其他指標源資料,比如 kube-state-metrics,需要注意的是 Heapster 已經被廢棄了,後續版本中會使用 metrics-server 代替。
  • cAdvisor:cAdvisor 是 Google 開源的容器資源監控和效能分析工具,它是專門為容器而生,本身也支援 Docker 容器。
  • kube-state-metrics:kube-state-metrics 通過監聽 API Server 生成有關資源物件的狀態指標,比如 Deployment、Node、Pod,需要注意的是 kube-state-metrics 只是簡單提供一個 metrics 資料,並不會儲存這些指標資料,所以我們可以使用 Prometheus 來抓取這些資料然後儲存。
  • metrics-server:metrics-server 也是一個叢集範圍內的資源資料聚合工具,是 Heapster 的替代品,同樣的,metrics-server 也只是顯示資料,並不提供資料儲存服務。

不過 kube-state-metrics 和 metrics-server 之間還是有很大不同的,二者的主要區別如下:

1、kube-state-metrics 主要關注的是業務相關的一些元資料,比如 Deployment、Pod、副本狀態等

​ 2、metrics-server 主要關注的是資源度量 API 的實現,比如 CPU、檔案描述符、記憶體、請求延時等指標。

監控叢集節點

要監控節點其實已經有很多非常成熟的方案了,比如 Nagios、zabbix,甚至我們自己來收集資料也可以,我們這裡通過 Prometheus 來採集節點的監控指標資料,可以通過 node_exporter 來獲取,顧名思義,node_exporter 就是抓取用於採集伺服器節點的各種執行指標,目前 node_exporter 支援幾乎所有常見的監控點,比如 conntrack,cpu,diskstats,filesystem,loadavg,meminfo,netstat 等,詳細的監控點列表可以參考其 Github 倉庫

我們可以通過 DaemonSet 控制器來部署該服務,這樣每一個節點都會自動執行一個這樣的 Pod,如果我們從叢集中刪除或者新增節點後,也會進行自動擴充套件。

在部署 node-exporter 的時候有一些細節需要注意,如下資源清單檔案:(prome-node-exporter.yaml)

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: kube-mon
  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
        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 是執行在容器中的,所以我們在 Pod 中需要配置一些 Pod 的安全策略,這裡我們就添加了 hostPID: truehostIPC: truehostNetwork: true 3個策略,用來使用主機的 PID namespaceIPC namespace 以及主機網路,這些 namespace 就是用於容器隔離的關鍵技術,要注意這裡的 namespace 和叢集中的 namespace 是兩個完全不相同的概念。

另外我們還將主機的 /dev/proc/sys這些目錄掛載到容器中,這些因為我們採集的很多節點資料都是通過這些資料夾下面的檔案來獲取到的,比如我們在使用 top 命令可以檢視當前 cpu 使用情況,資料就來源於檔案 /proc/stat,使用 free 命令可以檢視當前記憶體使用情況,其資料來源是來自 /proc/meminfo 檔案。

另外由於我們叢集使用的是 kubeadm 搭建的,所以如果希望 master 節點也一起被監控,則需要新增相應的容忍,然後直接建立上面的資源物件:

$ kubectl get pods -n kube-mon -l app=node-exporter -o wide
NAME                  READY   STATUS    RESTARTS   AGE     IP               NODE     NOMINATED NODE   READINESS GATES
node-exporter-d55w6   1/1     Running   0          2m12s   192.168.47.135   node2    <none>           <none>
node-exporter-j892n   1/1     Running   0          2m12s   192.168.47.133   master   <none>           <none>
node-exporter-nwjvr   1/1     Running   0          2m12s   192.168.47.134   node1    <none>           <none>

部署完成後,我們可以看到在3個節點上都運行了一個 Pod,由於我們指定了 hostNetwork=true,所以在每個節點上就會繫結一個埠 9100,我們可以通過這個埠去獲取到監控指標資料:

$ curl 192.168.47.133:9100/metrics

當然如果你覺得上面的手動安裝方式比較麻煩,我們也可以使用 Helm 的方式來安裝:

$ helm upgrade --install node-exporter --namespace kube-mon stable/prometheus-node-exporter

服務發現

由於我們這裡每個節點上面都運行了 node-exporter 程式,如果我們通過一個 Service 來將資料收集到一起用靜態配置的方式配置到 Prometheus 去中,就只會顯示一條資料,我們得自己在指標資料中去過濾每個節點的資料,當然我們也可以手動的把所有節點用靜態的方式配置到 Prometheus 中去,但是以後要新增或者去掉節點的時候就還得手動去配置,那麼有沒有一種方式可以讓 Prometheus 去自動發現我們節點的 node-exporter 程式,並且按節點進行分組呢?這就是 Prometheus 裡面非常重要的服務發現功能了。

在 Kubernetes 下,Promethues 通過與 Kubernetes API 整合,主要支援5中服務發現模式,分別是:NodeServicePodEndpointsIngress

我們通過 kubectl 命令可以很方便的獲取到當前叢集中的所有節點資訊:

$ kubectl get node
NAME     STATUS   ROLES    AGE     VERSION
master   Ready    master   2d13h   v1.18.0
node1    Ready    <none>   2d13h   v1.18.0
node2    Ready    <none>   9h      v1.18.0

但是要讓 Prometheus 也能夠獲取到當前叢集中的所有節點資訊的話,我們就需要利用 Node 的服務發現模式,同樣的,在 prometheus.yml 檔案中配置如下的 job 任務即可:

- job_name: 'kubernetes-nodes'
  kubernetes_sd_configs:
  - role: node

通過指定 kubernetes_sd_configs 的模式為node,Prometheus 就會自動從 Kubernetes 中發現所有的 node 節點並作為當前 job 監控的目標例項,發現的節點 /metrics 介面是預設的 kubelet 的 HTTP 介面。

Prometheus 的 ConfigMap 更新完成後,同樣的我們執行 reload 操作,讓配置生效:

$ curl -X POST "http://10.244.1.18:9090/-/reload"

配置生效後,我們再去 prometheus 的 dashboard 中檢視 Targets 是否能夠正常抓取資料。

我們可以看到上面的 kubernetes-nodes 這個 job 任務已經自動發現了我們3個 node 節點,但是在獲取資料的時候失敗了,出現了類似於下面的錯誤資訊:

server returned HTTP status 400 Bad Request

這個是因為 prometheus 去發現 Node 模式的服務的時候,訪問的埠預設是10250,而預設是需要認證的 https 協議才有權訪問的,但實際上我們並不是希望讓去訪問10250埠的 /metrics 介面,而是 node-exporter 繫結到節點的 9100 埠,所以我們應該將這裡的 10250 替換成 9100,但是應該怎樣替換呢?

這裡我們就需要使用到 Prometheus 提供的 relabel_configs 中的 replace 能力了,relabel 可以在 Prometheus 採集資料之前,通過 Target 例項的 Metadata 資訊,動態重新寫入 Label 的值。

除此之外,我們還能根據 Target 例項的 Metadata 資訊選擇是否採集或者忽略該 Target 例項。比如我們這裡就可以去匹配 __address__ 這個 Label 標籤,然後替換掉其中的埠,如果你不知道有哪些 Label 標籤可以操作的話,可以將滑鼠移動到 Targets 的標籤區域,其中顯示的 Before relabeling 區域都是我們可以操作的標籤:

現在我們來替換掉埠,修改 ConfigMap:

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

這裡就是一個正則表示式,去匹配 __address__ 這個標籤,然後將 host 部分保留下來,port 替換成了 9100,現在我們重新更新配置檔案,執行 reload 操作,然後再去看 Prometheus 的 Dashboard 的 Targets 路徑下面 kubernetes-nodes 這個 job 任務是否正常了:

我們可以看到現在已經正常了,但是還有一個問題就是我們採集的指標資料 Label 標籤就只有一個節點的 hostname,這對於我們在進行監控分組分類查詢的時候帶來了很多不方便的地方,要是我們能夠將叢集中 Node 節點的 Label 標籤也能獲取到就很好了。這裡我們可以通過 labelmap 這個屬性來將 Kubernetes 的 Label 標籤新增為 Prometheus 的指標資料的標籤:

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

添加了一個 action 為 labelmap,正則表示式是 __meta_kubernetes_node_label_(.+) 的配置,這裡的意思就是表示式中匹配都的資料也新增到指標資料的 Label 標籤中去。

對於 kubernetes_sd_configs 下面可用的元資訊標籤如下:

  • __meta_kubernetes_node_name:節點物件的名稱
  • _meta_kubernetes_node_label:節點物件中的每個標籤
  • _meta_kubernetes_node_annotation:來自節點物件的每個註釋
  • _meta_kubernetes_node_address:每個節點地址型別的第一個地址(如果存在)

關於 kubernets_sd_configs 更多資訊可以檢視官方文件:kubernetes_sd_config

另外由於 kubelet 也自帶了一些監控指標資料,就上面我們提到的 10250 埠,所以我們這裡也把 kubelet 的監控任務也一併配置上:

- job_name: 'kubernetes-kubelet'
  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:
  - action: labelmap
    regex: __meta_kubernetes_node_label_(.+)

這裡需要特別注意的是這裡必須使用 https 協議訪問,這樣就必然需要提供證書,我們這裡是通過配置 insecure_skip_verify: true 來跳過了證書校驗,但是除此之外,要訪問叢集的資源,還必須要有對應的許可權才可以,也就是對應的 ServiceAccount 許可權允許才可以,這裡我們只需將 Pod 啟動後自動注入的 /var/run/secrets/kubernetes.io/serviceaccount/ca.crt/var/run/secrets/kubernetes.io/serviceaccount/token 檔案配置上,就可以獲取到對應的許可權了。

現在我們再去更新下配置檔案,執行 reload 操作,讓配置生效,然後訪問 Prometheus 的 Dashboard 檢視 Targets 路徑:

現在我們就可以切換到 Graph 路徑下面檢視採集的一些指標資料了,比如查詢 node_load1 指標:

我們可以看到將3個節點對應的 node_load1 指標資料都查詢出來了,同樣的,我們還可以使用 PromQL 語句來進行更復雜的一些聚合查詢操作,還可以根據我們的 Labels 標籤對指標資料進行聚合,比如我們這裡只查詢 mster 節點的資料,可以使用表示式 node_load1{instance="master"} 來進行查詢:

容器監控

cAdvisor已經內建在了 kubelet 元件之中,所以我們不需要單獨去安裝,cAdvisor 的資料路徑為 /api/v1/nodes/<node>/proxy/metrics,同樣我們這裡使用 node 的服務發現模式,因為每一個節點下面都有 kubelet,自然都有 cAdvisor 採集到的資料指標,配置如下:

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

上面的配置和我們之前配置 node-exporter 的時候幾乎是一樣的,區別是我們這裡使用了 https 的協議(和上面的kubelet一樣),通過ca.crttoken兩個檔案我們可以在 Pod 中訪問 apiserver,比如我們這裡的 __address__ 不再是 nodeip 了,而是 kubernetes 在叢集中的服務地址,然後加上__metrics_path__ 的訪問路徑 /api/v1/nodes/${1}/proxy/metrics/cadvisor,因為我們現在是通過 kubernetes 的 apiserver 地址去進行訪問的,現在同樣更新下配置,然後檢視 Targets 路徑:

我們可以切換到 Graph 路徑下面查詢容器相關資料,比如我們這裡來查詢叢集中所有 Pod 的 CPU 使用情況,kubelet 中的 cAdvisor 採集的指標和含義,可以檢視 Monitoring cAdvisor with Prometheus 說明,其中有一項:

container_cpu_usage_seconds_total   Counter     Cumulative cpu time consumed    seconds

container_cpu_usage_seconds_total 是容器累計使用的 CPU 時間,用它除以 CPU 的總時間,就可以得到容器的 CPU 使用率了:

首先計算容器的 CPU 佔用時間,由於節點上的 CPU 有多個,所以需要將容器在每個 CPU 上佔用的時間累加起來,Pod 在 1m 內累積使用的 CPU 時間為:(根據 pod 和 namespace 進行分組查詢)

sum(rate(container_cpu_usage_seconds_total{image!="",pod!=""}[1m])) by (namespace, pod)

然後計算 CPU 的總時間,這裡的 CPU 數量是容器分配到的 CPU 數量,container_spec_cpu_quota 是容器的 CPU 配額,它的值是容器指定的 CPU 個數 * 100000,所以 Pod 在 1s 內 CPU 的總時間為:Pod 的 CPU 核數 * 1s:

sum(container_spec_cpu_quota{image!="", pod!=""}) by(namespace, pod) / 100000

CPU配額

由於 container_spec_cpu_quota 是容器的 CPU 配額,所以只有配置了 resource-limit CPU 的 Pod 才可以獲得該指標資料。

將上面這兩個語句的結果相除,就得到了容器的 CPU 使用率:

(sum(rate(container_cpu_usage_seconds_total{image!="",pod!=""}[1m])) by (namespace, pod))
/
(sum(container_spec_cpu_quota{image!="", pod!=""}) by(namespace, pod) / 100000) * 100

在 promethues 裡面執行上面的 promQL 語句可以得到下面的結果:

監控 apiserver

apiserver 作為 Kubernetes 最核心的元件,當然它的監控也是非常有必要的,對於 apiserver 的監控我們可以直接通過 kubernetes 的 Service 來獲取:

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

上面這個 Service 就是我們叢集的 apiserver 在叢集內部的 Service 地址,要自動發現 Service 型別的服務,我們就需要用到 role 為 Endpoints 的 kubernetes_sd_configs,我們可以在 ConfigMap 物件中新增上一個 Endpoints 型別的服務的監控任務:

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

面這個任務是定義的一個型別為 endpoints 的 kubernetes_sd_configs ,新增到 Prometheus 的 ConfigMap 的配置檔案中,然後更新配置,更新完成後,我們再去檢視 Prometheus 的 Dashboard 的 target 頁面:

可以看到 kubernetes-apiservers 下面出現了很多例項,這是因為這裡使用的是 Endpoints 型別的服務發現,所以 Prometheus 會把所有的 Endpoints 服務都抓取過來了,同樣的,上面我們需要的服務名為 kubernetes 這個 apiserver 的服務也在這個列表之中,同樣可以使用relabel_configs這個配置,只是這裡不是使用 replace 這個動作了,而是 keep,就是隻把符合我們要求的給保留下來。

哪些才是符合我們要求的呢?同樣我們可以把滑鼠放置在任意一個 target 上,可以檢視到Before relabeling裡面所有的元資料,比如要過濾的服務是 default 這個 namespace 下面,服務名為 kubernetes 的元資料,所以這裡就可以根據對應的 __meta_kubernetes_namespace__meta_kubernetes_service_name 這兩個元資料來 relabel,另外由於 kubernetes 這個服務對應的埠是 443,需要使用 https 協議,所以這裡我們需要使用 https 的協議,對應的就需要將 ca 證書配置上,如下所示:

- job_name: 'kubernetes-apiservers'
  kubernetes_sd_configs:
  - role: endpoints
  scheme: https
  tls_config:
    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
  relabel_configs:
  - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
    action: keep
    regex: default;kubernetes;https

現在重新更新配置檔案、重新載入 Prometheus,切換到 Prometheus 的 Targets 路徑下檢視:

現在可以看到 kubernetes-apiserver 這個任務下面只有 apiserver 這一個例項了,證明我們的 relabel 是成功的。

監控Pod

上面的 apiserver 實際上就是一種特殊的 Endpoints,現在我們同樣來配置一個任務用來專門發現普通型別的 Endpoint,其實就是 Service 關聯的 Pod 列表:

- job_name: 'kubernetes-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_scheme]
    action: replace
    target_label: __scheme__
    regex: (https?)
  - 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
  - action: labelmap
    regex: __meta_kubernetes_service_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_configs 區域做了大量的配置,特別是第一個保留__meta_kubernetes_service_annotation_prometheus_io_scrape 為 true 的才保留下來,這就是說要想自動發現叢集中的 Endpoint,就需要在 Service 的 annotation 區域新增 prometheus.io/scrape=true 的宣告,現在我們先將上面的配置更新,檢視下效果:

我們可以看到 kubernetes-endpoints 這一個任務下面只發現了兩個服務,這是因為我們在 relabel_configs 中過濾了 annotationprometheus.io/scrape=true 的 Service,而現在我們系統中只有這樣一個 kube-dns 服務符合要求,該 Service 下面有兩個例項,所以出現了兩個例項:

$ kubectl get svc kube-dns -n kube-system -o yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/port: "9153"  # metrics介面的埠
    prometheus.io/scrape: "true"  # 可以讓prometheus自動發現
  creationTimestamp: "2022-01-13T02:57:12Z"
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: KubeDNS
......

現在我們在之前建立的 redis 這個 Service 中新增上 prometheus.io/scrape=true 這個 annotation,於 redis 服務的 metrics 介面在 9121 這個 redis-exporter 服務上面,所以我們還需要新增一個 prometheus.io/port=9121 這樣的 annotations:

apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: kube-mon
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "9121"
spec:
  selector:
    app: redis
  ports:
  - name: redis
    port: 6379
    targetPort: 6379
  - name: prom
    port: 9121
    targetPort: 9121

更新完成後,去 Prometheus 檢視 Targets 路徑,可以看到 redis 服務自動出現在了 kubernetes-endpoints 這個任務下面:

這樣以後我們有了新的服務,服務本身提供了 /metrics 介面,我們就完全不需要用靜態的方式去配置了,到這裡我們就可以將之前配置的 redis 的靜態配置去掉了。

PromQL

Prometheus 通過指標名稱(metrics name)以及對應的一組標籤(label)定義一條唯一時間序列。指標名稱反映了監控樣本的基本標識,而 label 則在這個基本特徵上為採集到的資料提供了多種特徵維度。使用者可以基於這些特徵維度過濾、聚合、統計從而產生新的計算後的一條時間序列。

PromQL 是 Prometheus 內建的資料查詢語言,其提供對時間序列資料豐富的查詢,聚合以及邏輯運算能力的支援。並且被廣泛應用在 Prometheus 的日常應用當中,包括對資料查詢、視覺化、告警處理。可以這麼說,PromQL 是 Prometheus 所有應用場景的基礎,理解和掌握 PromQL 是我們使用 Prometheus 必備的技能。

時間序列

前面我們通過 node-exporter 暴露的 metrics 服務,Prometheus 可以採集到當前主機所有監控指標的樣本資料。例如:

# 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"} 39835.07
# HELP node_load1 1m load average.
# TYPE node_load1 gauge
node_load1 0.44

其中非 # 開頭的每一行表示當前 node-exporter 採集到的一個監控樣本:node_cpu_seconds_totalnode_load1 表明了當前指標的名稱、大括號中的標籤則反映了當前樣本的一些特徵和維度、浮點數則是該監控樣本的具體值。

Prometheus 會將所有采集到的樣本資料以時間序列的方式儲存在記憶體資料庫中,並且定時儲存到硬碟上。時間序列是按照時間戳和值的序列順序存放的,我們稱之為向量(vector),每條時間序列通過指標名稱(metrics name)和一組標籤集(labelset)命名。如下所示,可以將時間序列理解為一個以時間為 X 軸的數字矩陣:

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

在時間序列中的每一個點稱為一個樣本(sample),樣本由以下三部分組成:

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

如下所示:

<--------------- metric ---------------------><-timestamp -><-value->
http_request_total{status="200", method="GET"}@1434417560938 => 94355
http_request_total{status="200", method="GET"}@1434417561287 => 94334

http_request_total{status="404", method="GET"}@1434417560938 => 38473
http_request_total{status="404", method="GET"}@1434417561287 => 38544

http_request_total{status="200", method="POST"}@1434417560938 => 4748
http_request_total{status="200", method="POST"}@1434417561287 => 4785

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

<metric name>{<label name> = <label value>, ...}
  • 指標的名稱(metric name)可以反映被監控樣本的含義(比如,http_request_total - 表示當前系統接收到的 HTTP 請求總量)。指標名稱只能由 ASCII 字元、數字、下劃線以及冒號組成並必須符合正則表示式[a-zA-Z_:][a-zA-Z0-9_:]*
  • 標籤(label)反映了當前樣本的特徵維度,通過這些維度 Prometheus 可以對樣本資料進行過濾,聚合等。標籤的名稱只能由 ASCII 字元、數字以及下劃線組成並滿足正則表示式 [a-zA-Z_][a-zA-Z0-9_]*

每個不同的 metric_namelabel 組合都稱為時間序列,在 Prometheus 的表示式語言中,表示式或子表示式包括以下四種類型之一:

  • 瞬時向量(Instant vector):一組時間序列,每個時間序列包含單個樣本,它們共享相同的時間戳。也就是說,表示式的返回值中只會包含該時間序列中的最新的一個樣本值。而相應的這樣的表示式稱之為瞬時向量表示式。
  • 區間向量(Range vector):一組時間序列,每個時間序列包含一段時間範圍內的樣本資料,這些是通過將時間選擇器附加到方括號中的瞬時向量(例如[5m]5分鐘)而生成的。
  • 標量(Scalar):一個簡單的數字浮點值。
  • 字串(String):一個簡單的字串值。

所有這些指標都是 Prometheus 定期從 metrics 介面那裡採集過來的。採集的間隔時間的設定由 prometheus.yaml 配置中的 scrape_interval 指定。最多抓取間隔為30秒,這意味著至少每30秒就會有一個帶有新時間戳記錄的新資料點,這個值可能會更改,也可能不會更改,但是每隔 scrape_interval 都會產生一個新的資料點。

指標型別

從儲存上來講所有的監控指標 metric 都是相同的,但是在不同的場景下這些 metric 又有一些細微的差異。 例如,在 Node Exporter 返回的樣本中指標 node_load1 反應的是當前系統的負載狀態,隨著時間的變化這個指標返回的樣本資料是在不斷變化的。而指標 node_cpu_seconds_total 所獲取到的樣本資料卻不同,它是一個持續增大的值,因為其反應的是 CPU 的累計使用時間,從理論上講只要系統不關機,這個值是會一直變大。

為了能夠幫助使用者理解和區分這些不同監控指標之間的差異,Prometheus 定義了4種不同的指標型別:Counter(計數器)、Gauge(儀表盤)、Histogram(直方圖)、Summary(摘要)。

Counter

Counter (只增不減的計數器) 型別的指標其工作方式和計數器一樣,只增不減。常見的監控指標,如 http_requests_totalnode_cpu_seconds_total 都是 Counter 型別的監控指標。

Counter 是一個簡單但又強大的工具,例如我們可以在應用程式中記錄某些事件發生的次數,通過以時間序列的形式儲存這些資料,我們可以輕鬆的瞭解該事件產生的速率變化。PromQL 內建的聚合操作和函式可以讓使用者對這些資料進行進一步的分析,例如,通過 rate() 函式獲取 HTTP 請求量的增長率:

rate(http_requests_total[5m])

查詢當前系統中,訪問量前 10 的 HTTP 請求:

topk(10, http_requests_total)

Gauge

Counter 不同,Gauge(可增可減的儀表盤)型別的指標側重於反應系統的當前狀態。因此這類指標的樣本資料可增可減。常見指標如:node_memory_MemFree_bytes(主機當前空閒的記憶體大小)、node_memory_MemAvailable_bytes(可用記憶體大小)都是 Gauge 型別的監控指標。通過 Gauge 指標,使用者可以直接檢視系統的當前狀態:

node_memory_MemFree_bytes

對於 Gauge 型別的監控指標,通過 PromQL 內建函式 delta() 可以獲取樣本在一段時間範圍內的變化情況。例如,計算 CPU 溫度在兩個小時內的差異:

delta(cpu_temp_celsius{host="zeus"}[2h])

還可以直接使用 predict_linear() 對資料的變化趨勢進行預測。例如,預測系統磁碟空間在4個小時之後的剩餘情況:

predict_linear(node_filesystem_free_bytes[1h], 4 * 3600)

Histogram 和 Summary

HistogramSummary 主用用於統計和分析樣本的分佈情況。

在大多數情況下人們都傾向於使用某些量化指標的平均值,例如 CPU 的平均使用率、頁面的平均響應時間,這種方式也有很明顯的問題,以系統 API 呼叫的平均響應時間為例:如果大多數 API 請求都維持在 100ms 的響應時間範圍內,而個別請求的響應時間需要 5s,那麼就會導致某些 WEB 頁面的響應時間落到中位數上,而這種現象被稱為長尾問題

為了區分是平均的慢還是長尾的慢,最簡單的方式就是按照請求延遲的範圍進行分組。例如,統計延遲在 0~10ms 之間的請求數有多少而 10~20ms 之間的請求數又有多少。通過這種方式可以快速分析系統慢的原因。HistogramSummary 都是為了能夠解決這樣的問題存在的,通過 HistogramSummary 型別的監控指標,我們可以快速瞭解監控樣本的分佈情況。

例如,指標 prometheus_tsdb_wal_fsync_duration_seconds 的指標型別為 Summary。它記錄了 Prometheus Server 中 wal_fsync 的處理時間,通過訪問 Prometheus Server 的 /metrics 地址,可以獲取到以下監控樣本資料:

# HELP prometheus_tsdb_wal_fsync_duration_seconds Duration of WAL fsync.
# TYPE prometheus_tsdb_wal_fsync_duration_seconds summary
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.5"} 0.012352463
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.9"} 0.014458005
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.99"} 0.017316173
prometheus_tsdb_wal_fsync_duration_seconds_sum 2.888716127000002
prometheus_tsdb_wal_fsync_duration_seconds_count 216

從上面的樣本中可以得知當前 Prometheus Server 進行 wal_fsync 操作的總次數為216次,耗時2.888716127000002s。其中中位數(quantile=0.5)的耗時為0.012352463,9分位數(quantile=0.9)的耗時為0.014458005s。

在 Prometheus Server 自身返回的樣本資料中,我們還能找到型別為 Histogram 的監控指標prometheus_tsdb_compaction_chunk_range_seconds_bucket

# HELP prometheus_tsdb_compaction_chunk_range_seconds Final time range of chunks on their first compaction
# TYPE prometheus_tsdb_compaction_chunk_range_seconds histogram
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="100"} 71
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="400"} 71
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="1600"} 71
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="6400"} 71
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="25600"} 405
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="102400"} 25690
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="409600"} 71863
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="1.6384e+06"} 115928
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="6.5536e+06"} 2.5687892e+07
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="2.62144e+07"} 2.5687896e+07
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="+Inf"} 2.5687896e+07
prometheus_tsdb_compaction_chunk_range_seconds_sum 4.7728699529576e+13
prometheus_tsdb_compaction_chunk_range_seconds_count 2.5687896e+07

Summary 型別的指標相似之處在於 Histogram 型別的樣本同樣會反應當前指標的記錄的總數(以 _count 作為字尾)以及其值的總量(以 _sum 作為字尾)。不同在於 Histogram 指標直接反應了在不同區間內樣本的個數,區間通過標籤 le 進行定義。

查詢

當 Prometheus 採集到監控指標樣本資料後,我們就可以通過 PromQL 對監控樣本資料進行查詢。基本的 Prometheus 查詢的結構非常類似於一個 metric 指標,以指標名稱開始。

查詢結構

比如只查詢 node_cpu_seconds_total 則會返回所有采集節點的所有型別的 CPU 時長資料,當然如果資料量特別特別大的時候,直接在 Grafana 執行該查詢操作的時候,則可能導致瀏覽器崩潰,因為它同時需要渲染的資料點太多。

接下來,可以使用標籤進行過濾查詢,標籤過濾器支援4種運算子:

  • = 等於
  • != 不等於
  • =~ 匹配正則表示式
  • !~ 與正則表示式不匹配

標籤過濾器都位於指標名稱後面的{}內,比如過濾 master 節點的 CPU 使用資料可用如下查詢語句:

node_cpu_seconds_total{instance="master"}

此外我們還可以使用多個標籤過濾器,以逗號分隔。多個標籤過濾器之間是 AND 的關係,所以使用多個標籤進行過濾,返回的指標資料必須和所有標籤過濾器匹配。

例如如下查詢語句將返回所有以 node為字首的節點的並且是 idle 模式下面的節點 CPU 使用時長指標:

node_cpu_seconds_total{instance=~"node.*", mode="idle"}

範圍選擇器

我們可以通過將時間範圍選擇器([])附加到查詢語句中,指定為每個返回的區間向量樣本值中提取多長的時間範圍。每個時間戳的值都是按時間倒序記錄在時間序列中的,該值是從時間範圍內的時間戳獲取的對應的值。

時間範圍通過數字來表示,單位可以使用以下其中之一的時間單位:

  • s - 秒
  • m - 分鐘
  • h - 小時
  • d - 天
  • w - 周
  • y - 年

比如 node_cpu_seconds_total{instance="master", mode="idle"} 這個查詢語句,如果新增上 [1m] 這個時間範圍選擇器,則我們可以得到如下所示的資訊:

可以看到上面的五個時間序列都有4個值,這是因為我們 Prometheus 中配置的抓取間隔是15秒,所以,我們從圖中的 @ 符號後面的時間戳可以看出,它們之間的間隔基本上就是15秒。

但是現在如果我們在 Prometheus 的頁面中查詢上面的語句,然後切換到 Graph 選項卡的時候,則會出現如下所示的錯誤資訊:

這是因為現在每一個時間序列中都有多個時間戳多個值,所以沒辦法渲染,必須是標量或者瞬時向量才可以繪製圖形。

不過通常區間向量都會應用一個函式後變成可以繪製的瞬時向量,Prometheus 中對瞬時向量和區間向量有很多操作的函式,不過對於區間向量來說最常用的函式並不多,使用最頻繁的有如下幾個函式:

  • rate(): 計算整個時間範圍內區間向量中時間序列的每秒平均增長率
  • irate(): 僅使用時間範圍中的最後兩個資料點來計算區間向量中時間序列的每秒平均增長率,irate 只能用於繪製快速變化的序列,在長期趨勢分析或者告警中更推薦使用 rate 函式
  • increase(): 計算所選時間範圍內時間序列的增量,它基本上是速率乘以時間範圍選擇器中的秒數

我們選擇的時間範圍持續時間將確定圖表的粒度,比如,持續時間 [1m] 會給出非常尖銳的圖表,從而很難直觀的顯示出趨勢來,看起來像這樣:

對於一個一小時的圖表,[5m] 顯示的圖表看上去要更加合適一些,更能顯示出 CPU 使用的趨勢:

對於更長的時間跨度,可能需要設定更長的持續時間,以便消除波峰並獲得更多的長期趨勢圖表。

有的時候可能想要檢視5分鐘前或者昨天一天的區間內的樣本資料,這個時候我們就需要用到位移操作了,位移操作的關鍵字是 offset,比如我們可以查詢30分鐘之前的 master 節點 CPU 的空閒指標資料:

node_cpu_seconds_total{instance="master", mode="idle"} offset 30m

需要注意的是 offset 關鍵字需要緊跟在選擇器({})後面。

同樣位移操作也適用於區間向量,比如我們要查詢昨天的前5分鐘的 CPU 空閒增長率:

sum(rate(node_cpu_seconds_total{instance="master", mode="idle"}[5m] offset 1d))

關聯查詢

Prometheus 沒有提供類似與 SQL 語句的關聯查詢的概念,但是我們可以通過在 Prometheus 上使用 運算子 來組合時間序列,可以應用於多個時間序列或標量值的常規計算、比較和邏輯運算。

如果將運算子應用於兩個瞬時向量,則它將僅應用於匹配的時間序列,當且僅當時間序列具有完全相同的標籤集的時候,才認為是匹配的。當表示式左側的每個序列和右側的一個序列完全匹配的時候,在序列上使用這些運算子才可以實現一對一匹配。

比如如下的兩個瞬時向量:

node_cpu_seconds_total{instance="master", cpu="0", mode="idle"}

node_cpu_seconds_total{instance="node1", cpu="0", mode="idle"}

如果我們對這兩個序列做加法運算來嘗試獲取 master 和 node1 節點的總的空閒 CPU 時長,則不會返回任何內容了:

這是因為這兩個時間序列沒有完全匹配標籤。我們可以使用 on 關鍵字指定只希望在 mode 標籤上進行匹配,就可以計算出結果來:

node_cpu_seconds_total{instance="master", cpu="0", mode="idle"} + on  (mode) node_cpu_seconds_total{instance="node1", cpu="0", mode="idle"}

需要注意的是新的瞬時向量包含單個序列,其中僅包含 on 關鍵字中指定的標籤。

不過在 Prometheus 中還有很多 聚合操作,所以,如果我們真的想要獲取節點的 CPU 總時長,我們完全不用這麼操作,使用 sum 操作要簡單得多:

sum(node_cpu_seconds_total{mode="idle"}) by (instance)

on 關鍵字只能用於一對一的匹配中,如果是多對一或者一對多的匹配情況下,就不行了,比如我們可以通過 kube-state-metrics 這個工具來獲取 Kubernetes 叢集的各種狀態指標,包括 Pod 的基本資訊,比如我們執行如下所示的查詢語句:

container_cpu_user_seconds_total{namespace="kube-system"} * on (pod) kube_pod_info

就會出現 Error executing query: multiple matches for labels: many-to-one matching must be explicit (group_left/group_right) 這樣的錯誤提示,這是因為左側的序列資料在同一個 Pod 上面有可能會有多條時間序列,所以不能簡單通過 on (pod) 來進行查詢。

要解決這個問題,我們可以使用 group_leftgroup_right 關鍵字。這兩個關鍵字將匹配分別轉換為多對一一對多匹配。左側和右側表示基數較高的一側。因此,group_left 意味著左側的多個序列可以與右側的單個序列匹配。結果是,返回的瞬時向量包含基數較高的一側的所有標籤,即使它們與右側的任何標籤都不匹配。

例如如下所示的查詢語句就可以正常獲取到結果,而且獲取到的時間序列資料包含所有的標籤:

container_cpu_user_seconds_total{namespace="kube-system"} * on (pod) group_left() kube_pod_info

瞬時向量和標量結合

此外我們還可以將瞬時向量和標量值相結合,這個很簡單,就是簡單的數學計算,比如:

node_cpu_seconds_total{instance="master"} * 10

會為瞬時向量中每個序列的每個值都剩以10。這對於計算比率和百分比得時候非常有用。

  • 除了 * 之外,其他常用的算數運算子當然也支援:+-*/%^
  • 還有其他的比較運算子:==!=><>=<=
  • 邏輯運算子:andorunless,不過邏輯運算子只能用於瞬時向量之間。

除了這些關於 PromQL 最基本的知識點之外,還有很多相關的使用方法,可以參考官網相關介紹:https://prometheus.io/docs/prometheus/latest/querying/basics/。