Статьи

Сбор статистики мониторинга NGINX Plus с помощью Go

В наше время наличие масштабируемой и надежной инфраструктуры имеет решающее значение для успеха вашего сайта. Мониторинг производительности NGINX и работоспособности ваших приложений с балансировкой нагрузки имеет решающее значение. Знание объема трафика, получаемого вашими виртуальными серверами, позволяет масштабировать при необходимости, а отслеживание уровня ошибок позволяет эффективно сортировать приложения. Мониторинг и использование этих показателей помогают вашим пользователям получить надежный и полезный опыт.

Функция мониторинга активности в NGINX Plus позволяет легко получать ключевые показатели нагрузки и производительности в режиме реального времени. Большой объем полезных данных о трафике, проходящем через NGINX Plus, доступен как на встроенной панели инструментов, так и в удобном для анализа формате JSON через API на основе HTTP.

Существует много эффективных способов использования данных, предоставляемых API мониторинга активности в реальном времени. Например, вы можете захотеть отслеживать восходящий трафик конкретного виртуального сервера и автоматически масштабировать Docker- контейнеры, используя API-интерфейс реконфигурации NGINX Plus « на лету» . Возможно, вы захотите вывести метрики напрямую в стандартный формат и записать их в файл журнала, чтобы вы могли отправлять метрики в стек ELK или кластер Splunk . Или, возможно, вы хотите собирать и отправлять данные API в агрегатор данных, такой как statsd и collectd, для других целей построения графиков или ведения журналов.

Члены сообщества Go написали большое количество пакетов, которые могут предоставить нужную вам функциональность. В качестве другого примера я также использовал сторонний пакет go-statsd-client для отправки некоторых моих статистических данных непосредственно в StatsD.

StatsD — это сетевой демон, который работает на платформе Node.js и прослушивает статистику, такую ​​как счетчики и таймеры, отправляемую по UDP или TCP, и отправляет агрегаты одной или нескольким подключаемым внутренним службам.

О Go

Go, также известный как golang, — это язык программирования с открытым исходным кодом, разработанный Google, который позволяет легко создавать простые, надежные и эффективные программы. Если вы хотите узнать больше о Go, посмотрите «Как написать код Go» и «Go by Example» . На сайте Go также имеется обширная документация по различным доступным пакетам . Цель этого блога — показать вам некоторые основные методы и пакеты Go, которые можно использовать для сбора метрик API мониторинга активности NGINX Plus.

$ go version
go version go1.4.2 linux/amd64

$ sudo /usr/sbin/nginx -V
nginx version: nginx/1.9.4 (nginx-plus-r7)

Я протестировал образец скрипта на Go версии 1.4.2 и NGINX Plus R7 (на основе NGINX версии 1.9.4 с открытым исходным кодом).

Импорт пакетов Go

Первое, что мы определяем в скрипте, это имя пакета и пакеты, которые мы хотим импортировать во время выполнения или сборки.

package main

import (
  "encoding/json"
  "errors"
  "fmt"
  "github.com/cactus/go-statsd-client/statsd"
  "io/ioutil"
  "log"
  "net/http"
  "time"
)

В моем примере скрипта я использую несколько пакетов, которые доступны в стандартной  библиотеке Go . Вот список этих пакетов вместе с кратким описанием того, для чего они используются.

  •      encoding / json — Кодирование и декодирование объектов JSON
  •      ошибки — управление сообщениями об ошибках
  •      fmt — Форматирование функций ввода / вывода (печать данных на терминал)
  •      io / ioutil — реализация утилит ввода / вывода
  •      log — регистрация и ошибки печати
  •      net / http — Создание HTTP-подключения клиента к конечной точке API
  •      время — создание механизма сна в нашем скрипте

Определение структуры данных JSON

В Go вы должны определить структуру данных вашего JSON. Обратите внимание, что Go просто игнорирует любые данные JSON, которые вы не определили в своей структуре данных. В этом примере я получаю статистику соединения только из API NGINX Plus.

type NginxResponse struct {
  Connections struct {
        Accepted int64 `json:"accepted"`
        Active   int64 `json:"active"`
        Dropped  int64 `json:"dropped"`
        Idle int64 `json:"idle"`
  } `json:"connections"`
}

Если вы решите использовать эту структуру данных JSON и добавить значения зон апстрима и сервера, имейте в виду, что некоторые имена полей данных будут уникальными для вашего экземпляра и конфигурации NGINX Plus. В Интернете существует множество инструментов, которые легко преобразуют дамп JSON в структуру Go, но как только вы поймете, как определяется структура, очень легко создать свой собственный.

Определение функций NginxStatus и SendStatsD

Мой скрипт Go состоит из трех функций: NginxStatus, SendStatsD,и mainфункции, которую я расскажу в этом порядке. NginxStatusФункция делает вызов к NGINX Plus живой активности мониторинга API.

Сначала я определяю сервер NGINX Plus, затем выполняю запрос GET к API мониторинга активности в реальном времени. Я проверяю, что код статуса в ответе, 200 OKи использую пакет ошибок, чтобы зарегистрировать проблему, если нет. Я также откладываю закрытие соединения до завершения моей функции, после чего оно автоматически закрывает соединение.

Затем я считываю тело ответа в переменную и декодирую JSON, используя структуру, определенную мной в разделе «Определение структуры данных JSON». Я передаю данные обратно в основную функцию. Я также распечатываю статистику на экране и передаю ее в функцию SendStatsD.


func NginxStatus() (*NginxResponse, error) {

  // assign variable for NGINX Plus server
  var nginxStatusServer string = "my.nginx.server.com"

  // perform a request to the NGINX Plus status API
  resp, err := http.Get(fmt.Sprintf("http://%s/status", nginxStatusServer))
  if err != nil {
        return nil, err
  }
  if resp.StatusCode != http.StatusOK {
        return nil, errors.New("Non 200 OK")
  }

  // clean up the connection
  defer resp.Body.Close()

  // read the body of the request into a variable
  data, err := ioutil.ReadAll(resp.Body)
  if err != nil {
        return nil, err
  }

  // unmarshall the JSON data into a variable
  var er NginxResponse
  if err := json.Unmarshal(data, &er); err != nil {
        return nil, err
  }
  return &er, nil
}

В моей функции SendStatsD я определяю переменные и тип, который я хочу отправить непосредственно в StatsD. В начале функции я определяю сервер StatsD и порт, к которому я буду подключаться, а также имя клиента. Затем я устанавливаю соединение, регистрирую любые непредвиденные ошибки и снова откладываю закрытие соединения. Затем я назначаю переменную интервалу очистки для StatsD, а другую — части имени метрики. Интервал очистки сообщает StatsD, как долго ждать отправки данных в бэкэнд. Наконец, в конце моей функции SendStatsD я отправляю все данные в StatsD, используя встроенную функцию Inc.


func SendStatsD(gs string, gt string, gv int64) {

  // assign variables for statsd server and metric name prefix
  var statsdServer string = "127.0.0.1:8125"
  var gk string = "nginx"

  // connect to statsd
  client, err := statsd.NewClient(statsdServer, gk)
  if err != nil {
        log.Println(err)
  }

  // clean up the connections
  defer client.Close()

  // assign variables for metric name and interval
  var fi float32 = 1.0
  var st string = "status"
  var sn string = "my_nginx_server_com"

  // send metrics to StatsD
  client.Inc(st+"."+sn+"."+gs+"."+gt, gv, fi)
}

Очень важно, что это правильно сконструировано, потому что оно сообщает StatsD, как организовать ваши данные. Думайте о названии метрики как о структуре папок с точкой в ​​качестве разделителя между каждым уровнем. То, как вы называете пространство имен метрики, определяет, где данные хранятся и доступны в веб-интерфейсе Graphite. То, как вы храните свои данные, зависит от вас, но я рекомендовал вам использовать следующее соглашение об именах при определении имен метрик. Здесь я использую угловые скобки, чтобы было легче увидеть сегменты имени; они не используются в реальных именах.

<namespace>.<section-type>.<server-name>.<target noun>.<stat adjective or verb>

например:

nginx.status_api.my_nginx_server_com.connections.active

В этом примере StatsD отправляет данные API NGINX Plus о подключениях, которые в данный момент активны на сервере my.nginx.server.com. Обратите внимание, что вы заменяете любые точки или пробелы в сегменте имени символом подчеркивания.

Определение основной функции

В mainфункции я создаю бесконечный цикл, а затем просто вызываю функции, описанные выше, чтобы легко получать и отправлять данные по мере необходимости.


func main() {

  for {
    // query the NginxStatus function to get data
    nr, err := NginxStatus()
    if err != nil {
      log.Println(err)
    }

    // print the statistics on screen
    fmt.Println("Connections Accepted:", nr.Connections.Accepted)
    fmt.Println("Connections Dropped:", nr.Connections.Dropped)
    fmt.Println("Connections Active:", nr.Connections.Active)
    fmt.Println("Connections Idle", nr.Connections.Idle)

    // send metrics to the SendStatsD function
    go SendStatsD("connections", "accepted", nr.Connections.Accepted)
    go SendStatsD("connections", "dropped", nr.Connections.Dropped)
    go SendStatsD("connections", "active", nr.Connections.Active)
    go SendStatsD("connections", "idle", nr.Connections.Idle)

        // sleep for one second
        time.Sleep(time.Millisecond * 1000)
  }
}

Здесь я присваиваю переменную результатам вызова API для NGINX Plus, используя функцию NginxStatus. Затем я использую fmtпакет для вывода на экран некоторых статистических данных о своем соединении, а функцию SendStatsD — для передачи этих данных в Graphite.

Я также запускаю sleepфункцию для вставки задержки в одну секунду между каждым запросом. В этом блоге я не буду подробно рассказывать о том, как управлять связью или распределением времени. При создании вашего скрипта вам нужно будет определить, как вы хотите справиться с таймаутами или задержками соединения и как учитывать время между каждым запросом.

Вот пример вывода экрана после запуска скрипта. Данные, отправляемые в StatsD, прозрачны и могут быть проверены с помощью веб-интерфейса Graphite.


$ go run main.go
Connections Accepted: 36480962
Connections Dropped: 0
Connections Active: 13
Connections Idle 30

Полный сценарий

Вот полный текст сценария для справки. Интервалы важны в Go, поэтому обязательно проверяйте синтаксис, если вы получаете какие-либо ошибки.


package main

import (
  "encoding/json"
  "errors"
  "fmt"
  "github.com/cactus/go-statsd-client/statsd"
  "io/ioutil"
  "log"
  "net/http"
  "time"
)

type NginxResponse struct {
  Connections struct {
        Accepted int64 `json:"accepted"`
        Active   int64 `json:"active"`
        Dropped  int64 `json:"dropped"`
        Idle int64 `json:"idle"`
  } `json:"connections"`
}

func NginxStatus() (*NginxResponse, error) {

  // assign variable for nginx plus server
  var nginxStatusServer string = "my.nginx.server.com"

  // perform a request to the NGINX Plus status API
  resp, err := http.Get(fmt.Sprintf("http://%s/status", nginxStatusServer))
  if err != nil {
        return nil, err
  }
  if resp.StatusCode != http.StatusOK {
        return nil, errors.New("Non 200 OK")
  }

  // clean up the connection
  defer resp.Body.Close()

  // read the body of the request into a variable
  data, err := ioutil.ReadAll(resp.Body)
  if err != nil {
        return nil, err
  }

  // unmarshall the JSON data into a variable
  var er NginxResponse
  if err := json.Unmarshal(data, &er); err != nil {
        return nil, err
  }
  return &er, nil
}

func SendStatsD(gs string, gt string, gv int64) {

  // assign variables for statsd server and metric name prefix
  var statsdServer string = "127.0.0.1:8125"
  var gk string = "nginx"

  // connect to statsd
  client, err := statsd.NewClient(statsdServer, gk)
  if err != nil {
        log.Println(err)
  }

  // clean up the connections
  defer client.Close()

  // assign variables for metric name and interval
  var fi float32 = 1.0
  var st string = "status"
  var sn string = "my_nginx_server_com"

  // send metrics to statsd
  client.Inc(st+"."+sn+"."+gs+"."+gt, gv, fi)
}

func main() {

  for {
    // query the NginxStatus function to get data
    nr, err := NginxStatus()
    if err != nil {
          log.Println(err)
    }

    // print the statistics on screen
    fmt.Println("Connections Accepted:", nr.Connections.Accepted)
    fmt.Println("Connections Dropped:", nr.Connections.Dropped)
    fmt.Println("Connections Active:", nr.Connections.Active)
    fmt.Println("Connections Idle", nr.Connections.Idle)

    // send metrics to the SendStatsD function
    go SendStatsD("connections", "accepted", nr.Connections.Accepted)
    go SendStatsD("connections", "dropped", nr.Connections.Dropped)
    go SendStatsD("connections", "active", nr.Connections.Active)
    go SendStatsD("connections", "idle", nr.Connections.Idle)

        // sleep for one second
        time.Sleep(time.Millisecond * 1000)
  }
}

Резюме

Использование Go для доступа к API мониторинга активности NGINX Plus может обеспечить дополнительную гибкость в отношении данных, уже доступных вам в NGINX Plus. Статистические данные, которые вы извлекаете из NGINX, могут помочь улучшить ведение журналов, автоматизировать мониторинг активности в реальном времени и создавать графики или диаграммы на основе истории производительности NGINX Plus.

Первоначально написано  Кевином Джонсом  в блоге NGINX.