Grafana-Variable配置小技巧

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 Dashboard是Grafana中非常重要的概念,每个Dashboard都是一个巨大的看板,在Dashboard上可以配置Panel(图表)。在Dashboard中有个特别的配置–Variables,Variables提供了用户和面板交互,可以动态刷新面板。 有些特殊的场景,需要有些小技巧,否则Variables仍然无法满足我们的要求。 1. Case 1 我要查看不同股票的日线图 1.1 Query SELECT trade_date AS "time", close FROM daily_data where ts_code = "$ts_code" order by trade_date ts_code是股票的代码,显然如果只看股票代码,很难想起这个代码究竟对应的是哪一只股票。 最好的方式是如下图这样,在变量的下拉列表中,显示股票名称,但是在实际的Query中使用股票代码查询。 grafana支持这种玩法,称为text和value 1.2 变量配置 注意需要Grafana的版本在7.4.5 或以上 1.2.1 配置Query select concat(name, "#", ts_code) from target; 这一步会查出形如下面的结果 中国平安#601318.SH 三一重工#600031.SH ... 1.2.2 分离出text和value 配置Regex /(?<text>.*)#(?<value>.*)/ 2. Case 2 我们有一组MySQL实例需要监控,MySQL的数量多达几十个。 但是实际上大家都知道,MySQL是主从的,1个主和多个从构成了1个集群。每个集群单独为1个业务提供服务。 MySQL实例如果这样列出来,萌叔根本不可能知道,这个实例属于哪个集群,支撑那个业务。 我们希望的效果,是先选择partner(业务方),然后再选择再从这个集群中选出关心的实例 2.1 Query 事实上,我们可以通过构建2个Variables来达到这个目标 2.1.1 Query1 label_values(mysql_up, partner) 得到变量partner 2.1.2 Query2 label_values(mysql_up{partner="$partner"}, instance) Query2引用Query1的结果 3. Case 3 萌叔的服务部署在2个机房,机房名称是由k8s的维护定义好的,形如 ...

November 18, 2021 · 1 min

由grafana-image-renderer引出的一个问题

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 前段时间利用grafana/grafana-image-renderer 做了一个自动截图服务。 见萌叔的文章抓取GRAFANA PANEL视图。但是我发现了一个问题(grafana版本v6.0.1),如果grafana的实例数量超过1个,在grafana中访问grafana render link时,就会有一定的概率失败,图片无法成功渲染。这到底是是为什么? 本文将给出完整的问题排查和解决过程,希望读者可以从中汲取一些养分,为以后排查其它问题提供一些思路。 2. 日志,一切从日志开始 看到这种问题,首先看看日志中是否有线索 2.1 首先看grafana的日志 只能了解到点击grafana render link之后,会触发一个GET请求,访问grafana服务的 /render/d-solo/P3a2p0cZz/redis-and-mysql-and-cache,然后引起一个内部错误 2.2 看看grafana-image-renderer的日志 我们已经知道,图片渲染的过程,grafana要调用grafana-image-renderer,让我们来看下grafana-image-renderer的日志。 需要注意,grafana-image-renderer的日志默认是打印在标准输出和标准错误输出里的。 对比成功和失败的case我发现,在失败的case中,日志中会多了一条401 (Unauthorized)的日志 对应的链接,就是我们要渲染成图片的panel所对应的网页地址 猜测1 现在我们可以猜测grafana-image-renderer可能是请求grafana服务对应的网页资源失败,然后导致图片渲染失败。 2.3 抓包,需要API入口 2.3.1 对grafana-image-renderer抓包 为了进一步验证这个上面的猜测,抓包看一下,grafana-image-renderer所收到的请求 萌叔抓包使用的是buger/goreplay ./gor --input-raw :8081 --output-stdout 1 1a1a5c7c2e733bf0ed41f7ce35bee28da845ab49 1616730655714291898 2541293 GET /render?domain=grafana.example.com&encoding=&height=500&renderKey=HPXorbVBhC6taLHFEe6Jg9O3e5w6R5xn&timeout=60&timezone=Asia%2FShanghai&url=http%3a%2f%2fgrafana.example.com%2fd-solo%2fP3a2p0cZz%2fredis-and-mysql-and-cache%3forgId%3d1%26from%3d1616719854090%26to%3d1616730654091%26var-instance%3d192.168.1.100%3a9090%26panelId%3d8%26width%3d1000%26height%3d500%26tz%3dAsia%2fShanghai%26render%3d1&width=1000 HTTP/1.1 X-Forwarded-Proto: http Host: grafana-image-renderer.example.com Connection: keep-alive User-Agent: Go-http-client/1.1 Accept-Encoding: gzip 展开来看 将url参数解码以后,确实就是grafana中panel对应的网页地址, 到这里猜测1,已经被证实了。 猜测2 请求的参数中有个renderKey非常的可疑,它会不会是请求grafana获取对应网页时,用来鉴权的token呢? 2.3.2 对grafana抓包 这里使用tcpdump抓取之后,然后用wireshark中观察 tcpdump -i eth0 host 192.168.1.102 and port 3000 -w /tmp/xxx.cap grafana-image-renderer请求grafana时,确实在Cookie中携带了renderkey。离真相越来越近了。 ...

March 26, 2021 · 3 min

抓取Grafana Panel视图

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 最近萌叔在做监控系统的改造,其中一个目标是,当Prometheus触发告警之后需要将Alert Rule对应的grafana图表以邮件的形式发送给处理人员。 2. grafana/grafana-image-renderer 经过搜索资料,萌叔发现grafana官方出了一个插件,可以直接用于抓取grafana的Panel图表。 传送门: grafana/grafana-image-renderer 它有2种运行方式 以grafana的插件方式运行 以外部服务的形式运行 这里以第2种方式展开。 2.1 docker模式运行 这个外部服务是一个nodejs的程序 node build/app.js server --config=config.json puppeteer很像,它接收一个URL地址作为参数,然后运行Headless Chrome,向目标地址发起请求,解析并渲染网页,最后截图。 Headless Chrome可以这样理解,无需显卡、显示器,Chrome以命令行程序的方式运行对网页的请求、渲染、截图等工作。详见参考资料4 Grafana官方提供了docker镜像 grafana/grafana-image-renderer 直接运行即可 2.2 Grafana配置 [rendering] # grafana image renderer服务地址 server_url = http://grafana-image-renderer.example.com:8081/render # grafana服务地址 callback_url = http://grafana.example.com:3000/ 3. 获取图片地址 点击share 蓝色区域是Panel的网页地址,红色区域是Panel的图片地址 图片地址形如: http://dev2:3000/render/d-solo/TBeJbKBmz/fake_service?refresh=1m&panelId=6&orgId=1&from=1611216312639&to=1611218112639&theme=dark&width=1000&height=500&tz=Asia%2FShanghai 注意: from、to参数可能需要修改 6小时图表 now-6h 至 now http://dev2:3000/render/d-solo/TBeJbKBmz/fake_service?refresh=1m&panelId=6&orgId=1&from=now-6h&to=now&theme=dark&width=1000&height=500&tz=Asia%2FShanghai 2天图表 now-2h 至 now http://dev2:3000/render/d-solo/TBeJbKBmz/fake_service?refresh=1m&panelId=6&orgId=1&from=now-2d&to=now&theme=dark&width=1000&height=500&tz=Asia%2FShanghai 4.总结 webhook服务只需要在alert被触发时,请求对应的图片地址,下载图片,再将图片打包在邮件中发送出来即可。 参考资料 puppeteer/puppeteer grafana/grafana-image-renderer Grafana Image Renderer headless chrome 请我喝瓶饮料

January 21, 2021 · 1 min

玩转Prometheus(1)--第1个例子

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 在工作的这几年里,接触不少监控系统, Nagios、Cacti、Zabbix、Open-falcon, 今年开始在新公司使用Prometheus, 网上有文章把Prometheus 称为新一代的监控系统,我一直很好奇,它的新体现在哪儿,相比与传统的监控系统,它有什么优势。 在经过一段时间的使用以后,我觉得我有了一些体会,下面我们通过1个例子来感受一下。 Prometheus的体系架构图 应用场景 从目前各个公司的实践情况来看,Prometheus主要用于应用服务的监控,尤其是基于docker的应用服务;而像主机的运行情况(cpu使用率、内存使用率),网络设备的监控等,依然由传统的监控系统来做。 1. 模拟的应用服务 假定我们有一个web服务叫fake_service fake_server.go package main import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "gopkg.in/gin-gonic/gin.v1" "strconv" "strings" "time" ) var ( //HTTPReqDuration metric:http_request_duration_seconds HTTPReqDuration *prometheus.HistogramVec //HTTPReqTotal metric:http_request_total HTTPReqTotal *prometheus.CounterVec ) func init() { // 监控接口请求耗时 // HistogramVec 是一组Histogram HTTPReqDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "The HTTP request latencies in seconds.", Buckets: nil, }, []string{"method", "path"}) // 这里的"method"、"path" 都是label // 监控接口请求次数 // HistogramVec 是一组Histogram HTTPReqTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total number of HTTP requests made.", }, []string{"method", "path", "status"}) // 这里的"method"、"path"、"status" 都是label prometheus.MustRegister( HTTPReqDuration, HTTPReqTotal, ) } // /api/epgInfo/1371648200 -> /api/epgInfo func parsePath(path string) string { itemList := strings.Split(path, "/") if len(path) >= 4 { return strings.Join(itemList[0:3], "/") } return path } //Metric metric middleware func Metric() gin.HandlerFunc { return func(c *gin.Context) { tBegin := time.Now() c.Next() duration := float64(time.Since(tBegin)) / float64(time.Second) path := parsePath(c.Request.URL.Path) // 请求数加1 HTTPReqTotal.With(prometheus.Labels{ "method": c.Request.Method, "path": path, "status": strconv.Itoa(c.Writer.Status()), }).Inc() // 记录本次请求处理时间 HTTPReqDuration.With(prometheus.Labels{ "method": c.Request.Method, "path": path, }).Observe(duration) } } func DealAPI1(c *gin.Context) { time.Sleep(time.Microsecond * 10) c.Writer.Write([]byte("/api/api1")) } func DealAPI2(c *gin.Context) { time.Sleep(time.Microsecond * 20) c.Writer.Write([]byte("/api/api2")) } func main() { router := gin.Default() g := router.Group("/api") g.Use(Metric()) g.GET("api1", DealAPI1) g.GET("api2", DealAPI2) // 暴露给Prometheus router.GET("/metrics", gin.WrapH(promhttp.Handler())) router.Run(":28181") } 使用fake_client.go模拟真实用户的请求 完整代码 用法: ...

January 3, 2019 · 2 min