监控改造之路3

·

1 min read

也是没想到监控系列竟然断更如此之久,要做到笔耕不辍真的挺难,再一次反映自己行动力之差。话说人logseq里面的todo也是越积越多,眼看着2023年就快结束了,小破站虽然没啥人看,但这债,还是得快速还的。好,切入正题,上次写监控还是上次……

上篇提到主机监控,采用的是将主机的node-exporter写入到prometheus配置文件的方式,利用prom配置文件的热更新机制完成,这种方式是比较生硬的,不够优雅。一旦需要监控的主机数量多起来,就会带来一些问题,

  1. 每次要读写文件,速度是比较慢的,特别是当配置文件逐渐变大起来时

  2. 对于问题1,可以将配置文件拆分来缓解,拆分带来的配置的更多的管理和维护成本

  3. 操作文件意味着写文件的服务/进程需要跟prom的配置文件具体位置强关联,会带来其它问题,比如:读写配置文件的进程或者服务如果是多实例,需要处理资源争抢等问题

所以服务发现就有存在的必要的,具体而言,就是将需要监控的目标将给第三方服务来维护和管理,再将第三方服务的地址告诉prom,由prom通过第三方服务来获取需要监控的目标,例如比较流行的consul,以及上文提到的监控目标存在配置文件中,其实也是服务发现的一种。

但是团队的情况有一些复杂,因为早期在项目设计上技术调研上欠缺,导致整个项目引入了过多的中间件,例如zk、kafka、redis等,笔者进入这个团队以后,通过对整个项目的梳理,发现这些中间件并不是必须的,甚至于完全可以去掉,再加之开源竞品仅依赖一个MySQL,团队对于去中件间这件事显得不够有决心,但是对于引入新的中间件却是非常谨慎的。虽然我比较推荐使用consul,但是当时的一些情况,并没有得到支持,于是本着尽可能不引入新的中件间的原则,在架构师的坚持下,使用zk作为服务发现,但其实prom并不支持直接使用zk作为服务发现,不过支持一个叫做nerve的服务发现,使用zk作为存储,于是乎,通过走读promtheus的相关代码,使用一些骚操作,将需要监控的主机信息写入zk,以此支持将zk作为promtheus的服务发现,此事就此作罢。

从我的角度,这是一个非常不好的实践,为了之前错误的设计(过多的中间件使用),要采取一种社区少有人使用的服务发现机制,带来的隐患着实不小。

所以经过一番梳理,我发现平台需要监控的主机信息在主服务(下文以ms指代)中是能查到的,既然主服务中能查到,为何要劳什子地将这些信息写入zk呢?所以答案呼之欲出了,HTTP-based service discovery: ms提供一个查询监控主机信息的HTTP即可,以下是一个简单的示例:

代码示例:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type target struct {
    Targets []string          `json:"targets"`
    Labels  map[string]string `json:"labels"`
}

func main() {
    targets := []target{
        {
            Targets: []string{"172.88.1.71:8080", "172.88.1.56:8080"},
            Labels: map[string]string{
                "job": "myapp",
            },
        },
        {
            Targets: []string{"172.18.5.32:10250"},
            Labels: map[string]string{
                "job":              "myapp",
                "__scheme__":       "https",
                "__metrics_path__": "/metrics/cadvisor",
            },
        },
    }

    handler := func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(targets)
    }

    http.HandleFunc("/targets", handler)
    fmt.Println("Starting HTTP server on port 8080...")
    // ip:172.18.4.172
    http.ListenAndServe(":8080", nil)
}

相应地,在prometheus.yml中需要添加的配置如下所示:

scrape_configs:
  - job_name: myapp
    http_sd_configs:
    - url: http://172.18.4.172:8080/targets
      refresh_interval: 10s

这是一个相对接地气的方案,简单而有力。代码改动量也比较小,我比较惊讶的是,团队开发环境的主机不足30台,也就是说需要监控的主机数量不足30台,但这个接口的第一次响应查义时间竟然需要3s以上(http服务发现的方案是我出的,也给出了golang和prom配置示例,接口是其它同事在ms中实现的,ms是java写的) 。呃,又是历史债务么……


在如此少的监控目标下,prom的内存战用却意外的高,3G到8G的样子,我很吃惊,于是想用vm(victoriametrics)替换prom,在测试环境作对比,经过近两周的观察,vm完胜,但是却意外发现团队使用prom竟然还是两年前的版本,先前的对比就有些不讲武德了。本着公平的原则,将其升级到当时较新的版本2.45.0,经过一周的观察,监控等量的目标,vm与prom内存占用相当,甚至prom还略低于vm,虽然我是一个“激进”派,但此时,也不得不为prom而感动,一个老项目,能优化至此,实在是没理由不升级到2.45.0,这是一个令我印象深刻的prom版本……

你以为这就完事了?基于一些考量,最近我又谋划着使用vm……