Escrevendo um exportador GeoIP para Prometheus com visualizações em Grafana em 15 minutos


Olá pessoal!


Quero compartilhar com você como é fácil escrever seu exportador para Prometheus no Golang e mostrar como isso pode ser feito usando um exemplo de um pequeno programa que monitora de onde as conexões TCP atuais são geograficamente instaladas.


0. Isenção de responsabilidade


Gostaria de delinear imediatamente, desde o início, por assim dizer, o escopo desta publicação e dizer que não diz, para que mais tarde não haja perguntas:


  • Sim, isso não é uma visualização de clientes . Esta é uma visualização de conexões remotas . Ou seja, ele não divide as conexões entre aquelas nas quais o servidor remoto iniciou a conexão e as que foram iniciadas por esta máquina e mostrará tudo no mapa - por exemplo, o servidor com o repositório, onde as atualizações estão sendo baixadas para sua máquina a partir de agora.
  • Sim, entendo que existem ferramentas de anonimato na rede que ocultam o IP real do cliente. O objetivo desta ferramenta não é identificar as coordenadas exatas do GPS de qualquer cliente, mas ter pelo menos uma idéia geral de sua geografia.
  • whois fornece informações mais precisas do que o país do endereço IP, mas aqui eu estava conectado pelo limite do plugin do Grafan, que processa apenas países, mas não cidades.

1. Escrevemos "back-end": o exportador em movimento


Portanto, a primeira coisa que precisamos fazer é escrever um exportador que realmente coletará dados do nosso servidor e os enviará para o Prometheus. A escolha dos idiomas é ótima: o Prometheus possui bibliotecas clientes para escrever exportadores em muitos idiomas populares, mas eu escolhi Go, primeiro, porque é "mais nativo" (já que o Prometheus está escrito nele) e, segundo, porque ele próprio Eu uso na minha prática DevOps.


Bem, letras suficientes, vamos ao código. Vamos começar a escrever de baixo para cima: primeiro, as funções para determinar o país por IP e a lista de endereços IP remotos e depois enviar tudo para o Prometheus.


1.1 Determinamos o país pelo endereço IP


Bem, há absolutamente tudo na testa, eu não filosofei e apenas usei o serviço freegeoip.net , cuja API até o momento em que este artigo foi publicado estava obsoleta, e agora eles oferecem um registro gratuito e são capazes de fazer 10.000 solicitações por mês (o que é suficiente para nossos propósitos) ) Tudo é simples aqui: existe um terminal do formulário http://api.ipstack.com/<IP>?access_key=<API_KEY> , que simplesmente nos retorna json com o campo country_code que precisamos - é tudo o que precisamos para visualização.
Então, vamos escrever um pacote para atrair o país por IP.


Importamos as bibliotecas necessárias e criamos uma estrutura na qual o objeto json resultante será 'descompactado'.
 // 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"` } 

... e a própria função, que retornará o código do país para nó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 } 

Preste atenção ao parâmetro legacy=1 , eu tenho que usá-lo para compatibilidade com versões anteriores; Obviamente, se você usar a API deles, use a versão mais recente.


1.2 Crie uma lista de conexões TCP


Aqui, usaremos o pacote github.com/shirou/gopsutil/net e filtraremos as conexões com o status ESTABLISHED , excluindo endereços IP locais e endereços de uma lista negra personalizada que pode ser passada ao exportador na inicialização (por exemplo, para excluir todos os seus próprios endereços IP públicos)


Pacote com função retornando map [string] int: número de conexões do 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 E, finalmente, envie tudo para Prometeu


Mais precisamente, ele próprio vai levar tudo. Apenas ouviremos a porta e forneceremos as métricas coletadas.
Usando github.com/prometheus/client_golang/prometheus crie uma métrica do tipo Gauge . Na verdade, você pode criar um Counter , então usaríamos a rate ao consultar o banco de dados. Talvez o último seja mais eficaz do ponto de vista de Prometheus, mas enquanto escrevia este exportador (há seis meses), estava apenas começando a me familiarizar com Prometheus e Gauge era suficiente para mim:


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

Depois de coletar as métricas usando os parágrafos anteriores, atualizamos nosso vetor:


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

Começamos tudo isso com um loop infinito em uma goroutine separada e apenas ligamos a porta na principal e esperamos que Prometheus tome nossas métricas:


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

Na verdade, todo o código pode ser visualizado no repositório no GitHub , não quero copiar tudo aqui em uma linha.


2. "Front-end": Grafana


Mas primeiro, é claro, você precisa dizer ao Prometheus para coletar nossas métricas:


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

(ou usando a descoberta de serviço, se você tiver, por exemplo, Kubernetes). Prometheus pode ser feito para reler a configuração enviando a ele um sinal HUP :


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

Vamos a isso na interface do usuário e verificamos se as métricas são coletadas:



Ok, agora é a vez de Grafan. Usamos o grafana-worldmap-panel , que deve ser pré-instalado:


 $ grafana-cli plugins install grafana-worldmap-panel 

Em seguida, vá para ela na interface do usuário e clique em adicionar painel -> Painel Mapa do Mundo. Na guia Métricas, insira a seguinte consulta:


 sum(job_location) by (location) 

E especifique o formato da legenda: {{location}} . Tudo deve ficar assim:



Em seguida, vá para a guia Mapa do Mundo e configure tudo como na captura de tela:



E isso é tudo! Aproveite o nosso mapa.


De uma maneira tão simples, você pode fazer um belo mapa de conexões no Grafan.


Agradecemos sua atenção e aguardamos seus comentários.


Todo


Obviamente, para usar a ferramenta para a finalidade a que se destina, você precisa concluí-la: filtrar os endereços das sub-redes locais e muito mais. A propósito, se alguém estiver interessado e quiser desenvolver esse exportador - bem-vindo ao repositório no GitHub!



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


All Articles