Schreiben eines GeoIP-Exporters für Prometheus mit Visualisierungen in Grafana in 15 Minuten


Hallo an alle!


Ich möchte Ihnen mitteilen, wie einfach es ist, Ihren Exporteur für Prometheus auf Golang zu schreiben, und anhand eines Beispiels eines kleinen Programms zeigen, wie dies überwacht werden kann, von wo aus die aktuellen TCP-Verbindungen geografisch installiert werden.


0. Haftungsausschluss


Ich möchte gleich zu Beginn sofort sozusagen den Umfang dieser Veröffentlichung skizzieren und sagen, dass sie nicht aussagt, so dass es später keine Fragen mehr gibt:


  • Ja, dies ist keine Visualisierung von Kunden . Dies ist eine Visualisierung von Remoteverbindungen . Das heißt, die Verbindungen werden nicht in diejenigen unterteilt, in denen der Remoteserver die Verbindung initiiert hat, und diejenigen, die von diesem Computer initiiert wurden, und es wird alles auf der Karte angezeigt - beispielsweise der Server mit dem Repository, auf dem von nun an Updates auf Ihren Computer heruntergeladen werden.
  • Ja, ich verstehe, dass es im Netzwerk Anonymisierungstools gibt, die die tatsächliche IP des Clients verbergen. Der Zweck dieses Tools besteht nicht darin, die genauen GPS-Koordinaten eines Kunden zu identifizieren, sondern zumindest eine allgemeine Vorstellung von dessen Geografie zu haben.
  • whois liefert Informationen, die genauer sind als das Land der IP-Adresse, aber hier war ich durch das Limit des Plugins für Grafan verbunden, das nur Länder, aber keine Städte wiedergibt.

1. Wir schreiben "Backend": Der Exporteur ist unterwegs


Als erstes müssen wir einen Exporteur schreiben, der tatsächlich Daten von unserem Server sammelt und an Prometheus sendet. Die Auswahl der Sprachen ist großartig: Prometheus hat Client-Bibliotheken zum Schreiben von Exporteuren in vielen gängigen Sprachen, aber ich habe Go gewählt, zum einen, weil es so "einheimischer" ist (da Prometheus darauf geschrieben ist), und zum anderen, weil es selbst Ich benutze in meiner DevOps-Praxis.


Gut genug Texte, lasst uns zum Code kommen. Beginnen wir mit dem Schreiben von „Bottom-up“: Zuerst die Funktionen zum Bestimmen des Landes anhand der IP-Adresse und der Liste der Remote-IP-Adressen und dann das Senden an Prometheus.


1.1. Wir bestimmen das Land anhand der IP-Adresse


Nun, es ist absolut alles in der Stirn, ich habe nicht philosophiert und nur den freegeoip.net- Dienst verwendet, dessen API zum Zeitpunkt des Schreibens dieses Artikels bereits veraltet war. Jetzt bieten sie an, sich kostenlos zu registrieren und 10.000 Anfragen pro Monat zu stellen (was für unsere Zwecke ausreicht) ) Hier ist alles einfach: Es gibt einen Endpunkt des Formulars http://api.ipstack.com/<IP>?access_key=<API_KEY> , der uns einfach json mit dem Feld country_code , das wir benötigen - das ist alles, was wir für die Visualisierung benötigen.
Schreiben wir also ein Paket, um das Land per IP zu ziehen.


Wir importieren die erforderlichen Bibliotheken und erstellen eine Struktur, in die das resultierende JSON-Objekt 'entpackt' wird.
 // 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"` } 

... und die Funktion selbst, die uns den Ländercode zurückgibt.
 // 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 } 

Achten Sie auf den Parameter legacy=1 , ich muss ihn aus Gründen der Abwärtskompatibilität verwenden. Wenn Sie ihre API verwenden, verwenden Sie natürlich die neueste Version.


1.2. Erstellen Sie eine Liste der TCP-Verbindungen


Hier verwenden wir das Paket github.com/shirou/gopsutil/net und filtern Verbindungen mit dem Status ESTABLISHED , wobei lokale IP-Adressen und Adressen aus einer benutzerdefinierten schwarzen Liste ausgeschlossen werden, die beim Start an den Exporteur übergeben werden können (z. B. um alle Ihre eigenen öffentlichen IP-Adressen auszuschließen).


Paket mit Funktion, die map [string] int zurückgibt: Anzahl der Verbindungen aus dem Land.
 // 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. Und zum Schluss senden Sie alles an Prometheus


Genauer gesagt wird er selbst alles nehmen. Wir hören uns nur den Port an und geben ihm die gesammelten Metriken.
Erstellen Sie mit github.com/prometheus/client_golang/prometheus eine Metrik vom Typ Gauge . Eigentlich könnten Sie einen Counter erstellen, genau dann würden wir rate verwenden rate wenn wir die Datenbank abfragen. Vielleicht ist letzteres aus Sicht von Prometheus effektiver, aber als ich diesen Exporteur schrieb (vor sechs Monaten), fing ich gerade an, Prometheus Gauge und Gauge war genug für mich:


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

Nachdem wir die Metriken unter Verwendung der vorherigen Absätze gesammelt haben, aktualisieren wir unseren Vektor:


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

Wir beginnen dies alles mit einer Endlosschleife in einer separaten Goroutine und binden einfach den Port in den Hauptport und warten darauf, dass Prometheus unsere Metriken übernimmt:


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

Eigentlich kann der gesamte Code im Repository auf GitHub angezeigt werden. Ich möchte hier nicht alles hintereinander kopieren.


2. "Frontend": Grafana


Aber zuerst müssen Sie Prometheus natürlich anweisen, unsere Metriken zu sammeln:


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

(oder mithilfe der Serviceerkennung, wenn Sie beispielsweise Kubernetes haben). Prometheus kann dazu gebracht werden, die Konfiguration erneut zu lesen, indem ein HUP Signal gesendet wird:


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

Wir gehen in der Benutzeroberfläche darauf ein und überprüfen, ob die Metriken erfasst wurden:



Okay, jetzt ist Grafan an der Reihe. Wir verwenden das grafana-worldmap-panel Plugin, das vorinstalliert sein muss:


 $ grafana-cli plugins install grafana-worldmap-panel 

Gehen Sie als nächstes zu ihr in der Benutzeroberfläche und klicken Sie auf Panel hinzufügen -> Worldmap-Panel. Geben Sie auf der Registerkarte Metriken die folgende Abfrage ein:


 sum(job_location) by (location) 

Und geben Sie das Legendenformat an: {{location}} . Alles sollte ungefähr so ​​aussehen:



Wechseln Sie als Nächstes zur Registerkarte Worldmap und konfigurieren Sie alles wie im Screenshot dargestellt:



Und alle! Viel Spaß mit unserer Karte.


Auf so einfache Weise können Sie eine schöne Karte der Verbindungen in Grafan erstellen.


Vielen Dank für Ihre Aufmerksamkeit und freuen uns auf Ihre Kommentare.


Todo


Um das Tool für den vorgesehenen Zweck zu verwenden, müssen Sie es natürlich vervollständigen: Filtern Sie die Adressen lokaler Subnetze heraus und vieles mehr. Übrigens, wenn jemand interessiert ist und diesen Exporter entwickeln möchte - willkommen im Repository auf GitHub!



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


All Articles