Background
公司使用 Prometheus 和 Grafana 来对线上服务进行监控,这篇文章希望介绍一下这两个工具,并用简单的例子来学习一下这两个工具的使用方法
Prometheus简介
Prometheus由SoundCloud发布,是一套由go语言开发的开源的监控&报警&时间序列数据库的组合。
Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。这样做非常适合做虚拟化环境监控系统,比如VM、Docker、Kubernetes等。
输出被监控组件信息的HTTP接口被叫做exporter。目前互联网公司常用的组件大部分都有exporter可以直接使用,比如Varnish、Haproxy、Nginx、MySQL、Linux系统信息(包括磁盘、内存、CPU、网络等等)。
基本架构
Prometheus 主要的组件功能如下:
- Prometheus Server:server的作用主要是定期从静态配置的targets或者服务发现(主要是DNS、consul、k8s、mesos等)的 targets 拉取数据。
- Exporter: 主要负责向prometheus server做数据汇报。而不同的数据汇报由不同的exporters实现,比如监控主机有node-exporters,mysql有MySQL server exporter。
- Pushgateway:Prometheus获得数据的方式除了到对应exporter去Pull,还可以由服务先Push到pushgateway,server再去pushgateway 拉取。
- Alertmanager:实现prometheus的告警功能。
- webui:主要通过grafana来实现webui展示。
我们在实际使用的时候的基本流程就是:
各个服务push监控数据到其对应的指标(比如下面提到的http_requests_total
) --> Prometheus Server定时采集数据并存储 --> 配置Grafana展示数据 & 配置告警规则进行告警
常用指标
Prometheus 存储的是时序数据, 即按照相同时序(相同的名字和标签),以时间维度存储连续的数据的集合。时序(time series) 是由名字(Metric),以及一组 key/value 标签定义的,具有相同的名字以及标签属于相同时序。时序的名字由 ASCII 字符,数字,下划线,以及冒号组成,它必须满足正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*
, 其名字应该具有语义化,一般表示一个可以度量的指标,例如 http_requests_total
, 可以表示 http 请求的总数。
时序的标签可以使 Prometheus 的数据更加丰富,能够区分具体不同的实例,例如 http_requests_total{method="POST"}
可以表示所有 http 中的 POST 请求。标签名称由 ASCII 字符,数字,以及下划线组成, 其中 __
开头属于 Prometheus 保留,标签的值可以是任何 Unicode 字符,支持中文。
Prometheus常用的时序指标有以下4种
Ps.Prometheus客户端中提供4种指标类型,但是Prometheus的服务端并不区分指标类型,而是简单地把这些指标统一视为无类型的时间序列。
-
Counter: 计数器表示一种单调递增的指标,除非发生重置的情况下下只增不减,其样本值应该是不断增大的。适合用来表示服务的请求数、已完成的任务数、错误发生的次数等
-
Gauge: 仪表盘类型代表一种样本数据可以任意变化的指标,即可增可减。通常用于表示温度或者内存使用率这种指标数据。
-
Histogram:直方图主要用于表示一段时间范围内对数据进行采样,(通常是请求持续时间或响应大小),并能够对其指定区间以及总数进行统计。
直接上图更好理解一点,histogram由一系列不同范围的bucket组成,代表落在不同范围里的数据有多少(累计直方图),还有sum(总响应时间)和count(总次数)
- Summary:Summary 和 Histogram 类似,但Summary类型是在客户端直接聚合生成的百分位数
中位数(quantile=0.5)inerval长度为15.000115,全部数据耗时178380.0282111002s,一共有11892条数据
Histogram
能利用histogram_quantile函数计算百分位数但精度受分桶影响很大,分桶少的话会使百分位数计算很不准确,而分桶多的话会使数据量成倍增加。Summary
则是依靠原始数据计算出的百分位数,是很准确的值。但summary无法聚合,所以一般不怎么使用。
Prometheus使用
Prometheus的使用也较为简单
首先使用Docker部署一个Prometheus服务
1、本地创建 prometheus.yml
配置文件主要分为四个模块,这里只配置了数据源,到localhost:8000端口(宿主机)拉取数据。 9090服务器本身的一些数据。
global: 全局配置(如果有内部单独设定,会覆盖这个参数)
alerting: 告警插件定义。这里会设定alertmanager这个报警插件。
rule_files: 告警规则。 按照设定参数进行扫描加载,用于自定义报警规则,其报警媒介和route路由由alertmanager插件实现。
scrape_configs:采集配置。配置数据源,包含分组job_name以及具体target。又分为静态配置和服务发现
# my global confi
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']
- job_name: 'client'
static_configs:
- targets: ['host.docker.internal:8000']
docker使用起来确实方便,但是每次都会因为网络的问题卡半天,需要下来好好学习一下了。
一开始client配置的localhost:8000,然后代码上报数据放在宿主机,prometheus一直访问不到上报的数据,提示dial 127.0.0.1:8000被拒绝。
后面调了半天,把域名改成host.docker.internal才能获取到宿主机的数据。原因是docker内部和宿主机网络是隔离的,localhost是容器自己的网络。
2、拉取官方最新的prometheus镜像并使用我们写好的配置启动
docker run --name prometheus --restart=always -d -p 9090:9090 -p 8000:8000 \
-v /Users/zhifeng.wei/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus:latest --config.file=/etc/prometheus/prometheus.yml
这里把prometheus的配置文件映射到了本地的配置文件(上面配置的yml),需要改成你的文件路径
此时访问localhost可以访问到prometheus server的网站了,第一个数据源是我们配置的localhost:9090/metrics,第二个数据源是localhost:8000,后面我们上报的数据可以上报到这里
3、Go实现数据上报
这里实际上就是实现了一个exporter,prometheus会定时来8000端口拉取数据,exporter的handler会负责提供数据
import(
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main(){
// new一个counter,包含有taskName,status,errCode,msg等标签
opsProcessed := promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "test_ops_total",
Help: "The total number of processed events",
},
[]string{"taskName", "status", "errCode", "msg"},
)
// 注册指标,不使用默认的注册器
reg := prometheus.NewRegistry()
reg.MustRegister(opsProcessed)
go func() {
for {
fmt.Println("report")
opsProcessed.WithLabelValues("opsProcessed_task", "success", "1", "test").Inc()
time.Sleep(2 * time.Second)
}
}()
// 访问/metric会触发handler获得数据
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe("localhost:8000", nil)
}
运行程序,浏览器访问localhost:8000/metrics,可以看到上报的时序数据,重启会清除(如果不做持久化的话)
Grafana 简介
有了数据之后,还是希望可以用更好的方式可视化展示监控数据,这时候就需要Grafana了。
Grafana 是一个监控仪表系统,它是由 Grafana Labs 公司开源的的一个系统监测工具,它可以大大帮助我们简化监控的复杂度,我们只需要提供需要监控的数据,它就可以帮助生成各种可视化仪表,同时它还有报警功能,可以在系统出现问题时发出通知。
Grafana支持很多的数据源,包括我们前面说到的prometheus。
docker部署Grafana
我们使用doker-compose来一起部署prometheus和grafana docker-compose.yml
version: '2'
networks:
monitor:
driver: bridge
services:
prometheus:
image: prom/prometheus
container_name: prometheus
hostname: prometheus
restart: always
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
- "8000:8000"
networks:
- monitor
grafana:
image: grafana/grafana
container_name: grafana
hostname: grafana
restart: always
ports:
- "3000:3000"
networks:
- monitor
启动服务
docker-compose up -d
现在prometheus启动在9090端口,grafana启动在3000端口
grafana的默认用户密码是admin/admin
然后设置数据源prometheus
配置好数据源后可以配置仪表盘
根据自己需要配置监控数据,这里监控的是test_ops_total这个指标过去1min的增量。
系统指标 这里有系统默认上报的一些指标的含义
常用函数
监控图可以使用PromQL的一些函数对时序数据进行处理,下面介绍一些常用的函数。 参考资料
rate,irate函数
:
rate(v range-vector) 函数可以直接计算区间向量 v 在时间窗口内平均每秒增长速率
irate(v range-vector) 函数用于计算区间向量的增长率,但是其反应出的是瞬时增长率。
它们的计算方法有所不同:irate取的是在指定时间范围内的最后两个数据点来算速率,而rate会取指定时间范围内所有数据点,算出一组速率,然后取平均值作为结果。 两者的作用类似,使用rate函数计算的是样本的平均增长速率,容易陷入“长尾问题”当中,其无法反应在时间窗口内样本数据的突发变化。 例如,对于主机而言在2分钟的时间窗口内,可能在某一个由于访问量或者其它问题导致CPU占用100%的情况,但是通过计算在时间窗口内的平均增长率却无法反应出该问题。 irate 反应出的是瞬时增长,能用于绘制快速变化的计数器,但是在长期趋势分析或者告警中更推荐使用 rate 函数。
rate(http_requests_total[5m])
increase
:
increase(v range-vector),参数v是一个区间向量,increase函数获取区间向量中的第一个后最后一个样本并返回其增长量。因此,可以通过以下表达式Counter类型指标的增长数量(时间序列每2min内的增长量)
increase(node_cpu[2m])
histogram_quantile:
histogram_quantile(φ float, b instant-vector)函数可以进行计算Histogram的分位数,其中φ(0<φ<1)表示需要计算的分位数。
下面统计的95%分位置的数目,需要注意的是通过histogram_quantile计算的分位数,并非为精确值
histogram_quantile(0.95, http_request_duration_seconds_bucket)
QA
1、前面配置的prometheus配置中,容器访问宿主机网络是通过host.docker.internal
,但这种方式只能在mac和window使用,不适合用在生产环境
解决方法: Docker启动的时候会在主机上自动创建一个docker0网络,实际上是一个Linux网桥
ip addr show docker0
将host.docker.internal更换成这个网桥ip即可访问宿主机
2、使用gin框架上报的话,不需要像上面一样使用
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe("localhost:8000", nil)
可以直接使用gin进行上报,prometheus服务配置文件改成对应端口即可
s.GET("metrics", gin.WrapH(promhttp.Handler()))
3、linux 进入容器的命令 docker exec -it 容器id sh