監控作為底層基礎設施的一環,是保障生產環境服務穩定性不可或缺的一部分,線上問題從發現到定位再到解決,通過監控和告警手段可以有效地覆蓋了「發現」和「定位」,甚至可以通過故障自愈等手段實現解決,服務開發和運維人員能及時有效地發現服務運行的異常,從而更有效率地排查和解決問題。 一個典型的監控(如白盒監控),通常會關注于目標服務的內部狀態,例如: 單位時間接收到的請求數量 單位時間內請求的成功率/失敗率 請求的平均處理耗時 白盒監控很好地描述了系統的內部狀態,但缺少從外部角度看到的現象,比如:白盒監控只能看到已經接收的請求,并不能看到由于 DNS 故障導致沒有發送成功的請求,而黑盒監控此時便可以作為補充手段,由探針(probe)程序來探測目標服務是否成功返回,更好地反饋系統的當前狀態。 某日需要為服務搭建一個監控系統來采集應用埋點上報的指標,經過一番對比,最終選擇了 Prometheus 來作為我們的業務監控,因為它具有以下優點: 支持 PromQL(一種查詢語言),可以靈活地聚合指標數據 部署簡單,只需要一個二進制文件就能跑起來,不需要依賴分布式存儲 Go 語言編寫,組件更方便集成在同樣是Go編寫項目代碼中 原生自帶 WebUI,通過 PromQL 渲染時間序列到面板上 生態組件眾多,Alertmanager,Pushgateway,Exporter…… Prometheus 的架構圖如下: 在上面流程中,Prometheus 通過配置文件中指定的服務發現方式來確定要拉取監控指標的目標(Target),接著從要拉取的目標(應用容器和Pushgateway)發起HTTP請求到特定的端點(Metric Path),將指標持久化至本身的TSDB中,TSDB最終會把內存中的時間序列壓縮落到硬盤,除此之外,Prometheus 會定期通過 PromQL 計算設置好的告警規則,決定是否生成告警到 Alertmanager,后者接收到告警后會負責把通知發送到郵件或企業內部群聊中。 Prometheus 的指標名稱只能由 ASCII 字符、數字、下劃線以及冒號組成,而且有一套命名規范: 使用基礎 Unit(如 seconds 而非 milliseconds) 指標名以 application namespace 作為前綴,如: process_cpu_seconds_total http_request_duration_seconds 用后綴來描述 Unit,如: http_request_duration_seconds node_memory_usage_bytes http_requests_total process_cpu_seconds_total foobar_build_info Prometheus 提供了以下基本的指標類型: Counter:代表一種樣本數據單調遞增的指標,即只增不減,通常用來統計如服務的請求數,錯誤數等。 Gauge:代表一種樣本數據可以任意變化的指標,即可增可減,通常用來統計如服務的CPU使用值,內存占用值等。 Histogram 和 Summary:用于表示一段時間內的數據采樣和點分位圖統計結果,通常用來統計請求耗時或響應大小等。 Prometheus 是基于時間序列存儲的,首先了解一下什么是時間序列,時間序列的格式類似于(timestamp,value)這種格式,即一個時間點擁有一個對應值,例如生活中很常見的天氣預報,如:[(14:00,27℃),(15:00,28℃),(16:00,26℃)],就是一個單維的時間序列,這種按照時間戳和值存放的序列也被稱之為向量(vector)。 再來舉另一個例子,如上圖所示,假如有一個指標 http_requests,它的作用是統計每個時間段對應的總請求量是多少,這時候它即為上面提到的是一個單維矩陣,而當我們給這個指標加上一個維度:主機名,這時候這個指標的作用就變成了統計每個時間段各個主機名對應的請求量是多少,這時候這個矩陣區域就變成擁有多列向量(每一列對應一個主機名)的時間序列,當給這個時間序列再添加多個標簽(key=value)時,這個矩陣就相應會變成一個多維矩陣。 每一組的標簽集合對應著一個的向量(vector),也可叫做一個時間序列(Time Serie),當在某一個時間點來看它時,它是一個瞬時向量(Instant Vector),瞬時向量的時序只有一個時間點以及它對于的一個值,比如:今天 12:05:30 時服務器的 CPU 負載;而在一個時間段來看它時,它是一個范圍向量(Range Vector),范圍向量對于著一組時序數據,比如:今天11:00到12:00時服務器的CPU負載。 類似的,可以通過指標名和標簽集來查詢符合條件的時間序列: http_requests{host="host1",service="web",code="200",env="test"} 查詢結果會是一個瞬時向量: http_requests{host="host1",service="web",code="200",env="test"} 10 http_requests{host="host2",service="web",code="200",env="test"} 0 http_requests{host="host3",service="web",code="200",env="test"} 12 而如果給這個條件加上一個時間參數,查詢一段時間內的時間序列: http_requests{host="host1",service="web",code="200",env="test"}[:5m] 結果將會是一個范圍向量: http_requests{host="host1",service="web",code="200",env="test"} 0 4 6 8 10 http_requests{host="host2",service="web",code="200",env="test"} 0 0 0 0 0 http_requests{host="host3",service="web",code="200",env="test"} 0 2 5 9 12 擁有了范圍向量,我們是否可以針對這些時間序列進行一些聚合運算呢?沒錯,PromQL就是這么干的,比如我們要算最近5分鐘的請求增長速率,就可以拿上面的范圍向量加上聚合函數來做運算: rate(http_requests{host="host1",service="web",code="200",env="test"}[:5m]) 比如要求最近5分鐘請求的增長量,可以用以下的 PromQL: increase(http_requests{host="host1",service="web",code="200",env="test"}[:5m]) 要計算過去10分鐘內第90個百分位數: histogram_quantile(0.9, rate(employee_age_bucket_bucket[10m])) 在 Prometheus 中,一個指標(即擁有的標簽集的 metric)和一個(timestamp,value)組成了一個樣本(sample),Prometheus 將采集的樣本放到內存中,默認每隔2小時將數據壓縮成一個 block,持久化到硬盤中,樣本的數量越多,Prometheus占用的內存就越高,因此在實踐中,一般不建議用區分度(cardinality)太高的標簽,比如:用戶IP,ID,URL地址等等,否則結果會造成時間序列數以指數級別增長(label數量相乘)。 除了控制樣本數量和大小合理之外,還可以通過降低 storage.tsdb.min-block-duration 來加快數據落盤時間和增加 scrape interval 的值提高拉取間隔來控制 Prometheus 的占用內存。 通過聲明配置文件中的 scrape_configs 來指定 Prometheus 在運行時需要拉取指標的目標,目標實例需要實現一個可以被 Prometheus 進行輪詢的端點,而要實現一個這樣的接口,可以用來給 Prometheus 提供監控樣本數據的獨立程序一般被稱作為 Exporter,比如用來拉取操作系統指標的 Node Exporter,它會從操作系統上收集硬件指標,供 Prometheus 來拉取。 在開發環境,往往只需要部署一個 Prometheus 實例便可以滿足數十萬指標的收集。但在生產環境中,應用和服務實例數量眾多,只部署一個 Prometheus 實例通常是不夠的,比較好的做法是部署多個Prometheus實例,每個實例通過分區只拉取一部分指標,例如Prometheus Relabel配置中的hashmod功能,可以對拉取目標的地址進行hashmod,再將結果匹配自身ID的目標保留: relabel_configs: - source_labels: [__address__] modulus: 3 target_label: __tmp_hash action: hashmod - source_labels: [__tmp_hash] regex: $(PROM_ID) action: keep 或者說,我們想讓每個 Prometheus 拉取一個集群的指標,一樣可以用 Relabel 來完成: relabel_configs: - source_labels: ["__meta_consul_dc"] regex: "dc1" action: keep 現在每個 Prometheus 都有各自的數據了,那么怎么把他們關聯起來,建立一個全局的視圖呢?官方提供了一個做法:聯邦集群(federation),即把 Prometheuse Server 按照樹狀結構進行分層,根節點方向的 Prometheus 將查詢葉子節點的 Prometheus 實例,再將指標聚合返回。 不過顯然易見的時,使用聯邦集群依然不能解決問題,首先單點問題依然存在,根節點掛了的話查詢將會變得不可用,如果配置多個父節點的話又會造成數據冗余和抓取時機導致數據不一致等問題,而且葉子節點目標數量太多時,更加會容易使父節點壓力增大以至打滿宕機,除此之外規則配置管理也是個大麻煩。 還好社區出現了一個 Prometheus 的集群解決方案:Thanos,它提供了全局查詢視圖,可以從多臺Prometheus查詢和聚合數據,因為所有這些數據均可以從單個端點獲取。 Querier 收到一個請求時,它會向相關的 Sidecar 發送請求,并從他們的 Prometheus 服務器獲取時間序列數據。 它將這些響應的數據聚合在一起,并對它們執行 PromQL 查詢。它可以聚合不相交的數據也可以針對 Prometheus 的高可用組進行數據去重。 再來說到存儲,Prometheus 查詢的高可用可以通過水平擴展+統一查詢視圖的方式解決,那么存儲的高可用要怎么解決呢?在 Prometheus 的設計中,數據是以本地存儲的方式進行持久化的,雖然本地持久化方便,當也會帶來一些麻煩,比如節點掛了或者 Prometheus 被調度到其他節點上,就會意味著原節點上的監控數據在查詢接口中丟失,本地存儲導致了 Prometheus 無法彈性擴展,為此 Prometheus 提供了 Remote Read 和 Remote Write 功能,支持把 Prometheus 的時間序列遠程寫入到遠端存儲中,查詢時可以從遠端存儲中讀取數據。 其中一個例子中就是M3DB,M3DB是一個分布式的時間序列數據庫,它提供了Prometheus的遠程讀寫接口,當一個時間序列寫入到M3DB集群后會按照分片(Shard)和復制(Replication Factor)參數把數據復制到集群的其他節點上,實現存儲高可用。除了M3DB外,Prometheus目前還支持InfluxDB、OpenTSDB等作為遠程寫的端點。 解決了 Prometheus 的高可用,再來關注一下 Prometheus 如何對監控目標進行采集,當監控節點數量較小時,可以通過 Static Config 將目標主機列表寫到 Prometheus 的拉取配置中,但如果目標節點一多的話這種方式管理便有很大問題了,而且在生產環境中,服務實例的IP通常不是固定的,這時候用靜態配置就沒辦法對目標節點進行有效管理,這時候 Prometheus 提供的服務發現功能便可以有效解決監控節點狀態變化的問題,在這種模式下, Prometheus 會到注冊中心監聽查詢節點列表,定期對節點進行指標的拉取。 如果對服務發現有更靈活的需求,Prometheus 也支持基于文件的服務發現功能,這時候我們可以從多個注冊中心中獲取節點列表,再通過自己的需求進行過濾,最終寫入到文件,這時候 Prometheus 檢測到文件變化后便能動態地替換監控節點,再去拉取目標了。 前面看到 Prometheus 都是以拉模式定期對目標節點進行抓取的,那假如有一種情況是一些任務節點還沒來得及被拉取就運行完退出了,這時候監控數據就會丟失,為了應對這種情況,Prometheus 提供了一個工具:Pushgateway,用來接收來自服務的主動上報,它適用于那些短暫存活的批量任務來將指標推送并暫存到自身上,借著再由Prometheus 來拉取自身,以防止指標還沒來得及被 Prometheus 拉取便退出。 除此以外 Pushgateway 也適用于在 Prometheus 與應用節點運行在異構網絡或被防火墻隔絕時,無法主動拉取節點的問題,在這種情況下應用節點可以通過使用Pushgateway的域名將指標推送到Pushgateway實例上,Prometheus就可以拉取同網絡下的Pushgateway 節點了,另外配置拉取 Pushgateway 時要注意一個問題:Prometheus 會把每個指標賦予 job 和instance標簽,當Prometheus拉取Pushgateway時,job和instance則可能分別是Pushgateway和Pushgateway主機的ip,當pushgateway上報的指標中也包含job和instance標簽時,Prometheus會把沖突的標簽重命名為exported_job和exported_instance,如果需要覆蓋這兩個標簽的話,需要在Prometheus中配置honor_labels: true。 Pushgateway可以替代拉模型來作為指標的收集方案,但在這種模式下會帶來許多負面影響: Pushgateway 被設計為一個監控指標的緩存,這意味著它不會主動過期服務上報的指標,這種情況在服務一直運行的時候不會有問題,但當服務被重新調度或銷毀時,Pushgateway 依然會保留著之前節點上報的指標。而且,假如多個 Pushgateway 運行在LB下,會造成一個監控指標有可能出現在多個 Pushgateway 的實例上,造成數據重復多份,需要在代理層加入一致性哈希路由來解決。 在拉模式下,Prometheus可以更容易的查看監控目標實例的健康狀態,并且可以快速定位故障,但在推模式下,由于不會對客戶端進行主動探測,因此對目標實例的健康狀態也變得一無所知。 最后再來聊一下Alertmanager,簡單說 Alertmanager 是與 Prometheus 分離的告警組件,主要接收 Promethues 發送過來的告警事件,然后對告警進行去重,分組,抑制和發送,在實際中可以搭配 webhook 把告警通知發送到企業微信或釘釘上,其架構圖如下: 最后的最后再來嘗試一下用 Kubernetes 來搭建一套 Prometheus 的監控系統,關于Kubernetes 也是摸爬滾打折騰了一周才清楚怎么使用的,雖然 Promehteus 已經有官方的 Operator 了,但是為了學習都用手動編寫 yaml 文件,整個完成下來發現還是挺方便的,而且只需要用幾個實例就可以完成收集監控200+服務數千個實例的業務指標。 為了部署 Prometheus 實例,需要聲明 Prometheus 的 StatefulSet,Pod 中包括了三個容器,分別是 Prometheus 以及綁定的 Thanos Sidecar,最后再加入一個 watch 容器,來監聽 prometheus 配置文件的變化,當修改 ConfigMap 時就可以自動調用Prometheus 的 Reload API 完成配置加載,這里按照之前提到的數據分區的方式,在Prometheus 啟動前加入一個環境變量 PROM_ID,作為 Relabel 時 hashmod 的標識,而 POD_NAME 用作 Thanos Sidecar 給 Prometheus 指定的 external_labels.replica 來使用: apiVersion: apps/v1 kind: StatefulSet metadata: name: prometheus labels: app: prometheus spec: serviceName: "prometheus" updateStrategy: type: RollingUpdate replicas: 3 selector: matchLabels: app: prometheus template: metadata: labels: app: prometheus thanos-store-api: "true" spec: serviceAccountName: prometheus volumes: - name: prometheus-config configMap: name: prometheus-config - name: prometheus-data hostPath: path: /data/prometheus - name: prometheus-config-shared emptyDir: {} containers: - name: prometheus image: prom/prometheus:v2.11.1 args: - --config.file=/etc/prometheus-shared/prometheus.yml - --web.enable-lifecycle - --storage.tsdb.path=/data/prometheus - --storage.tsdb.retention=2w - --storage.tsdb.min-block-duration=2h - --storage.tsdb.max-block-duration=2h - --web.enable-admin-api ports: - name: http containerPort: 9090 volumeMounts: - name: prometheus-config-shared mountPath: /etc/prometheus-shared - name: prometheus-data mountPath: /data/prometheus livenessProbe: httpGet: path: /-/healthy port: http - name: watch image: watch args: ["-v", "-t", "-p=/etc/prometheus-shared", "curl", "-X", "POST", "--fail", "-o", "-", "-sS", "http://localhost:9090/-/reload"] volumeMounts: - name: prometheus-config-shared mountPath: /etc/prometheus-shared - name: thanos image: improbable/thanos:v0.6.0 command: ["/bin/sh", "-c"] args: - PROM_ID=`echo $POD_NAME| rev | cut -d '-' -f1` /bin/thanos sidecar --prometheus.url=http://localhost:9090 --reloader.config-file=/etc/prometheus/prometheus.yml.tmpl --reloader.config-envsubst-file=/etc/prometheus-shared/prometheus.yml env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name ports: - name: http-sidecar containerPort: 10902 - name: grpc containerPort: 10901 volumeMounts: - name: prometheus-config mountPath: /etc/prometheus - name: prometheus-config-shared mountPath: /etc/prometheus-shared 因為 Prometheus 默認是沒辦法訪問 Kubernetes 中的集群資源的,因此需要為之分配RBAC: apiVersion: v1 kind: ServiceAccount metadata: name: prometheus --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: prometheus namespace: default labels: app: prometheus rules: - apiGroups: [""] resources: ["services", "pods", "nodes", "nodes/proxy", "endpoints"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["configmaps"] verbs: ["create"] - apiGroups: [""] resources: ["configmaps"] resourceNames: ["prometheus-config"] verbs: ["get", "update", "delete"] - nonResourceURLs: ["/metrics"] verbs: ["get"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: prometheus namespace: default labels: app: prometheus subjects: - kind: ServiceAccount name: prometheus namespace: default roleRef: kind: ClusterRole name: prometheus apiGroup: "" 接著 Thanos Querier 的部署比較簡單,需要在啟動時指定 store 的參數為dnssrv+thanos-store-gateway.default.svc來發現Sidecar: apiVersion: apps/v1 kind: Deployment metadata: labels: app: thanos-query name: thanos-query spec: replicas: 2 selector: matchLabels: app: thanos-query minReadySeconds: 5 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: app: thanos-query spec: containers: - args: - query - --log.level=debug - --query.timeout=2m - --query.max-concurrent=20 - --query.replica-label=replica - --query.auto-downsampling - --store=dnssrv+thanos-store-gateway.default.svc - --store.sd-dns-interval=30s image: improbable/thanos:v0.6.0 name: thanos-query ports: - containerPort: 10902 name: http - containerPort: 10901 name: grpc livenessProbe: httpGet: path: /-/healthy port: http --- apiVersion: v1 kind: Service metadata: labels: app: thanos-query name: thanos-query spec: type: LoadBalancer ports: - name: http port: 10901 targetPort: http selector: app: thanos-query --- apiVersion: v1 kind: Service metadata: labels: thanos-store-api: "true" name: thanos-store-gateway spec: type: ClusterIP clusterIP: None ports: - name: grpc port: 10901 targetPort: grpc selector: thanos-store-api: "true" 部署Thanos Ruler:apiVersion: apps/v1 kind: Deployment metadata: labels: app: thanos-rule name: thanos-rule spec: replicas: 1 selector: matchLabels: app: thanos-rule template: metadata: labels: labels: app: thanos-rule spec: containers: - name: thanos-rule image: improbable/thanos:v0.6.0 args: - rule - --web.route-prefix=/rule - --web.external-prefix=/rule - --log.level=debug - --eval-interval=15s - --rule-file=/etc/rules/thanos-rule.yml - --query=dnssrv+thanos-query.default.svc - --alertmanagers.url=dns+http://alertmanager.default ports: - containerPort: 10902 name: http volumeMounts: - name: thanos-rule-config mountPath: /etc/rules volumes: - name: thanos-rule-config configMap: name: thanos-rule-config 部署 Pushgateway: apiVersion: apps/v1 kind: Deployment metadata: labels: app: pushgateway name: pushgateway spec: replicas: 15 selector: matchLabels: app: pushgateway template: metadata: labels: app: pushgateway spec: containers: - image: prom/pushgateway:v1.0.0 name: pushgateway ports: - containerPort: 9091 name: http resources: limits: memory: 1Gi requests: memory: 512Mi --- apiVersion: v1 kind: Service metadata: labels: app: pushgateway name: pushgateway spec: type: LoadBalancer ports: - name: http port: 9091 targetPort: http selector: app: pushgateway 部署 Alertmanager: apiVersion: apps/v1 kind: Deployment metadata: name: alertmanager spec: replicas: 3 selector: matchLabels: app: alertmanager template: metadata: name: alertmanager labels: app: alertmanager spec: containers: - name: alertmanager image: prom/alertmanager:latest args: - --web.route-prefix=/alertmanager - --config.file=/etc/alertmanager/config.yml - --storage.path=/alertmanager - --cluster.listen-address=0.0.0.0:8001 - --cluster.peer=alertmanager-peers.default:8001 ports: - name: alertmanager containerPort: 9093 volumeMounts: - name: alertmanager-config mountPath: /etc/alertmanager - name: alertmanager mountPath: /alertmanager volumes: - name: alertmanager-config configMap: name: alertmanager-config - name: alertmanager emptyDir: {} --- apiVersion: v1 kind: Service metadata: labels: name: alertmanager-peers name: alertmanager-peers spec: type: ClusterIP clusterIP: None selector: app: alertmanager ports: - name: alertmanager protocol: TCP port: 9093 targetPort: 9093 最后部署一下 ingress,大功告成: apiVersion: extensions/v1beta1 kind: Ingress metadata: name: pushgateway-ingress annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/upstream-hash-by: "$request_uri" nginx.ingress.kubernetes.io/ssl-redirect: "false" spec: rules: - host: $(DOMAIN) http: paths: - backend: serviceName: pushgateway servicePort: 9091 path: /metrics --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: prometheus-ingress annotations: kubernetes.io/ingress.class: "nginx" spec: rules: - host: $(DOMAIN) http: paths: - backend: serviceName: thanos-query servicePort: 10901 path: / - backend: serviceName: alertmanager servicePort: 9093 path: /alertmanager - backend: serviceName: thanos-rule servicePort: 10092 path: /rule - backend: serviceName: grafana servicePort: 3000 path: /grafana 訪問 Prometheus 地址,監控節點狀態正常: 來源:https://zhuanlan.zhihu.com/p/101184971 聲明:文章收集于網絡,版權歸原作者所有,為傳播信息而發,如有侵權,請聯系小編刪除,謝謝! |
關于我們 | 新手指南 | 企業合作 | 聯系我們 |
|
||||
關于我們 | 教育模式 | 企業會員 | 電話:4008-010-006 | |||||
講師招募 | 選課流程 | 內訓合作 | 郵箱:kefu@dataguru.cn | |||||
校園大使 | 學費返還 | 媒體合作 | 客服QQ: |
|||||
版權聲明 | 獎學金激勵 | 代理合作 | 售后QQ: |