Écrire un exportateur GeoIP pour Prometheus avec des visualisations dans Grafana en 15 minutes


Bonjour à tous!


Je veux partager avec vous combien il est facile d'écrire votre exportateur pour Prometheus sur Golang et montrer comment cela peut être fait en utilisant un exemple d'un petit programme qui surveille d'où les connexions TCP actuelles sont géographiquement installées.


0. Avertissement


Je voudrais immédiatement, au tout début, esquisser, pour ainsi dire, la portée de cette publication et dire qu'elle ne le dit pas , pour que plus tard il n'y ait plus de questions:


  • Oui, ce n'est pas une visualisation des clients . Il s'agit d'une visualisation des connexions distantes . Autrement dit, il ne divise pas les connexions entre celles dans lesquelles le serveur distant a initié la connexion et celles qui ont été lancées par cette machine, et affichera tout sur la carte - par exemple, le serveur avec le référentiel, où les mises à jour sont téléchargées à partir de maintenant sur votre machine.
  • Oui, je comprends qu'il existe des outils d'anonymisation sur le réseau qui cachent la véritable IP du client. Le but de cet outil n'est pas d'identifier les coordonnées GPS exactes d'un client, mais d'avoir au moins une idée générale de sa géographie.
  • whois fournit des informations plus précises que le pays de l'adresse IP, mais ici j'étais connecté par la limite du plugin pour Grafan, qui rend uniquement les pays, mais pas les villes.

1. Nous écrivons "back-end": l'exportateur en marche


Donc, la première chose que nous devons faire est d'écrire à un exportateur qui collectera réellement les données de notre serveur et les enverra à Prometheus. Le choix des langues est excellent: Prometheus a des bibliothèques clientes pour écrire des exportateurs dans de nombreuses langues populaires, mais j'ai choisi Go, premièrement parce qu'il est plus "natif" (puisque Prometheus y est écrit), et deuxièmement parce qu'il est lui-même J'utilise dans ma pratique DevOps.


Assez bien les paroles, passons au code. Commençons par écrire «de bas en haut»: tout d'abord, les fonctions de détermination du pays par IP et la liste des adresses IP distantes, puis tout envoyer à Prometheus.


1.1. Nous déterminons le pays par son adresse IP


Eh bien, il y a absolument tout sur le front, je n'ai pas philosophe et j'ai juste utilisé le service freegeoip.net , dont l'API était déjà obsolète au moment de la rédaction de cet article, et maintenant ils proposent de s'inscrire gratuitement et de pouvoir faire 10000 requêtes par mois (ce qui est suffisant pour nos besoins) ) Tout est simple ici: il y a un point de terminaison du formulaire http://api.ipstack.com/<IP>?access_key=<API_KEY> , qui nous renverra simplement json avec le champ country_code nous avons besoin - c'est tout ce dont nous avons besoin pour la visualisation.
Alors, écrivons un paquet pour tirer le pays par IP.


Nous importons les bibliothèques nécessaires et créons une structure dans laquelle l'objet json résultant sera «décompressé».
 // 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"` } 

... et la fonction elle-même, qui nous renverra le code du pays.
 // 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 } 

Faites attention au paramètre legacy=1 , je dois l'utiliser pour une compatibilité descendante; Bien sûr, si vous utilisez leur API, utilisez la dernière version.


1.2. Créer une liste de connexions TCP


Ici, nous allons utiliser le package github.com/shirou/gopsutil/net et filtrer les connexions avec le statut github.com/shirou/gopsutil/net , à l'exclusion des adresses IP locales et des adresses d'une liste noire personnalisée qui peut être transmise à l'exportateur au démarrage (par exemple, pour exclure toutes vos propres adresses IP publiques)


Package avec fonction renvoyant map [string] int: nombre de connexions depuis le pays.
 // 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. Et enfin, envoyez tout à Prométhée


Plus précisément, il prendra lui-même tout. Nous allons simplement écouter le port et lui donner les statistiques collectées.
En utilisant github.com/prometheus/client_golang/prometheus créez une métrique de type Gauge . En fait, vous pourriez créer Counter , juste à ce moment-là, nous utiliserions rate lors de la requête dans la base de données. Peut-être que ce dernier est plus efficace du point de vue de Prométhée, mais pendant que j'écrivais cet exportateur (il y a six mois), je commençais tout juste avec Prométhée et Gauge me suffisait:


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

Après avoir collecté les métriques à l'aide des paragraphes précédents, nous mettons à jour notre vecteur:


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

Nous commençons tout cela avec une boucle sans fin dans un goroutine séparé, et lions simplement le port dans le principal et attendons que Prometheus prenne nos métriques:


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

En fait, tout le code peut être consulté dans le référentiel sur GitHub , je ne veux pas tout copier ici de suite.


2. "Front-end": Grafana


Mais d'abord, bien sûr, vous devez dire à Prometheus de collecter nos métriques:


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

(ou en utilisant la découverte de service, si vous avez, par exemple, Kubernetes). Prométhée peut être fait pour relire la configuration en lui envoyant un signal HUP :


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

Nous y accédons dans l'interface utilisateur et vérifions que les métriques sont collectées:



D'accord, c'est maintenant au tour de Grafan. Nous utilisons le grafana-worldmap-panel , qui doit être pré-installé:


 $ grafana-cli plugins install grafana-worldmap-panel 

Ensuite, allez vers elle dans l'interface utilisateur et cliquez sur Ajouter un panneau -> Panneau Worldmap. Dans l'onglet Métriques, entrez la requête suivante:


 sum(job_location) by (location) 

Et spécifiez le format de légende: {{location}} . Tout devrait ressembler à ceci:



Ensuite, allez dans l'onglet Worldmap et configurez tout comme dans la capture d'écran:



Et c'est tout! Profitez de notre carte.


D'une manière si simple, vous pouvez faire une belle carte des connexions à Grafan.


Merci de votre attention et attendons vos commentaires avec impatience.


Todo


Bien sûr, pour utiliser l'outil à sa destination, vous devez le compléter: filtrer les adresses des sous-réseaux locaux et bien plus encore. Soit dit en passant, si quelqu'un est intéressé et souhaite développer cet exportateur - bienvenue dans le référentiel sur GitHub!



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


All Articles