在15分钟内用Grafana编写具有可视化效果的Prometheus GeoIP导出器


大家好!


我想与您分享在Golang上编写Prometheus的导出器有多么容易,并用一个小程序的示例演示如何做到这一点,该程序监视当前TCP连接的地理位置。


0.免责声明


可以这么说,我想一开始就概述本出版物的范围,并说它没有说清楚,以便以后再没有问题:


  • 是的,这不是客户的可视化。 这是远程连接的可视化。 也就是说,它不会将连接分为远程服务器发起连接的连接和本机发起的连接,而是将在地图上显示所有内容-例如,具有存储库的服务器,从此处将更新下载到您的计算机。
  • 是的,我知道网络上有一些匿名工具可以隐藏客户端的真实IP。 该工具的目的不是要识别任何客户的确切GPS坐标,而是要至少了解他们的地理位置。
  • whois提供的信息比IP地址所在的国家/地区更为准确,但是在这里,我与Grafan插件的限制联系在一起,该插件仅显示国家/地区,而不显示城市。

1.我们写“后端”:出口商在忙


因此,我们需要做的第一件事是编写一个出口商,该出口商实际上将从我们的服务器收集数据并将其发送给Prometheus。 语言的选择非常好:Prometheus拥有用于以多种流行语言编写导出程序客户端库 ,但我选择Go,首先是因为它是如此“本地化”(因为Prometheus被写在上面),其次是因为它本身我在DevOps实践中使用。


足够好的歌词,让我们开始编写代码。 让我们开始写“自下而上”:首先,这些功能用于通过IP和远程IP地址列表确定国家,然后将其全部发送给Prometheus。


1.1。 我们通过IP地址确定国家


嗯,额头上绝对有所有东西,我没什么好想的 ,只是使用了freegeoip.net服务,在撰写本文时,其API已被弃用,现在他们提供免费注册,并且每月可以发出10,000个请求(这足以满足我们的目的) ) 一切都很简单:这里有一个http://api.ipstack.com/<IP>?access_key=<API_KEY>形式的终结点,它仅向我们返回带有我们所需country_code字段的country_code这就是可视化所需的全部内容。
因此,让我们编写一个通过IP拉动国家的软件包。


我们导入必要的库,并创建一个结构,将生成的json对象“解压”到该结构中。
 // Package geo implements function for searching // for a country code by IP address. package geo import ( "encoding/json" "fmt" "io/ioutil" "net/http" ) // Type GeoIP stores whois info. type GeoIP struct { Ip string `json:""` CountryCode string `json:"country_code"` CountryName string `json:""` RegionCode string `json:"region_code"` RegionName string `json:"region_name"` City string `json:"city"` Zipcode string `json:"zipcode"` Lat float32 `json:"latitude"` Lon float32 `json:"longitude"` MetroCode int `json:"metro_code"` AreaCode int `json:"area_code"` } 

...以及函数本身,它将国家代码返回给我们。
 // Function GetCode returns country code by IP address. func GetCode(address string) (string, error) { response, err = http.Get("http://api.ipstack.com/" + address + "?access_key=<API_KEY>&format=1&legacy=1") if err != nil { fmt.Println(err) return "", err } defer response.Body.Close() body, err = ioutil.ReadAll(response.Body) if err != nil { fmt.Println(err) return "", err } err = json.Unmarshal(body, &geo) if err != nil { fmt.Println(err) return "", err } return geo.CountryCode, nil } 

注意参数legacy=1 ,我必须使用它来实现向后兼容; 当然,如果您使用他们的API,请使用最新版本。


1.2。 创建TCP连接列表


在这里,我们将使用github.com/shirou/gopsutil/net软件包并过滤掉处于ESTABLISHED状态的连接,不包括本地IP地址和自定义黑名单中的地址,这些地址可以在启动时传递给导出器(例如,排除您自己的所有公共IP地址)


带有函数的包,返回地图[string] int:与国家/地区的连接数。
 // Package conn implements function for collecting // active TCP connections. package conn import ( "log" "github.com/gree-gorey/geoip-exporter/pkg/geo" "github.com/shirou/gopsutil/net" ) // Type Connections stores map of active connections: country code -> number of connections. type Connections struct { ConnectionsByCode map[string]int `json:"connections_by_code"` } // Function RunJob retrieves active TCP connections. func (c *Connections) RunJob(p *Params) { if p.UseWg { defer p.Wg.Done() } c.GetActiveConnections(p.BlackList) } // Function GetActiveConnections retrieves active TCP connections. func (c *Connections) GetActiveConnections(blackList map[string]bool) { cs, err := net.Connections("tcp") if err != nil { log.Println(err) } c.ConnectionsByCode = make(map[string]int) for _, conn := range cs { if _, ok := blackList[conn.Raddr.IP]; !ok && (conn.Status == "ESTABLISHED") && (conn.Raddr.IP != "127.0.0.1") { code, err := geo.GetCode(conn.Raddr.IP) if code != "" && err == nil { _, ok := c.ConnectionsByCode[code] if ok == true { c.ConnectionsByCode[code] += 1 } else { c.ConnectionsByCode[code] = 1 } } } } } 

1.3。 最后,将所有内容发送给普罗米修斯


更准确地说,他本人将承担一切。 我们只听端口并提供收集的指标。
使用github.com/prometheus/client_golang/prometheus创建一个Gauge类型的度量。 实际上,您可以创建Counter ,然后我们将在查询数据库时使用rate 。 从Prometheus的角度来看,后者也许更有效,但是在我写这个出口商的时候(六个月前),我才刚开始熟悉Prometheus,而Gauge对我来说已经足够了:


 location = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "job_location", Help: "Location connections number", }, []string{"location"}, ) 

使用前面的段落收集了指标之后,我们更新了向量:


 for code, number := range c.ConnectionsByCode { location.With(prometheus.Labels{"location": code}).Set(float64(number)) } 

我们以一个单独的goroutine中的无限循环开始所有这一切,然后将端口绑定到主端口中,然后等待Prometheus获取我们的指标:


 prometheus.MustRegister(location) http.Handle("/metrics", prometheus.Handler()) log.Fatal(http.ListenAndServe(*addr, nil)) 

实际上,所有代码都可以在GitHub存储库中查看,我不想在这里连续复制所有内容。


2.“前端”:Grafana


但是首先,当然,您需要告诉Prometheus收集我们的指标:


  - job_name: 'GeoIPExporter' scrape_interval: 10s static_configs: - targets: ['127.0.0.1:9300'] 

(或者,如果有Kubernetes,则使用服务发现)。 可以通过发送HUP信号使Prometheus重新读取配置:


 $ pgrep "^prometheus$" | xargs -i kill -HUP {} 

我们在用户界面中转到它并检查是否已收集指标:



好的,现在轮到Grafan了。 我们使用必须预先安装的grafana-worldmap-panel插件:


 $ grafana-cli plugins install grafana-worldmap-panel 

接下来,在用户界面中转到她,然后单击添加面板->世界地图面板。 在“指标”选项卡中,输入以下查询:


 sum(job_location) by (location) 

并指定图例格式: {{location}} 。 一切应该看起来像这样:



接下来,转到“世界地图”选项卡,并按照屏幕快照中的说明配置所有内容:



仅此而已! 享受我们的地图。


通过这种简单的方法,您可以在Grafan中绘制漂亮的连接图。


感谢您的关注,并期待您的评论。


待办


当然,为了将工具用于其预期目的,您需要完成它:过滤出本地子网的地址等等。 顺便说一句,如果有人感兴趣并想要开发此导出器-欢迎使用GitHub上的存储库!



Source: https://habr.com/ru/post/zh-CN420633/


All Articles