创造新看点
首页 >> 科技咖 >> 正文

也可叫做一个时间序列,一个典型的监控

日期:2021-04-06 12:37:53 来源:互联网 编辑:小狐 阅读人数:917

监控作为底层基础设施的一环,是保障生产环境服务稳定性不可或缺的一部分,线上问题从发现到定位再到解决,通过监控和告警手段可以有效地覆盖了发现和定位,甚至可以通过故障自愈等手段实现解决,服务和运维人员能及时有效地发现服务运行的异常,从而更有效率地排查和解决问题。

一、Prometheus介绍

单位时间接收到的请求数量

单位时间内请求的成功率/失败率

请求的平均处理耗时

白盒监控很好地描述了的内部状态,但缺少从外部角度看到的现象,比如:白盒监控只能看到已经接收的请求,并不能看到由于DNS故障导致没有发送成功的请求,而黑盒监控此时便可以作为补充手段,由探针(probe)程序来探测目标服务是否成功更好地反馈的当前状态。

某日需要为服务搭建一个监控来采集应用埋点上报的指标,经过一番对比,最终选择了Prometheus来作为我们的业务监控,因为它具有以下优点:

支持PromQL(一种查询语言)可以灵活地聚合指标数据。

部署简单,只需要一个二进制文件就能跑起来,不需要依赖分布式存储。

Go语言编写,组件更方便集成在同样是Go编写项目代码中。

原生自带WebUI,通过PromQL渲染时间序列到面板上。

生态组件众多,Alertmanager,Pushgateway,Exporter...。

也可叫做一个时间序列,一个典型的监控(图1)

在上面流程中,Prometheus通过配置文件中指定的服务发现方式来确定要拉取监控指标的目标(Target)接着从要拉取的目标(应用容器和Pushgateway)发起HTTP请求到特定的端点(Metric Path)将指标持久化至本身的TSDB中,TSDB最终会把内存中的时间序列压缩落到硬盘,除此之外,Prometheus会定期通过PromQL计算设置好的告警规则,决定是否生成告警到Alertmanager,后者接收到告警后会负责把发送到或企业内部群聊中。

Prometheus的指标名称只能由 ASCII 字符、数字、下划线以及冒号组成,而且有一套命名规范:

使用基础Unit(如seconds而非milliseconds)

Counter:代表一种样本数据单调递增的指标,即只增不减,通常用来统计如服务的请求数,错误数等。

Gauge:代表一种样本数据可以任意变化的指标,即可增可减,通常用来统计如服务的CPU使用值,内存占用值等。

Histogram和Summary:用于表示一段时间内的数据采样和点分位图统计结果,通常用来统计请求耗时或响应大小等。

也可叫做一个时间序列,一个典型的监控(图2)

每一组唯一的集合对应着一个唯一的向量(vector)也可叫做一个时间序列(Time Serie)当在某一个时间点来看它时,它是一个瞬时向量(Instant Vector)瞬时向量的时序只有一个时间点以及它对于的一个值,比如:今天 :30 时的 CPU 负载;而在一个时间段来看它时,它是一个范围向量(Range Vector)范围向量对于着一组时序数据,比如:今天到时的CPU负载。

类似的,可以通过指标名和集来查询符合条件的时间序列:

查询结果会是一个瞬时向量:

而如果给这个条件加上一个时间参数,查询一段时间内的时间序列:

结果将会是一个范围向量:

拥有了范围向量,我们是否可以针对这些时间序列进行一些聚合运算呢?没错,PromQL就是这么干的,比如我们要算最近5分钟的请求增长速率,就可以拿上面的范围向量加上聚合函数来做运算:

比如要求最近5分钟请求的增长量,可以用以下的PromQL:

要计算过去10分钟内第90个百分位数:

histogram_quantile0.9, rateemployee_age_bucket_bucket【10m】

storage.tsdb.min-block-duration来加快数据落盘时间和增加scrape interval的值提高拉取间隔来控制Prometheus的占用内存。

通过配置文件中的scrape_configs来指定Prometheus在运行时需要拉取指标的目标,目标实例需要实现一个可以被Prometheus进行轮询的端点,而要实现一个这样的接口,可以用来给Prometheus监控样本数据的独立程序一般被称作为Exporter,比如用来拉取操作指标的Node Exporter,它会从操作上收集硬件指标,供Prometheus来拉取。

relabel_configs:

- source_labels: 【__address__】

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高可用

现在每个Prometheus都有各自的数据了,那么怎么把他们关联起来,建立一个全局的视图呢?了一个做法:联邦集群(federation)即把Prometheuse Server按照树状结构进行分层,根节点方向的Prometheus将查询叶子节点的Prometheus实例,再将指标聚合返回。

也可叫做一个时间序列,一个典型的监控(图3)

联邦集群

不过显然易见的时,使用联邦集群依然不能解决问题,首先单点问题依然存在,根节点挂了的话查询将会变得不可用,如果配置多个父节点的话又会造成数据冗余和抓取时机导致数据不一致等问题,而且叶子节点目标数量太多时,更加会容易使父节点压力增大以至打满宕机,除此之外规则配置也是个大麻烦。

还好社区出现了一个Prometheus的集群解决方案:Thanos,它了全局查询视图,可以从多台Prometheus查询和聚合数据,因为所有这些数据均可以从单个端点获取。

也可叫做一个时间序列,一个典型的监控(图4)

Thanos

Querier收到一个请求时,它会向相关的Sidecar发送请求,并从他们的Prometheus获取时间序列数据。

它将这些响应的数据聚合在一起,并对它们执行PromQL查询。它可以聚合不相交的数据也可以针对Prometheus的高可用组进行数据去重。

有了Thanos之后,Prometheus的水平扩展就能变得更加简单,不仅如此,Thanos还了可靠的数据存储方案,可以监听和备份prometheus本地数据到远程存储。另外,由于Thanos了Prometheus集群的全局视图,那么针对全局Prometheus的记录规则也不是问题,Thanos的Ruler组件,会基于Thanos Querier执行规则并发出告警。

三、Prometheus存储

再来说到存储,Prometheus查询的高可用可以通过水平扩展+统一查询视图的方式解决,那么存储的高可用要怎么解决呢?在Prometheus的设计中,数据是以本地存储的方式进行持久化的,虽然本地持久化方便,当也会带来一些麻烦,比如节点挂了或者Prometheus被调度到其他节点上,就会意味着原节点上的监控数据在查询接口中丢失,本地存储导致了Prometheus无法弹性扩展,为此Prometheus了Remote Read和Remote Write功能,支持把Prometheus的时间序列远程写入到远端存储中,查询时可以从远端存储中读取数据。

也可叫做一个时间序列,一个典型的监控(图5)

其中一个例子中就是M3DB,M3DB是一个分布式的时间序列数据库,它了Prometheus的远程读写接口,当一个时间序列写入到M3DB集群后会按照分片(Shard)和复制(Replication Factor)参数把数据复制到集群的其他节点上,实现存储高可用。除了M3DB外,Prometheus目前还支持InfluxDB、OpenTSDB等作为远程写的端点。

四、Prometheus采集数据方式

1、拉模式

Pushgateway

前面看到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,简单说Alertmanager是与Prometheus分离的告警组件,主要接收Promethues发送过来的告警事件,对告警进行去重,分组,抑制和发送,在实际中可以搭配webhook把告警发送到企业或钉钉上,其架构图如下:

也可叫做一个时间序列,一个典型的监控(图6)

六、Kubernetes搭建Prometheus监控

最后的最后再来尝试一下用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来使用:

kind: StatefulSet

metadata:

name: prometheus

labels:

app: prometheus

spec:

serviceName: “prometheus”

updateStrategy:

type: RollingUpdate

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

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:

volumeMounts:

- name: prometheus-config-shared

mountPath: /etc/prometheus-shared

- name: prometheus-data

mountPath: /data/prometheus

livenessProbe:

path: /-/healthy

- name: watch

image: watch

volumeMounts:

- name: prometheus-config-shared

mountPath: /etc/prometheus-shared

- name: thanos

command: “/bin/sh” “-c”

args:

- PROM_ID=`echo $POD_NAME rev cut -d “-” -f1` /bin/thanos sidecar

--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: grpc

volumeMounts:

- name: prometheus-config

mountPath: /etc/prometheus

- name: prometheus-config-shared

mountPath: /etc/prometheus-shared

因为Prometheus默认是没办法访问Kubernetes中的集群资源的,因此需要为之分配RBAC:

kind: ServiceAccount

metadata:

name: prometheus

---

kind: ClusterRole

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

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:

kind: Deployment

metadata:

labels:

app: thanos-query

name: thanos-query

spec:

selector:

matchLabels:

app: thanos-query

strategy:

type: RollingUpdate

rollingUpdate:

template:

metadata:

labels:

app: thanos-query

spec:

containers:

- args:

- query

- --log.level=debug

- --query.timeout=2m

- --query.replica-label=replica

- --query.auto-downsampling

- --store=dnssrv+thanos-store-gateway.default.svc

- --store.sd-dns-interval=30s

name: thanos-query

ports:

name: grpc

livenessProbe:

path: /-/healthy

---

kind: Service

metadata:

labels:

app: thanos-query

name: thanos-query

spec:

type: LoadBalancer

ports:

selector:

app: thanos-query

---

kind: Service

metadata:

labels:

thanos-store-api: “true”

name: thanos-store-gateway

spec:

type: ClusterIP

clusterIP: None

ports:

- name: grpc

targetPort: grpc

selector:

thanos-store-api: “true”

部署Thanos Ruler:

kind: Deployment

metadata:

labels:

app: thanos-rule

name: thanos-rule

spec:

selector:

matchLabels:

app: thanos-rule

template:

metadata:

labels:

labels:

app: thanos-rule

spec:

containers:

- name: thanos-rule

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

ports:

volumeMounts:

- name: thanos-rule-config

mountPath: /etc/rules

volumes:

- name: thanos-rule-config

configMap:

name: thanos-rule-config

部署Pushgateway:

kind: Deployment

metadata:

labels:

app: pushgateway

name: pushgateway

spec:

selector:

matchLabels:

app: pushgateway

template:

metadata:

labels:

app: pushgateway

spec:

containers:

name: pushgateway

ports:

resources:

limits:

memory: 1Gi

requests:

memory: 512Mi

---

kind: Service

metadata:

labels:

app: pushgateway

name: pushgateway

spec:

type: LoadBalancer

ports:

selector:

app: pushgateway

部署Alertmanager:

kind: Deployment

metadata:

name: alertmanager

spec:

selector:

matchLabels:

app: alertmanager

template:

metadata:

name: alertmanager

labels:

app: alertmanager

spec:

containers:

- name: alertmanager

args:

- --web.route-prefix=/alertmanager

- --config.file=/etc/alertmanager/config.yml

- --storage.path=/alertmanager

- --cluster.listen-address=0.0.0.

ports:

- name: alertmanager

volumeMounts:

- name: alertmanager-config

mountPath: /etc/alertmanager

- name: alertmanager

mountPath: /alertmanager

volumes:

- name: alertmanager-config

configMap:

name: alertmanager-config

- name: alertmanager

emptyDir: {}

---

kind: Service

metadata:

labels:

name: alertmanager-peers

name: alertmanager-peers

spec:

type: ClusterIP

clusterIP: None

selector:

app: alertmanager

ports:

- name: alertmanager

protocol: TCP

最后部署一下ingress,大功告成:

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)

paths:

- backend:

serviceName: pushgateway

path: /metrics

---

kind: Ingress

metadata:

name: prometheus-ingress

annotations:

kubernetes.io/ingress.class: “nginx”

spec:

rules:

- host: $(DOMAIN)

paths:

- backend:

serviceName: thanos-query

path: /

- backend:

serviceName: alertmanager

path: /alertmanager

- backend:

serviceName: thanos-rule

path: /rule

- backend:

serviceName: grafana

path: /grafana

本文相关词条概念解析:

节点

“节点”一概念被广泛应用于许多领域。电力学中,节点是塔的若干部件的汇合点。机械工程学中,节点是在一对相啮合的齿轮上,其两节圆的切点。在网络拓扑学中,节点是网络任何支路的终端或网络中两个或更多支路的互连公共点。生化工程中,代谢网络分流处的代谢产物称为节点。在程序语言中,节点是XML文件中有效而完整的结构的最小单元。在作图软件MAYA中,节点是最小的单位。每个节点都是一个属性组。节点可以输入,输出,保存属性。

网友评论