Docker 部署 Prometheus 实现一个极简的 QPS 监控

2,711 阅读6分钟

背景 : Prometheus 是近年来最流行的开源监控框架, 其功能强大且易于使用, 拥有各种主流后端语言(Java/Go/Python/Node.js等)与各种场景(如web handler/ k8s/Nginx/MySQL等)的客户端, 并自带图形化显示页面。分享一个快速入门Prometheus 的教程, 实现一个极简的, 后端开发需要特别关注的 QPS 监控。

Docker 部署 Prometheus

命令行输入

docker run -d --name prometheus-node1  -p 9090:9090  bitnami/prometheus:latest

这条命令会创建一个名为 prometheus-node1 的容器, 使用 bitnami/prometheus:latest 的镜像, 宿主机的 9090 端口与容器内的9090端口相通。

修改 prometheus 配置文件

docker cp  prometheus-node1:/opt/bitnami/prometheus/conf/prometheus.yml prometheus.yml 

这将 prometheus-node1 容器内的 prometheus.yml 配置文件拷贝出来, 大概长这样:

# my global config
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']

简单看看这个配置文件里面最重要的两个配置。先看 global 下面的两个配置项, scrape_interval: 15 s 表示 每15秒获取一次监控指标(prometheus 中叫 target), evaluation_interval: 15s 表示 每15秒执行一次 rules。 scrape_configs 直接定义了监控的 target. job_name 为 这个 target的名字, static_configs 下面的 tartgets 直接指出了监控的 IP:端口。剩下的配置留给大家自己去学习,出于快速上手 Prometheus的目的,我就不细讲了。

我们下面修改一下 targets 配置, 变成这样:

# my global config
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: ['172.17.0.2:20001'] # 需要监控的 IP:端口

我们修改了 targets 配置, 将他修改成需要监控的 IP:端口, 这里的 172.17.0.2 为另外一个 docker 容器的 IP地址(待会会将), 20001 为要监控的端口(待会会将)

然后将修改后的 配置文件放回 docker 容器

docker cp prometheus.yml  prometheus-node1:/opt/bitnami/prometheus/conf/prometheus.yml 

再重启 容器

docker restart  prometheus-node1

写一个 Web Handler 和 Web Client

创建一个 prometheus_demo 目录, 命令行输入

go mod init prometheus_demo
go mod tidy 

文件目录如下:


-- prometheus_demo
    -- go.mod
    -- main.go
    -- client
       --- client.go
    

其中main.go 为 server 端, client.go 为客户端

其中 main.go 如下:

package main

import (
   "fmt"
   "github.com/prometheus/client_golang/prometheus"
   "github.com/prometheus/client_golang/prometheus/promhttp"
   "net/http"
   "time"
)

// 只可增加的一个计数器
var req_counter_vec = prometheus.NewCounterVec(
   prometheus.CounterOpts{
      Name: "req_counter_vec",
      Help: "request counter vector",
   },
   []string{"endpoint"},
)

func main() {

   prometheus.MustRegister(req_counter_vec)

   http.Handle("/metrics", promhttp.Handler())
   http.HandleFunc("/hello", HelloHandler)

   errChan := make(chan error)
   go func() {
      errChan <- http.ListenAndServe(":20001", nil)
   }()
   err := <-errChan
   if err != nil {
      fmt.Println("Hello server stop running.")
   }

}

func HelloHandler(w http.ResponseWriter, r *http.Request) {
   path := r.URL.Path
   req_counter_vec.WithLabelValues(path).Inc()
   time.Sleep(100 * time.Millisecond)
}

服务端比较简单, 定义了 一个 counter vector, 里面装的是prometheus 四种数据类型的 Counter。Name 为 vector 的 名字, help 为详细的解释, 都可以自取。[]string{"endpoint"} 表示以 endpoint 进行区分 vector 内不同的 counter。这就好比一个数组, 用 索引0,1,2 区分数组内的不同元素。

Counter 正如我注释里面写的, 就是一个计数器, 一次只能增加1, 比如每次来一个请求, 那么就增加1。与 Counter 相对的是 Prometheus 的四种数据类型中的 Gauge。 Gauge 可加可减。数据类型 name 为变量名字, help 为变量详细 解释, 随后将这个变量注册一下, 以便被 prometheus 监控到。当然还有另外两种用于直方图的 数据类型 Histogram 和 Summary, 我就不细说了。

随后定义了一个简单的 web handler, 里面干了两件事, 一件是记录将 counter 加 1, withLabelValues 就是就和刚才的 endpoint 相对应, 相当于标记一下这个 vector 中的 哪一个 counter 加一。 另一件事情就是休眠100ms, 不至于太快结束不利于观察。

client.go 如下

package main

import (
   "log"
   "net/http"
   "sync"
   "time"
)

func main() {

   for {

      wg := sync.WaitGroup{}

      for i := 0; i < 50; i++ {
         wg.Add(1)
         go func(i int) {
            defer wg.Done()
            resp, err := http.Get("http://localhost:20001/hello")
            if err != nil {
               log.Println(err)
               return
            }
            resp.Body.Close()
         }(i)
      }

      wg.Wait()
      time.Sleep(5 * time.Second)

   }

}

客户端就更简单了, 死循环里面开50个 go routine 不断发请求。

随后将 prometheus_demo 文件部署到 docker 中, 如何在 docker 中搭建 go 开发环境可以参考我的另一篇 文章: 保姆级从0到1讲解go远程开发环境搭建(从Docker安装到使用Goland远程部署和调试)

然后在docker容器中 prometheus_demo 目录 和 prometheus_demo/client 目录下 分别使用下面两个命令运行服务端和客户端

go run main.go
go run client.go

打开 Prometheus Web 界面

在宿主机上用浏览器打开 http://localhost:9090/targets?search= 如果可以观察到下面这样, 说明 prometheus 部署成功。 promethus_targets.PNG

注意,上面这幅图一定要启动 prometheus_demo 的 main.go 才能观察得到, 因为 prometheus 监控 20001 端口, 如果 server 端没启动, prometheus 当然啥都监控不到。

下面来看如何监控 QPS, 在宿主机上用浏览器打开, http://localhost:9090/graph

然后在放大镜旁边的框框内输入下面这一串指令

rate(req_counter_vec{endpoint="/hello"}[15s])

再点击 graph 应该看到下面这样类似的图片

promethus_graph.PNG

解释一下, rate(req_counter_vec{endpoint="/hello"}[15s]) 这句指令是什么意思。 req_counter_vec 就是之前定义的装 counter 的 vector, {endpoint="/hello"} 也就是 HelloHandler 里面记录请求次数的 那个counter, rate 接 [15s] 表示每15秒(和 配置文件里面的15秒保持一致)记录一下 counter 的变化情况(因为 counter只能增加, 所以变化为一个 非负数), 总请求次数除以时间段, 就是一个范围内的 QPS。我们这里并不是1秒, 而是15秒, 也就可以近似看作 QPS。

如果有同学发现没有图形出现, 显示 empty query result, 可能是北京时间和标准时间不同步, 可以勾选 use local time, 或者 调整一 图形界面的窗口时间(我图片上的 5m 和 2022-12-27 20:14:02 那里) 。

还有点同学出现的不是直方图而是一个个小的线段, 这是因为图形的不同展示方式的原因, 可以 点一下 Hide Exemplars 左边的两个小图标。

巨人的肩膀

yunlzheng.gitbook.io/prometheus-…

hub.docker.com/r/bitnami/p…

juejin.cn/post/707865…

cjting.me/2017/03/12/…