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個模組:global
、rule_files
和 scrape_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/metrics 和 http://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_exporter
、node_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_auth
和bearer_token
:比如我們提供的/metrics
介面需要 basic 認證的時候,通過傳統的使用者名稱/密碼或者在請求的 header 中新增對應的 token 都可以支援kubernetes_sd_configs
或consul_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: true
、hostIPC: true
、hostNetwork: true
3個策略,用來使用主機的 PID namespace
、IPC 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中服務發現模式,分別是:Node
、Service
、Pod
、Endpoints
、Ingress
。
我們通過 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.crt
和token
兩個檔案我們可以在 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
中過濾了 annotation
有 prometheus.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_total
和 node_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_name
和 label
組合都稱為時間序列,在 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_total
、node_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
Histogram
和 Summary
主用用於統計和分析樣本的分佈情況。
在大多數情況下人們都傾向於使用某些量化指標的平均值,例如 CPU 的平均使用率、頁面的平均響應時間,這種方式也有很明顯的問題,以系統 API 呼叫的平均響應時間為例:如果大多數 API 請求都維持在 100ms 的響應時間範圍內,而個別請求的響應時間需要 5s,那麼就會導致某些 WEB 頁面的響應時間落到中位數上,而這種現象被稱為長尾問題。
為了區分是平均的慢還是長尾的慢,最簡單的方式就是按照請求延遲的範圍進行分組。例如,統計延遲在 0~10ms 之間的請求數有多少而 10~20ms 之間的請求數又有多少。通過這種方式可以快速分析系統慢的原因。Histogram
和 Summary
都是為了能夠解決這樣的問題存在的,通過 Histogram
和Summary
型別的監控指標,我們可以快速瞭解監控樣本的分佈情況。
例如,指標 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_left
或group_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。這對於計算比率和百分比得時候非常有用。
- 除了
*
之外,其他常用的算數運算子當然也支援:+
、-
、*
、/
、%
、^
。 - 還有其他的比較運算子:
==
、!=
、>
、<
、>=
、<=
。 - 邏輯運算子:
and
、or
、unless
,不過邏輯運算子只能用於瞬時向量之間。
除了這些關於 PromQL
最基本的知識點之外,還有很多相關的使用方法,可以參考官網相關介紹:https://prometheus.io/docs/prometheus/latest/querying/basics/。