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 整合,提供了內建的服務發現分別是:Node
、Service
、Pod
、Endpoints
、Ingress
配置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
- Node Exporter https://grafana.com/grafana/dashboards/8919
- Prometheus: https://grafana.com/grafana/dashboards/8588
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
-
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
對應的內容
自定義監控面板
- CA Cert:使用config檔案中的
通用的監控需求基本上都可以使用第三方的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
-
映象使用官方提供的v0.7.0最新版 https://hub.docker.com/r/directxman12/k8s-prometheus-adapter/tags
-
準備證書
$ 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
-
準備資源清單
$ 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: - {}
-
替換名稱空間
# 資源清單檔案預設用的名稱空間是custom-metrics,替換為本例中使用的monitor $ sed -i 's/namespace: custom-metrics/namespace: monitor/g' yamls/*
-
配置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 ...
-
部署服務
$ 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)
-
自定義指標可以配置多個,因此,需要將規則使用陣列來配置
rules: - {}
-
告訴Adapter,哪些自定義指標可以使用
rules: - seriesQuery: 'nginx_vts_server_requests_total{host="*",code="total"}'
seriesQuery是PromQL語句,和直接用
nginx_vts_server_requests_total
查詢到的結果一樣,凡是seriesQuery可以查詢到的指標,都可以用作自定義指標 -
告訴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_namespace
和kubernetes_pod_name
) -
指定自定義的指標名稱,供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規則中可以直接配置該名稱。 -
告訴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
: 指標查詢的labelGroupBy
: 結果分組,針對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
}
]
}