Escribir un exportador GeoIP para Prometheus con visualizaciones en Grafana en 15 minutos


Hola a todos!


Quiero compartir con ustedes lo fácil que es escribir su exportador para Prometheus en Golang y mostrar cómo se puede hacer esto usando un ejemplo de un pequeño programa que monitorea desde dónde se instalan geográficamente las conexiones TCP actuales.


0. Descargo de responsabilidad


Me gustaría describir de inmediato, al principio, delinear, por así decirlo, el alcance de esta publicación y decir que no dice nada , para que luego no haya preguntas:


  • Sí, esto no es una visualización de clientes . Esta es una visualización de conexiones remotas . Es decir, no divide las conexiones en aquellas en las que el servidor remoto inició la conexión y las que inició esta máquina, y mostrará todo en el mapa, por ejemplo, el servidor con el repositorio, donde las actualizaciones se descargarán a su máquina a partir de ahora.
  • Sí, entiendo que hay herramientas de anonimato en la red que ocultan la IP real del cliente. El propósito de esta herramienta no es identificar las coordenadas GPS exactas de ningún cliente, sino tener al menos una idea general de su geografía.
  • whois proporciona información más precisa que el país de la dirección IP, pero aquí estaba conectado por el límite del complemento para Grafan, que representa solo países, pero no ciudades.

1. Escribimos "back-end": el exportador en marcha


Entonces, lo primero que debemos hacer es escribir un exportador que realmente recolecte datos de nuestro servidor y los envíe a Prometheus. La elección de los idiomas es excelente: Prometheus tiene bibliotecas cliente para escribir exportadores en muchos idiomas populares, pero elegí Go, en primer lugar, porque es muy "más nativo" (ya que Prometheus está escrito en él), y en segundo lugar, porque es Lo uso en mi práctica de DevOps.


Bueno, suficiente letra, vamos al código. Comencemos a escribir “de abajo hacia arriba”: primero, las funciones para determinar el país por IP y la lista de direcciones IP remotas, y luego enviarlo todo a Prometheus.


1.1. Determinamos el país por dirección IP


Bueno, hay absolutamente todo en la frente, no filosofé y solo utilicé el servicio freegeoip.net , cuya API al momento de escribir este artículo ya había quedado en desuso, y ahora ofrecen registrarse de forma gratuita y poder hacer 10,000 solicitudes por mes (lo cual es suficiente para nuestros propósitos ) Aquí todo es simple: hay un punto final del formulario http://api.ipstack.com/<IP>?access_key=<API_KEY> , que simplemente nos devuelve json con el campo country_code que necesitamos, eso es todo lo que necesitamos para la visualización.
Entonces, escribamos un paquete para extraer el país por IP.


Importamos las librerías necesarias y creamos una estructura en la que el objeto json resultante se 'desempaquetará'.
 // 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"` } 

... y la función en sí, que nos devolverá el código del país.
 // 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 } 

Presta atención al parámetro legacy=1 , tengo que usarlo para compatibilidad con versiones anteriores; Por supuesto, si usa su API, use la última versión.


1.2. Crear una lista de conexiones TCP


Aquí usaremos el paquete github.com/shirou/gopsutil/net y github.com/shirou/gopsutil/net las conexiones con el estado ESTABLISHED , excluyendo las direcciones IP locales y las direcciones de una lista negra personalizada que se puede pasar al exportador al inicio (por ejemplo, para excluir todas sus propias direcciones IP públicas)


Paquete con función que devuelve map [string] int: número de conexiones desde el país.
 // 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. Y finalmente, envía todo a Prometeo


Más precisamente, él mismo tomará todo. Simplemente escucharemos el puerto y le daremos las métricas recopiladas.
Usando github.com/prometheus/client_golang/prometheus cree una métrica de tipo Gauge . En realidad, podría crear Counter , en ese momento usaríamos rate al consultar la base de datos. Quizás este último sea más efectivo desde el punto de vista de Prometheus, pero mientras escribía este exportador (hace medio año) estaba empezando a familiarizarme con Prometheus y Gauge fue suficiente para mí:


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

Una vez recopiladas las métricas utilizando los párrafos anteriores, actualizamos nuestro vector:


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

Comenzamos todo esto con un bucle sin fin en una rutina diferente, y solo vinculamos el puerto en el principal y esperamos a que Prometheus tome nuestras métricas:


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

En realidad, todo el código se puede ver en el repositorio en GitHub , no quiero copiar todo aquí en una fila.


2. "Front-end": Grafana


Pero primero, por supuesto, debe decirle a Prometheus que recopile nuestras métricas:


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

(o usando el descubrimiento de servicios, si tiene, por ejemplo, Kubernetes). Se puede hacer que Prometheus vuelva a leer la configuración enviándole una señal HUP :


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

Accedemos a él en la interfaz de usuario y verificamos que se recopilan las métricas:



Bien, ahora es el turno de Grafan. Usamos el grafana-worldmap-panel , que debe estar preinstalado:


 $ grafana-cli plugins install grafana-worldmap-panel 

A continuación, vaya a ella en la interfaz de usuario y haga clic en Agregar panel -> Panel de Worldmap. En la pestaña Métricas, ingrese la siguiente consulta:


 sum(job_location) by (location) 

Y especifique el formato de leyenda: {{location}} . Todo debería verse así:



A continuación, vaya a la pestaña Worldmap y configure todo como en la captura de pantalla:



¡Y eso es todo! Disfruta nuestro mapa.


De una manera tan simple, puedes hacer un hermoso mapa de conexiones en Grafan.


Gracias por su atención y esperamos sus comentarios.


Todo


Por supuesto, para utilizar la herramienta para el propósito previsto, debe completarla: filtrar las direcciones de subredes locales y mucho más. Por cierto, si alguien está interesado y quiere desarrollar este exportador, ¡bienvenido al repositorio en GitHub!



Source: https://habr.com/ru/post/es420633/


All Articles