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

Kubernetes 監控:Prometheus Operator

安裝

前面的章節中我們學習了用自定義的方式來對 Kubernetes 叢集進行監控,基本上也能夠完成監控報警的需求了。但實際上對上 Kubernetes 來說,還有更簡單方式來監控報警,那就是 Prometheus Operator。Prometheus Operator 為監控 Kubernetes 資源和 Prometheus 例項的管理提供了簡單的定義,簡化在 Kubernetes 上部署、管理和執行 Prometheus 和 Alertmanager 叢集。

介紹

首先我們先來了解下 Prometheus-Operator 的架構圖:

上圖是 Prometheus-Operator 官方提供的架構圖,各元件以不同的方式執行在 Kubernetes 叢集中,其中 Operator 是最核心的部分,作為一個控制器,他會去建立Prometheus、ServiceMonitor、AlertManager 以及 PrometheusRule 4個 CRD 資源物件,然後會一直監控並維持這4個資源物件的狀態。

  • Operator:根據自定義資源來部署和管理 Prometheus Server,同時監控這些自定義資源事件的變化來做相應的處理,是整個系統的控制中心。
  • Prometheus:宣告 Prometheus 資源物件期望的狀態,Operator 確保這個資源物件執行時一直與定義保持一致。
  • Prometheus Server:Operator 根據自定義資源 Prometheus 型別中定義的內容而部署的 Prometheus Server 叢集,這些自定義資源可以看作是用來管理 Prometheus Server 叢集的 StatefulSets 資源。
  • ServiceMonitor:宣告指定監控的服務,描述了一組被 Prometheus 監控的目標列表,就是 exporter 的抽象,用來提供 metrics 資料介面的工具。該資源通過 Labels 來選取對應的 Service Endpoint,讓 Prometheus Server 通過選取的 Service 來獲取 Metrics 資訊。
  • Service:簡單的說就是 Prometheus 監控的物件。
  • Alertmanager:定義 AlertManager 資源物件期望的狀態,Operator 確保這個資源物件執行時一直與定義保持一致。

這樣我們要在叢集中監控什麼資料,就變成了直接去操作 Kubernetes 叢集的資源物件了,是不是方便很多了。

安裝

我們可以使用 Helm 來快速安裝 Prometheus Operator,也可以通過 https://github.com/coreos/kube-prometheus 專案來手動安裝,我們這裡採用手動安裝的方式可以去了解更多的實現細節。

首先 clone 專案程式碼:

$ git clone https://github.com/coreos/kube-prometheus.git
$ cd manifests

進入到 manifests 目錄下面,首先我們需要安裝 setup 目錄下面的 CRD 和 Operator 資源物件:

$ kubectl apply -f setup/
$ kubectl get pods -n monitoring
NAME                                   READY   STATUS    RESTARTS   AGE
prometheus-operator-6694b5cb64-z64ns   2/2     Running   0          114s
$ kubectl get crd |grep coreos
alertmanagers.monitoring.coreos.com              2020-04-10T07:00:12Z
podmonitors.monitoring.coreos.com                2020-04-10T07:00:13Z
prometheuses.monitoring.coreos.com               2020-04-10T07:00:14Z
prometheusrules.monitoring.coreos.com            2020-04-10T07:00:15Z
servicemonitors.monitoring.coreos.com            2020-04-10T07:00:16Z
thanosrulers.monitoring.coreos.com               2020-04-10T07:00:17Z

這會建立一個名為 monitoring 的名稱空間,以及相關的 CRD 資源物件宣告和 Prometheus Operator 控制器。前面章節中我們講解過 CRD 和 Operator 的使用,當我們宣告完 CRD 過後,就可以來自定義資源清單了,但是要讓我們宣告的自定義資源物件生效就需要安裝對應的 Operator 控制器,這裡我們都已經安裝了,所以接下來就可以來用 CRD 建立真正的自定義資源物件了。其實在 manifests 目錄下面的就是我們要去建立的 Prometheus、Alertmanager 以及各種監控物件的資源清單。

沒有特殊的定製需求我們可以直接一鍵安裝:

$ kubectl apply -f .

這會自動安裝 node-exporter、kube-state-metrics、grafana、prometheus-adapter 以及 prometheus 和 alertmanager 元件,而且 prometheus 和 alertmanager 還是多副本的。

$ kubectl get pods -n monitoring
NAME                                   READY   STATUS              RESTARTS   AGE
alertmanager-main-0                    2/2     Running             0          10m
alertmanager-main-1                    2/2     Running             0          10m
alertmanager-main-2                    2/2     Running             0          10m
grafana-86b55cb79f-jpnmr               1/1     Running             0          9m53s
kube-state-metrics-dbb85dfd5-hl2sn     3/3     Running             0          9m49s
node-exporter-482tf                    2/2     Running             0          95s
node-exporter-9g2cv                    2/2     Running             0          9m47s
node-exporter-dxr2d                    2/2     Running             0          9m47s
node-exporter-h4f6c                    2/2     Running             0          9m47s
node-exporter-hxwqb                    2/2     Running             0          9m47s
node-exporter-lzdw2                    2/2     Running             0          9m47s
node-exporter-n2qj6                    2/2     Running             0          9m47s
prometheus-adapter-5cd5798d96-4r6lx    1/1     Running             0          9m40s
prometheus-k8s-0                       3/3     Running             0          9m27s
prometheus-k8s-1                       3/3     Running             1          9m25s
prometheus-operator-6694b5cb64-z64ns   2/2     Running             0          18m
$ kubectl get svc -n monitoring
NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
alertmanager-main       ClusterIP   10.111.28.173    <none>        9093/TCP                     12m
alertmanager-operated   ClusterIP   None             <none>        9093/TCP,9094/TCP,9094/UDP   12m
grafana                 ClusterIP   10.99.62.32      <none>        3000/TCP                     11m
kube-state-metrics      ClusterIP   None             <none>        8443/TCP,9443/TCP            11m
node-exporter           ClusterIP   None             <none>        9100/TCP                     11m
prometheus-adapter      ClusterIP   10.100.102.211   <none>        443/TCP                      11m
prometheus-k8s          ClusterIP   10.111.105.155   <none>        9090/TCP                     11m
prometheus-operated     ClusterIP   None             <none>        9090/TCP                     11m
prometheus-operator     ClusterIP   None             <none>        8443/TCP                     20m

可以看到上面針對 grafana、alertmanager 和 prometheus 都建立了一個型別為 ClusterIP 的 Service,當然如果我們想要在外網訪問這兩個服務的話可以通過建立對應的 Ingress 物件或者使用 NodePort 型別的 Service,我們這裡為了簡單,直接使用 NodePort 型別的服務即可,編輯 grafana、alertmanager-main 和 prometheus-k8s 這3個 Service,將服務型別更改為 NodePort:

# 將 type: ClusterIP 更改為 type: NodePort
$ kubectl edit svc grafana -n monitoring  
$ kubectl edit svc alertmanager-main -n monitoring
$ kubectl edit svc prometheus-k8s -n monitoring
$ kubectl get svc -n monitoring
NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
alertmanager-main       NodePort    10.111.28.173    <none>        9093:30733/TCP               18m
grafana                 NodePort    10.99.62.32      <none>        3000:32150/TCP               17m
prometheus-k8s          NodePort    10.111.105.155   <none>        9090:30206/TCP               17m
......

更改完成後,我們就可以通過上面的 NodePort 去訪問對應的服務了,比如檢視 prometheus 的服務發現頁面:

可以看到已經監控上了很多指標資料了,上面我們可以看到 Prometheus 是兩個副本,我們這裡通過 Service 去訪問,按正常來說請求是會去輪詢訪問後端的兩個 Prometheus 例項的,但實際上我們這裡訪問的時候始終是路由到後端的一個例項上去,因為這裡的 Service 在建立的時候添加了 sessionAffinity: ClientIP 這樣的屬性,會根據 ClientIP 來做 session 親和性,所以我們不用擔心請求會到不同的副本上去:

apiVersion: v1
kind: Service
metadata:
  labels:
    prometheus: k8s
  name: prometheus-k8s
  namespace: monitoring
spec:
  ports:
  - name: web
    port: 9090
    targetPort: web
  selector:
    app: prometheus
    prometheus: k8s
  sessionAffinity: ClientIP

配置

我們可以看到上面的監控指標大部分的配置都是正常的,只有兩三個沒有管理到對應的監控目標,比如 kube-controller-manager 和 kube-scheduler 這兩個系統元件。

這其實就和 ServiceMonitor 的定義有關係了,我們先來檢視下 kube-scheduler 元件對應的 ServiceMonitor 資源的定義,manifests/prometheus-serviceMonitorKubeScheduler.yaml:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  labels:
    k8s-app: kube-scheduler
  name: kube-scheduler
  namespace: monitoring
spec:
  endpoints:
  - interval: 30s  # 每30s獲取一次資訊
    port: http-metrics  # 對應 service 的埠名
  jobLabel: k8s-app
  namespaceSelector:  # 表示去匹配某一名稱空間中的service,如果想從所有的namespace中匹配用any: true
    matchNames:
    - kube-system
  selector:  # 匹配的 Service 的 labels,如果使用 mathLabels,則下面的所有標籤都匹配時才會匹配該 service,如果使用 matchExpressions,則至少匹配一個標籤的 service 都會被選擇
    matchLabels:
      k8s-app: kube-scheduler

上面是一個典型的 ServiceMonitor 資源物件的宣告方式,上面我們通過 selector.matchLabels 在 kube-system 這個名稱空間下面匹配具有 k8s-app=kube-scheduler 這樣的 Service,但是我們系統中根本就沒有對應的 Service:

$ kubectl get svc -n kube-system -l k8s-app=kube-scheduler
No resources found.

所以我們需要去建立一個對應的 Service 物件,才能核 ServiceMonitor 進行關聯:(prometheus-kubeSchedulerService.yaml)

apiVersion: v1
kind: Service
metadata:
  namespace: kube-system
  name: kube-scheduler
  labels:
    k8s-app: kube-scheduler
spec:
  selector:
    component: kube-scheduler
  ports:
  - name: http-metrics
    port: 10251
    targetPort: 10251

其中最重要的是上面 labels 和 selector 部分,labels 區域的配置必須和我們上面的 ServiceMonitor 物件中的 selector 保持一致,selector 下面配置的是 component=kube-scheduler,為什麼會是這個 label 標籤呢,我們可以去 describe 下 kube-scheduler 這個 Pod:

$ kubectl describe pod kube-scheduler-ydzs-master -n kube-system
Name:               kube-scheduler-ydzs-master
Namespace:          kube-system
Priority:           2000000000
PriorityClassName:  system-cluster-critical
Node:               ydzs-master/10.151.30.11
Start Time:         Sat, 04 Jan 2020 17:42:05 +0800
Labels:             component=kube-scheduler
                    tier=control-plane
......

我們可以看到這個 Pod 具有 component=kube-scheduler 和 tier=control-plane 這兩個標籤,而前面這個標籤具有更唯一的特性,所以使用前面這個標籤較好,這樣上面建立的 Service 就可以和我們的 Pod 進行關聯了,直接建立即可:

$ kubectl apply -f prometheus-kubeSchedulerService.yaml
$ kubectl get svc -n kube-system -l k8s-app=kube-scheduler
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)     AGE
kube-scheduler   ClusterIP   10.100.213.244   <none>        10251/TCP   13s

建立完成後,隔一小會兒後去 Prometheus 頁面上檢視 targets 下面 kube-scheduler 已經可以採集到指標資料了。可以用同樣的方式來修復下 kube-controller-manager 元件的監控,只需要建立一個如下所示的 Service 物件,只是埠改成 10252 即可:(prometheus-kubeControllerManagerService.yaml)

apiVersion: v1
kind: Service
metadata:
  namespace: kube-system
  name: kube-controller-manager
  labels:
    k8s-app: kube-controller-manager
spec:
  selector:
    component: kube-controller-manager
  ports:
  - name: http-metrics
    port: 10252
    targetPort: 10252

上面的監控資料配置完成後,我們就可以去檢視下 Grafana 下面的監控圖表了,同樣使用上面的 NodePort 訪問即可,第一次登入使用 admin:admin 登入即可,進入首頁後,我們可以發現其實 Grafana 已經有很多配置好的監控圖表了。

我們可以隨便選擇一個 Dashboard 檢視監控圖表資訊。

接下來我們再來學習如何完全自定義一個 ServiceMonitor 以及 AlertManager 相關的配置。

自定義監控報警

上節課和大家講解了 Prometheus Operator 的安裝和基本使用方法,這節課給大家介紹如何在 Prometheus Operator 中新增一個自定義的監控項。

除了 Kubernetes 叢集中的一些資源物件、節點以及元件需要監控,有的時候我們可能還需要根據實際的業務需求去新增自定義的監控項,新增一個自定義監控的步驟也是非常簡單的。

  • 第一步建立一個 ServiceMonitor 物件,用於 Prometheus 新增監控項
  • 第二步為 ServiceMonitor 物件關聯 metrics 資料介面的一個 Service 物件
  • 第三步確保 Service 物件可以正確獲取到 metrics 資料

接下來我們就來為大家演示如何新增 etcd 叢集的監控。無論是 Kubernetes 叢集外的還是使用 Kubeadm 安裝在叢集內部的 etcd 叢集,我們這裡都將其視作叢集外的獨立叢集,因為對於二者的使用方法沒什麼特殊之處。

etcd監控

由於我們這裡演示環境使用的是 Kubeadm 搭建的叢集,我們可以使用 kubectl 工具去獲取 etcd 啟動的相關引數:

$ kubectl get pods -n kube-system -l component=etcd
NAME               READY   STATUS    RESTARTS   AGE
etcd-ydzs-master   1/1     Running   3          157d
$ kubectl get pods etcd-ydzs-master -n kube-system -o yaml
......
spec:
  containers:
  - command:
    - etcd
    - --advertise-client-urls=https://10.151.30.11:2379
    - --cert-file=/etc/kubernetes/pki/etcd/server.crt
    - --client-cert-auth=true
    - --data-dir=/var/lib/etcd
    - --initial-advertise-peer-urls=https://10.151.30.11:2380
    - --initial-cluster=ydzs-master=https://10.151.30.11:2380
    - --key-file=/etc/kubernetes/pki/etcd/server.key
    - --listen-client-urls=https://127.0.0.1:2379,https://10.151.30.11:2379
    - --listen-metrics-urls=http://127.0.0.1:2381
    - --listen-peer-urls=https://10.151.30.11:2380
    - --name=ydzs-master
    - --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
    - --peer-client-cert-auth=true
    - --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
    - --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    - --snapshot-count=10000
    - --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
......
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
    - mountPath: /etc/kubernetes/pki/etcd
      name: etcd-certs
......
  volumes:
  - hostPath:
      path: /etc/kubernetes/pki/etcd
      type: DirectoryOrCreate
    name: etcd-certs
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data
......

我們可以看到啟動引數裡面有一個 --listen-metrics-urls=http://127.0.0.1:2381 的配置,該引數就是來指定 metrics 介面執行在 2381 埠下面的,而且是 http 的協議,所以也不需要什麼證書配置,這就比以前的版本要簡單許多了,以前的版本需要用 https 協議訪問,所以要配置對應的證書。

接下來我們直接建立對應的 ServiceMonitor 物件即可(prometheus-serviceMonitorEtcd.yaml):

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: etcd-k8s
  namespace: monitoring
  labels:
    k8s-app: etcd-k8s
spec:
  jobLabel: k8s-app
  endpoints:
  - port: port
    interval: 15s
  selector:
    matchLabels:
      k8s-app: etcd
  namespaceSelector:
    matchNames:
    - kube-system

上面我們在 monitoring 名稱空間下面建立了名為 etcd-k8s 的 ServiceMonitor 物件,基本屬性和前面章節中的一致,匹配 kube-system 這個名稱空間下面的具有 k8s-app=etcd 這個 label 標籤的 Service,jobLabel 表示用於檢索 job 任務名稱的標籤,由於 etcd 的 metrics 介面在 2381 埠下面,不需要 https 安全認證,所以用預設的配置即可。關於 ServiceMonitor 更多的配置屬性,可以參考官方的 API 文件的描述。

然後我們直接建立這個 ServiceMonitor 物件即可:

$ kubectl apply -f prometheus-serviceMonitorEtcd.yaml
servicemonitor.monitoring.coreos.com "etcd-k8s" created

但實際上現在並不能監控到 etcd 叢集,因為並沒有一個滿足 ServiceMonitor 條件的 Service 物件與之關聯:

$ kubectl get svc -n kube-system -l k8s-app=etcd
No resources found.

所以接下來我們需要建立一個滿足上面條件的 Service 物件,由於我們把 etcd 當成是叢集外部的服務,所以要引入到叢集中來我們就需要自定義 Endpoints 物件來建立 Service 物件了:(etcd-service.yaml)

apiVersion: v1
kind: Service
metadata:
  name: etcd-k8s
  namespace: kube-system
  labels:
    k8s-app: etcd
spec:
  type: ClusterIP
  clusterIP: None  # 一定要設定 clusterIP:None
  ports:
  - name: port
    port: 2381
---
apiVersion: v1
kind: Endpoints
metadata:
  name: etcd-k8s
  namespace: kube-system
  labels:
    k8s-app: etcd
subsets:
- addresses:
  - ip: 10.151.30.11  # 指定etcd節點地址,如果是叢集則繼續向下新增
    nodeName: etc-master
  ports:
  - name: port
    port: 2381

我們這裡建立的 Service 沒有采用前面通過 label 標籤的形式去匹配 Pod 的做法,因為前面我們說過很多時候我們建立的 etcd 叢集是獨立於叢集之外的,這種情況下面我們就需要自定義一個 Endpoints,要注意 metadata 區域的內容要和 Service 保持一致,Service 的 clusterIP 設定為 None,新版本的 etcd 將 metrics 介面資料放置到了 2381 埠。直接建立該資源物件即可:

$ kubectl apply -f etcd-service.yaml
service/etcd-k8s configured
endpoints/etcd-k8s configured
$ kubectl get svc -n kube-system -l k8s-app=etcd
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
etcd-k8s   ClusterIP   None         <none>        2381/TCP   128d

建立完成後,隔一會兒去 Prometheus 的 Dashboard 中檢視 targets,便會有 etcd 的監控項了:

可以看到有一個明顯的錯誤,2381 埠連結被拒絕,這是因為我們這裡的 etcd 的 metrics 介面是監聽在 127.0.0.1 這個 IP 上面的,所以訪問會拒絕:

--listen-metrics-urls=http://127.0.0.1:2381

我們只需要在 /etc/kubernetes/manifest/ 目錄下面(靜態 Pod 預設的目錄)的 etcd.yaml 檔案中將上面的listen-metrics-urls 更改成節點 IP 即可:

--listen-metrics-urls=http://0.0.0.0:2381

當 etcd 重啟生效後,檢視 etcd 這個監控任務就正常了:

資料採集到後,可以在 grafana 中匯入編號為 3070 的 dashboard,就可以獲取到 etcd 的監控圖表:

配置 PrometheusRule

現在我們知道怎麼自定義一個 ServiceMonitor 物件了,但是如果需要自定義一個報警規則的話呢?我們去檢視 Prometheus Dashboard 的 Alert 頁面下面就已經有很多報警規則了,這一系列的規則其實都來自於專案 https://github.com/kubernetes-monitoring/kubernetes-mixin,我們都通過 Prometheus Operator 安裝配置上了。

但是這些報警資訊是哪裡來的呢?他們應該用怎樣的方式通知我們呢?我們知道之前我們使用自定義的方式可以在 Prometheus 的配置檔案之中指定 AlertManager 例項和 報警的 rules 檔案,現在我們通過 Operator 部署的呢?我們可以在 Prometheus Dashboard 的 Config 頁面下面檢視關於 AlertManager 的配置:

alerting:
  alert_relabel_configs:
  - separator: ;
    regex: prometheus_replica
    replacement: $1
    action: labeldrop
  alertmanagers:
  - kubernetes_sd_configs:
    - role: endpoints
      namespaces:
        names:
        - monitoring
    scheme: http
    path_prefix: /
    timeout: 10s
    api_version: v1
    relabel_configs:
    - source_labels: [__meta_kubernetes_service_name]
      separator: ;
      regex: alertmanager-main
      replacement: $1
      action: keep
    - source_labels: [__meta_kubernetes_endpoint_port_name]
      separator: ;
      regex: web
      replacement: $1
      action: keep
rule_files:
- /etc/prometheus/rules/prometheus-k8s-rulefiles-0/*.yaml

上面 alertmanagers 的配置我們可以看到是通過 role 為 endpoints 的 kubernetes 的自動發現機制獲取的,匹配的是服務名為 alertmanager-main,埠名為 web 的 Service 服務,我們可以檢視下 alertmanager-main 這個 Service:

$ kubectl describe svc alertmanager-main -n monitoring
Name:                     alertmanager-main
Namespace:                monitoring
Labels:                   alertmanager=main
Annotations:              kubectl.kubernetes.io/last-applied-configuration:
                            {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"alertmanager":"main"},"name":"alertmanager-main","namespace":"...
Selector:                 alertmanager=main,app=alertmanager
Type:                     NodePort
IP:                       10.106.211.33
Port:                     web  9093/TCP
TargetPort:               web/TCP
NodePort:                 web  31742/TCP
Endpoints:                10.244.3.119:9093,10.244.4.112:9093,10.244.8.164:9093
Session Affinity:         ClientIP
External Traffic Policy:  Cluster
Events:                   <none>

可以看到服務名正是 alertmanager-main,Port 定義的名稱也是 web,符合上面的規則,所以 Prometheus 和 AlertManager 元件就正確關聯上了。而對應的報警規則檔案位於:/etc/prometheus/rules/prometheus-k8s-rulefiles-0/目錄下面所有的 YAML 檔案。我們可以進入 Prometheus 的 Pod 中驗證下該目錄下面是否有 YAML 檔案:

$ kubectl exec -it prometheus-k8s-0 /bin/sh -n monitoring
Defaulting container name to prometheus.
Use 'kubectl describe pod/prometheus-k8s-0 -n monitoring' to see all of the containers in this pod.
/prometheus $ ls /etc/prometheus/rules/prometheus-k8s-rulefiles-0/
monitoring-prometheus-k8s-rules.yaml
/prometheus $ cat /etc/prometheus/rules/prometheus-k8s-rulefiles-0/monitoring-pr
ometheus-k8s-rules.yaml
groups:
- name: k8s.rules
  rules:
  - expr: |
      sum(rate(container_cpu_usage_seconds_total{job="kubelet", image!="", container_name!=""}[5m])) by (namespace)
    record: namespace:container_cpu_usage_seconds_total:sum_rate
......

這個 YAML 檔案實際上就是我們之前建立的一個 PrometheusRule 檔案包含的內容:

$ cat prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  labels:
    prometheus: k8s
    role: alert-rules
  name: prometheus-k8s-rules
  namespace: monitoring
spec:
  groups:
  - name: node-exporter.rules
    rules:
    - expr: |
        count without (cpu) (
          count without (mode) (
            node_cpu_seconds_total{job="node-exporter"}
          )
        )
      record: instance:node_num_cpu:sum
    - expr: |
......

我們這裡的 PrometheusRule 的 name 為 prometheus-k8s-rules,namespace 為 monitoring,我們可以猜想到我們建立一個 PrometheusRule 資源物件後,會自動在上面的 prometheus-k8s-rulefiles-0 目錄下面生成一個對應的 -.yaml 檔案,所以如果以後我們需要自定義一個報警選項的話,只需要定義一個 PrometheusRule 資源物件即可。至於為什麼 Prometheus 能夠識別這個 PrometheusRule 資源物件呢?這就需要檢視我們建立的 prometheus 這個資源物件了,裡面有非常重要的一個屬性 ruleSelector,用來匹配 rule 規則的過濾器,要求匹配具有 prometheus=k8s 和 role=alert-rules 標籤的 PrometheusRule 資源物件,現在明白了吧?

ruleSelector:
  matchLabels:
    prometheus: k8s
    role: alert-rules

所以我們要想自定義一個報警規則,只需要建立一個具有 prometheus=k8s 和 role=alert-rules 標籤的 PrometheusRule 物件就行了,比如現在我們新增一個 etcd 是否可用的報警,我們知道 etcd 整個叢集有一半以上的節點可用的話叢集就是可用的,所以我們判斷如果不可用的 etcd 數量超過了一半那麼就觸發報警,建立檔案 prometheus-etcdRules.yaml:

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  labels:
    prometheus: k8s
    role: alert-rules
  name: etcd-rules
  namespace: monitoring
spec:
  groups:
  - name: etcd
    rules:
    - alert: EtcdClusterUnavailable
      annotations:
        summary: etcd cluster small
        description: If one more etcd peer goes down the cluster will be unavailable
      expr: |
        count(up{job="etcd"} == 0) > (count(up{job="etcd"}) / 2 - 1)
      for: 3m
      labels:
        severity: critical

注意 label 標籤一定至少要有 prometheus=k8s 和 role=alert-rules,建立完成後,隔一會兒再去容器中檢視下 rules 資料夾:

$ kubectl apply -f prometheus-etcdRules.yaml
prometheusrule.monitoring.coreos.com/etcd-rules created
$ kubectl exec -it prometheus-k8s-0 /bin/sh -n monitoring
Defaulting container name to prometheus.
Use 'kubectl describe pod/prometheus-k8s-0 -n monitoring' to see all of the containers in this pod.
/prometheus $ ls /etc/prometheus/rules/prometheus-k8s-rulefiles-0/
monitoring-etcd-rules.yaml            monitoring-prometheus-k8s-rules.yaml

可以看到我們建立的 rule 檔案已經被注入到了對應的 rulefiles 資料夾下面了,證明我們上面的設想是正確的。然後再去 Prometheus Dashboard 的 Alert 頁面下面就可以檢視到上面我們新建的報警規則了:

配置報警

我們知道了如何去新增一個報警規則配置項,但是這些報警資訊用怎樣的方式去傳送呢?前面的課程中我們知道我們可以通過 AlertManager 的配置檔案去配置各種報警接收器,現在我們是通過 Operator 提供的 alertmanager 資源物件建立的元件,應該怎樣去修改配置呢?

首先我們去 Alertmanager 的頁面上 status 路徑下面檢視 AlertManager 的配置資訊:

這些配置資訊實際上是來自於前面建立的 alertmanager-secret.yaml 檔案:

apiVersion: v1
data: {}
kind: Secret
metadata:
  name: alertmanager-main
  namespace: monitoring
stringData:
  alertmanager.yaml: |-
    "global":
      "resolve_timeout": "5m"
    "inhibit_rules":
    - "equal":
      - "namespace"
      - "alertname"
      "source_match":
        "severity": "critical"
      "target_match_re":
        "severity": "warning|info"
    - "equal":
      - "namespace"
      - "alertname"
      "source_match":
        "severity": "warning"
      "target_match_re":
        "severity": "info"
    "receivers":
    - "name": "Default"
    - "name": "Watchdog"
    - "name": "Critical"
    "route":
      "group_by":
      - "namespace"
      "group_interval": "5m"
      "group_wait": "30s"
      "receiver": "Default"
      "repeat_interval": "12h"
      "routes":
      - "match":
          "alertname": "Watchdog"
        "receiver": "Watchdog"
      - "match":
          "severity": "critical"
        "receiver": "Critical"
type: Opaque

我們可以看到內容和上面檢視的配置資訊是一致的,所以如果我們想要新增自己的接收器,我們就可以直接更改這個檔案,比如我們將 Critical 這個接收器的報警資訊都發送到釘釘進行報警。

首先在 monitoring 名稱空間下面部署一個簡單的釘釘 webhook 處理器,前面 Alertmanager 章節已經學習過,這裡就不贅述了。

然後修改報警配置檔案 alertmanager-secret.yaml,我們將 Critial 這個接收器配置成了 webhook,其他保持不變:

- "name": "Critical"
  "webhook_configs":
  - "url": "http://dingtalk-hook:5000"
    "send_resolved": true
"route":
  "group_by":
  - "namespace"

修改完成後,重新更新這個資源物件:

# 刪除然後重新建立相當於強制更新
$ kubectl delete -f alertmanager-secret.yaml
secret "alertmanager-main" deleted
$ kubectl apply -f alertmanager-secret.yaml
secret/alertmanager-main configured

更新完成後,很快我們就會收到釘釘的報警訊息了,而且 AlertManager 頁面的 status 頁面的配置資訊可以看到也已經變成上面我們的配置資訊了:

到這裡我們就完成了 Prometheus Operator 的自定義監控和報警。

高階配置

前面我們一起學習瞭如何在 Prometheus Operator 下面自定義一個監控項,以及自定義報警規則的使用。那麼我們還能夠直接使用前面課程中的自動發現功能嗎?如果在我們的 Kubernetes 叢集中有了很多的 Service/Pod,那麼我們都需要一個一個的去建立一個對應的 ServiceMonitor 物件來進行監控嗎?這樣豈不是又變得麻煩起來了?

自動發現配置

為解決上面的問題,Prometheus Operator 為我們提供了一個額外的抓取配置的來解決這個問題,我們可以通過新增額外的配置來進行服務發現進行自動監控。和前面自定義的方式一樣,我們可以在 Prometheus Operator 當中去自動發現並監控具有prometheus.io/scrape=true 這個 annotations 的 Service,之前我們定義的 Prometheus 的配置如下:

- 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

如果你對上面這個配置還不是很熟悉的話,建議去檢視下前面關於 Kubernetes 常用資源物件監控章節的介紹,要想自動發現叢集中的 Service,就需要我們在 Service 的 annotation 區域新增 prometheus.io/scrape=true 的宣告,將上面檔案直接儲存為 prometheus-additional.yaml,然後通過這個檔案建立一個對應的 Secret 物件:

$ kubectl create secret generic additional-configs --from-file=prometheus-additional.yaml -n monitoring
secret "additional-configs" created

然後我們需要在宣告 prometheus 的資源物件檔案中通過 additionalScrapeConfigs 屬性新增上這個額外的配置:(prometheus-prometheus.yaml)

apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
  labels:
    prometheus: k8s
  name: k8s
  namespace: monitoring
spec:
  alerting:
    alertmanagers:
    - name: alertmanager-main
      namespace: monitoring
      port: web
  image: quay.io/prometheus/prometheus:v2.15.2
  nodeSelector:
    kubernetes.io/os: linux
  podMonitorNamespaceSelector: {}
  podMonitorSelector: {}
  replicas: 2
  resources:
    requests:
      memory: 400Mi
  ruleSelector:
    matchLabels:
      prometheus: k8s
      role: alert-rules
  securityContext:
    fsGroup: 2000
    runAsNonRoot: true
    runAsUser: 1000
  serviceAccountName: prometheus-k8s
  serviceMonitorNamespaceSelector: {}
  serviceMonitorSelector: {}
  version: v2.15.2
  additionalScrapeConfigs:
    name: additional-configs
    key: prometheus-additional.yaml

關於 additionalScrapeConfigs 屬性的具體介紹,我們可以使用 kubectl explain 命令來了解詳細資訊:

$ kubectl explain prometheus.spec.additionalScrapeConfigs
KIND:     Prometheus
VERSION:  monitoring.coreos.com/v1

RESOURCE: additionalScrapeConfigs <Object>

DESCRIPTION:
     AdditionalScrapeConfigs allows specifying a key of a Secret containing
     additional Prometheus scrape configurations. Scrape configurations
     specified are appended to the configurations generated by the Prometheus
     Operator. Job configurations specified must have the form as specified in
     the official Prometheus documentation:
     https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config.
     As scrape configs are appended, the user is responsible to make sure it is
     valid. Note that using this feature may expose the possibility to break
     upgrades of Prometheus. It is advised to review Prometheus release notes to
     ensure that no incompatible scrape configs are going to break Prometheus
     after the upgrade.

FIELDS:
   key  <string> -required-
     The key of the secret to select from. Must be a valid secret key.

   name <string>
     Name of the referent. More info:
     https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
     TODO: Add other useful fields. apiVersion, kind, uid?

   optional     <boolean>
     Specify whether the Secret or its key must be defined

新增完成後,直接更新 prometheus 這個 CRD 資源物件即可:

$ kubectl apply -f prometheus-prometheus.yaml
prometheus.monitoring.coreos.com "k8s" configured

隔一小會兒,可以前往 Prometheus 的 Dashboard 中檢視配置已經生效了:

但是我們切換到 targets 頁面下面卻並沒有發現對應的監控任務,檢視 Prometheus 的 Pod 日誌:

$ kubectl logs -f prometheus-k8s-0 prometheus -n monitoring
......
level=error ts=2020-04-18T02:38:27.800Z caller=klog.go:94 component=k8s_client_runtime func=ErrorDepth msg="/app/discovery/kubernetes/kubernetes.go:261: Failed to list *v1.Endpoints: endpoints is forbidden: User \"system:serviceaccount:monitoring:prometheus-k8s\" cannot list resource \"endpoints\" in API group \"\" at the cluster scope"
level=error ts=2020-04-18T02:38:27.801Z caller=klog.go:94 component=k8s_client_runtime func=ErrorDepth msg="/app/discovery/kubernetes/kubernetes.go:263: Failed to list *v1.Pod: pods is forbidden: User \"system:serviceaccount:monitoring:prometheus-k8s\" cannot list resource \"pods\" in API group \"\" at the cluster scope"

可以看到有很多錯誤日誌出現,都是 xxx is forbidden,這說明是 RBAC 許可權的問題,通過 prometheus 資源物件的配置可以知道 Prometheus 綁定了一個名為 prometheus-k8s 的 ServiceAccount 物件,而這個物件繫結的是一個名為 prometheus-k8s 的 ClusterRole:(prometheus-clusterRole.yaml)

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus-k8s
rules:
- apiGroups:
  - ""
  resources:
  - nodes/metrics
  verbs:
  - get
- nonResourceURLs:
  - /metrics
  verbs:
  - get

上面的許可權規則中我們可以看到明顯沒有對 Service 或者 Pod 的 list 許可權,所以報錯了,要解決這個問題,我們只需要新增上需要的許可權即可:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus-k8s
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  - services
  - endpoints
  - pods
  - nodes/proxy
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - configmaps
  - nodes/metrics
  verbs:
  - get
- nonResourceURLs:
  - /metrics
  verbs:
  - get

更新上面的 ClusterRole 這個資源物件,然後重建下 Prometheus 的所有 Pod,正常就可以看到 targets 頁面下面有 kubernetes-endpoints 這個監控任務了:

這裡發現的幾個抓取目標是因為 Service 中都有 prometheus.io/scrape=true 這個 annotation。

資料持久化

上面我們在修改完許可權的時候,重啟了 Prometheus 的 Pod,如果我們仔細觀察的話會發現我們之前採集的資料已經沒有了,這是因為我們通過 prometheus 這個 CRD 建立的 Prometheus 並沒有做資料的持久化,我們可以直接檢視生成的 Prometheus Pod 的掛載情況就清楚了:

$ kubectl get pod prometheus-k8s-0 -n monitoring -o yaml
......
    volumeMounts:
    - mountPath: /etc/prometheus/config_out
      name: config-out
      readOnly: true
    - mountPath: /prometheus
      name: prometheus-k8s-db
......
  volumes:
......
  - emptyDir: {}
    name: prometheus-k8s-db
......

我們可以看到 Prometheus 的資料目錄 /prometheus 實際上是通過 emptyDir 進行掛載的,我們知道 emptyDir 掛載的資料的生命週期和 Pod 生命週期一致的,所以如果 Pod 掛掉了,資料也就丟失了,這也就是為什麼我們重建 Pod 後之前的資料就沒有了的原因,對應線上的監控資料肯定需要做資料的持久化的,同樣的 prometheus 這個 CRD 資源也為我們提供了資料持久化的配置方法,由於我們的 Prometheus 最終是通過 Statefulset 控制器進行部署的,所以我們這裡通過 storageclass 來做資料持久化,同樣還是直接使用前面課程中的 Ceph RBD 的 rook-ceph-block 這個StorageClass 物件,此外由於 Prometheus 本身對 NFS 儲存沒有做相關的支援,所以線上一定不要用 NFS 來做資料持久化,對於如何去為 prometheus 這個 CRD 物件配置儲存資料,我們可以去檢視官方文件 API,也可以用 kubectl explain 命令去了解:

$ kubectl explain prometheus.spec.storage
KIND:     Prometheus
VERSION:  monitoring.coreos.com/v1

RESOURCE: storage <Object>

DESCRIPTION:
     Storage spec to specify how storage shall be used.

FIELDS:
   emptyDir     <Object>
     EmptyDirVolumeSource to be used by the Prometheus StatefulSets. If
     specified, used in place of any volumeClaimTemplate. More info:
     https://kubernetes.io/docs/concepts/storage/volumes/#emptydir

   volumeClaimTemplate  <Object>
     A PVC spec to be used by the Prometheus StatefulSets.

所以我們在 prometheus 的 CRD 物件中通過 storage 屬性配置 volumeClaimTemplate 物件即可:(prometheus-prometheus.yaml)

......
storage:
  volumeClaimTemplate:
    spec:
      storageClassName: rook-ceph-block
      resources:
        requests:
          storage: 20Gi

然後更新 prometheus 這個 CRD 資源,更新完成後會自動生成兩個 PVC 和 PV 資源物件:

$ kubectl apply -f prometheus-prometheus.yaml 
prometheus.monitoring.coreos.com/k8s configured
$ kubectl get pvc -n monitoring                              
NAME                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
prometheus-k8s-db-prometheus-k8s-0   Bound    pvc-79ad4856-2ab0-4445-814f-958a4699fab9   20Gi       RWO            rook-ceph-block   56s
prometheus-k8s-db-prometheus-k8s-1   Bound    pvc-8eae438e-bf7f-41a3-ae58-d7018c727866   20Gi       RWO            rook-ceph-block   55s
$ kubectl get pv |grep monitoring
pvc-79ad4856-2ab0-4445-814f-958a4699fab9   20Gi       RWO            Retain           Bound      monitoring/prometheus-k8s-db-prometheus-k8s-0          rook-ceph-block            90s
pvc-8eae438e-bf7f-41a3-ae58-d7018c727866   20Gi       RWO            Retain           Bound      monitoring/prometheus-k8s-db-prometheus-k8s-1          rook-ceph-block            90s

現在我們再去看 Prometheus Pod 的資料目錄就可以看到是關聯到一個 PVC 物件上了:

$ kubectl get pod prometheus-k8s-0 -n monitoring -o yaml
......
    volumeMounts:
    - mountPath: /etc/prometheus/config_out
      name: config-out
      readOnly: true
    - mountPath: /prometheus
      name: prometheus-k8s-db
......
  volumes:
......
  - name: prometheus-k8s-db
    persistentVolumeClaim:
      claimName: prometheus-k8s-db-prometheus-k8s-0
......

現在即使我們的 Pod 掛掉了,資料也不會丟失了。到這裡 Prometheus Operator 的一些基本配置就算完成了,對於大型的監控叢集還需要做一些其他配置,比如前面我們學習的使用 Thanos 來做 Prometheus 叢集的高可用已經資料遠端儲存,對於 Prometheus Operator 來說,要配置 Thanos 也比較簡單,因為 prometheus 這個 CRD 物件本身也支援的:

$ kubectl explain prometheus.spec.thanos                     
KIND:     Prometheus
VERSION:  monitoring.coreos.com/v1

RESOURCE: thanos <Object>

DESCRIPTION:
     Thanos configuration allows configuring various aspects of a Prometheus
     server in a Thanos environment. This section is experimental, it may change
     significantly without deprecation notice in any release. This is
     experimental and may change significantly without backward compatibility in
     any release.

FIELDS:
   baseImage    <string>
     Thanos base image if other than default.

   grpcServerTlsConfig  <Object>
     GRPCServerTLSConfig configures the gRPC server from which Thanos Querier
     reads recorded rule data. Note: Currently only the CAFile, CertFile, and
     KeyFile fields are supported. Maps to the '--grpc-server-tls-*' CLI args.

   image        <string>
     Image if specified has precedence over baseImage, tag and sha combinations.
     Specifying the version is still necessary to ensure the Prometheus Operator
     knows what version of Thanos is being configured.

   listenLocal  <boolean>
     ListenLocal makes the Thanos sidecar listen on loopback, so that it does
     not bind against the Pod IP.

   objectStorageConfig  <Object>
     ObjectStorageConfig configures object storage in Thanos.

   resources    <Object>
     Resources defines the resource requirements for the Thanos sidecar. If not
     provided, no requests/limits will be set

   sha  <string>
     SHA of Thanos container image to be deployed. Defaults to the value of
     `version`. Similar to a tag, but the SHA explicitly deploys an immutable
     container image. Version and Tag are ignored if SHA is set.

   tag  <string>
     Tag of Thanos sidecar container image to be deployed. Defaults to the value
     of `version`. Version is ignored if Tag is set.

   tracingConfig        <Object>
     TracingConfig configures tracing in Thanos. This is an experimental
     feature, it may change in any upcoming release in a breaking way.

   version      <string>
     Version describes the version of Thanos to use.

關於 prometheus operator 中如何配置 thanos,可以檢視官方文件的介紹:https://github.com/coreos/prometheus-operator/blob/master/Documentation/thanos.md。

我們可以看到上面的屬性中有一個 objectStorageConfig 欄位,該欄位也就是用來指定物件儲存相關配置的,這裡同樣我們使用前面 Thanos 章節中的物件儲存配置即可:(thanos-storage-minio.yaml)

type: s3
config:
  bucket: promethes-operator-data  # 記得在 minio 中建立這個 bucket
  endpoint: minio.minio.svc.cluster.local:9000
  access_key: minio
  secret_key: minio123
  insecure: true
  signature_version2: false

使用上面的配置檔案建立一個對應的 Secret 資源物件:

$ kubectl -n monitoring create secret generic thanos-objectstorage --from-file=thanos.yaml=thanos-storage-minio.yaml
secret/thanos-objectstorage created

建立完成後在 prometheus 的 CRD 物件中新增如下配置:(prometheus-prometheus.yaml )

thanos:
  objectStorageConfig:
    key: thanos.yaml
    name: thanos-objectstorage

然後直接更新 prometheus 這個 CRD 物件即可:

$ kubectl apply -f prometheus-prometheus.yaml 
prometheus.monitoring.coreos.com/k8s configured
$ kubectl get pods -n monitoring -l app=prometheus
NAME               READY   STATUS    RESTARTS   AGE
prometheus-k8s-0   4/4     Running   1          11m
prometheus-k8s-1   4/4     Running   0          11m

更新完成後,可以看到 Prometheus 的 Pod 變成了4個容器,新增了一個 sidecar 容器:

$ kubectl get pod prometheus-k8s-0 -n monitoring -o yaml
......
  - args:
    - sidecar
    - --prometheus.url=http://localhost:9090/
    - --tsdb.path=/prometheus
    - --grpc-address=[$(POD_IP)]:10901
    - --http-address=[$(POD_IP)]:10902
    - --objstore.config=$(OBJSTORE_CONFIG)
    env:
    - name: POD_IP
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: status.podIP
    - name: OBJSTORE_CONFIG
      valueFrom:
        secretKeyRef:
          key: thanos.yaml
          name: thanos-objectstorage
    image: quay.io/thanos/thanos:v0.11.0
    imagePullPolicy: IfNotPresent
    name: thanos-sidecar
    ports:
    - containerPort: 10902
      name: http
      protocol: TCP
    - containerPort: 10901
      name: grpc
      protocol: TCP
......

這個其實和前面課程中學習的手動方式部署 Thanos 非常類似,現在相當於我們將 Prometheus 的資料輸出到了遠端物件儲存上面去了,但是這還只是第一步,我們還需要部署其他的 Thanos 元件,比如 Querier、Store、Compactor 等,這些內容其實前面章節我們已經詳細講解過了,這裡我們就不再贅述了。

最後我們檢視下面我們的 prometheus 的 CRD 物件的完整配置:

apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
  labels:
    prometheus: k8s
  name: k8s
  namespace: monitoring
spec:
  alerting:
    alertmanagers:
    - name: alertmanager-main
      namespace: monitoring
      port: web
  image: quay.io/prometheus/prometheus:v2.15.2
  nodeSelector:
    kubernetes.io/os: linux
  podMonitorNamespaceSelector: {}
  podMonitorSelector: {}
  replicas: 2
  retention: 6h  # 本地只保留6h小時的資料
  resources:
    requests:
      memory: 400Mi
  ruleSelector:
    matchLabels:
      prometheus: k8s
      role: alert-rules
  securityContext:
    fsGroup: 2000
    runAsNonRoot: true
    runAsUser: 1000
  serviceAccountName: prometheus-k8s
  serviceMonitorNamespaceSelector: {}
  serviceMonitorSelector: {}
  version: v2.15.2
  additionalScrapeConfigs:  # 新增服務發現的配置
    name: additional-configs
    key: prometheus-additional.yaml
  storage:  # 新增本地資料持久化
    volumeClaimTemplate:
      spec:
        storageClassName: rook-ceph-block
        resources:
          requests:
            storage: 20Gi
  thanos:  #  新增 thanos 配置
    objectStorageConfig: 
      key: thanos.yaml
      name: thanos-objectstorage  # 物件儲存對應的 secret 資源物件

當然如果最後配置了 Thanos,那麼 Grafana 的資料來源也需要更改成 Querier 元件的地址,否則就只是本地的資料。