Как мы подключали Prometheus

    Как-то мне предстояло разобраться с метриками для нашего API, как всегда (нет времени?!) дописать позже — очень сложно и ещё не внедрили — значит пора внедрять. После некоторых скитаний в сети, самой популярной системой мониторинга, как мне показалось, был Prometheus.


    При помощи Prometheus мы можем отслеживать различные ресурсы компьютера, такие как: память, загрузка процессора, диска, сети. Также нам может быть важно посчитать количество обращений к методам нашего API или измерить время их выполнения, ведь чем больше нагрузка на систему, тем дороже время её простоя. И тут нам на помощь приходит Prometheus. В данной статье приводятся, как мне кажется, основные моменты для понимания работы Prometheus и для добавления сбора метрик в API. Поэтому начнём с самого банального, с небольшого описания.


    Prometheus является системой с открытым исходным кодом и Time Series СУБД, написанный на Go и разработанной в компании SoundCloud. У него есть официальная документация и поддержка таких языков как: Go, Java или Scala, Python, Ruby. Есть неофициальная поддержка для других языков, таких как: C#, C++, C, Bash, Lua for Nginx, Lua for Tarantool и другие, весь список приведён на официальном сайте Prometheus.


    Все сервисы Prometheus доступны как образы Docker на Docker Hub или Quay.io.


    Запуск Prometheus осуществляется командой docker run -p 9090:9090 prom/prometheus, которая запускает его с дефолтной конфигурацией и устанавливает для него порт 9090. После чего UI Prometheus будет доступен по адресу localhost:9090.


    Prometheus представляет собой систему мониторинга, включающую в себя различные инструменты для настройки мониторинга приложений (конечных точек) при использовании HTTP-протокола. При соединении с Prometheus HTTP API не поддерживает "basic auth". Если требуется применить базовую аутентификацию для соединения с Prometheus, рекомендуется использовать Prometheus в сочетании с обратным прокси-сервером и применять аутентификацию на уровне прокси. Вы можете использовать любой обратный прокси-сервер с Prometheus.


    Основные компоненты Prometheus:


    • сервер, который собирает метрики, сохраняет их в базу и очищает;
    • пакеты для сбора метрик в API;
    • Pushgateway — компонент для приёма метрик от приложений, для которых не может быть использован Pull-запрос;
    • Exporters — инструменты для экспорта метрик из сторонних приложений и сервисов, устанавливаются на целевые машины;
    • AlertManager — менеджер уведомлений (оповещения), оповещения определяются в файле конфигурации и задаются набором правил для метрик.
      Если во время работы возникает соответствие правилу, оповещение инициируется и отправляется заданным получателям через Email, Slack или др.

    Объекты, c которыми работает Prometheus, называются метриками, получаемыми от целевых объектов или через Pushgateway, или через Exporters.


    При сборе метрик используются несколько способов их передачи:


    • Prometheus запрашивает метрики у целевого объекта через Pull-запрос, настройки которого прописаны в файле конфигурации в секции scrape_config для каждого из заданий (job).
      Когда система собирает данные, можно контролировать частоту сбора и создавать несколько конфигураций сбора данных, чтобы выбирать разную частоту для разных объектов;
    • Exporters позволят собирать метрики с различных объектов, например: базы данных (MongoDB, SQL и т.д.), брокеры сообщений (RabbitMQ, EMQ, NSQ и т.д.), балансировщики нагрузки HTTP и т.д.;
    • Pushgateway. Может быть использован в случае необходимости, когда приложение не может предоставить возможность напрямую отдавать метрики в Prometheus; либо когда используются пакетные задания, которые не имеют возможности использовать Pull-запрос Prometheus.

    Таким образом, все полученные метрики будут сохранены Prometheus в базе данных с указанием временных меток.


    Конфигурация


    Конфигурация Prometheus осуществляется при помощи флагов командной строки и файлов конфигурации, представленных в YAML-формате. Флаги командной строки позволяют настраивать неизменяемые параметры, такие как: пути, объемы данных, хранящиеся на диске и в памяти и т.д. Файл конфигурации позволяет настраивать всё, что связано с заданиями (jobs) и настройкой загружаемых yaml-файлов правил. Всё прописывается в глобальном файле конфигурации, он позволяет задать общие настройки для всех и выделить настройки для различных секций конфигураций в отдельности. Настройка целевых объектов, которые опрашивает Prometheus, осуществляются в файле конфигурации в секции scrape_configs.


    Prometheus может перезагружать файлы конфигурации во время работы, если новая конфигурация невалидна, то она не будет применена. Перезагрузка файла конфигурации запускается отправкой команды SIGHUP Prometheus или отправкой запроса HTTP POST на /-/reload, при условии, что установлен флаг --web.enable-lifecycle. Это также перезагрузит все настроенные файлы правил.


    Какие типы данных используются


    Prometheus хранит пользовательскую многомерную модель данных и использует язык запросов для многомерных данных, называемый PromQL. Prometheus хранит данные в виде временных рядов, он поддерживает несколько вариантов хранения:


    • локальное дисковое хранилище: каждые 2 часа происходит сжатие данных, которые были буферизованы в памяти, и сохранение их на диск. По умолчанию в рабочем каталоге используется директория ./data для сохранение сжатых файлов;
    • удаленное хранилище: Prometheus поддерживает интеграцию со сторонними хранилищами (например: Kafka, PostgreSQL, Amazon S3 и др.) через адаптер Protocol Buffer.

    Сохраняемый временной ряд определяется метрикой и метаданными в виде пар «ключ-значение», хотя, при необходимости, имя метрики может не использоваться и сама метрика будет состоять только из метаданных. Временной ряд можно формально определить как <имя метрики>{<метаданные>}. Ключ — <имя метрики>{<метаданные>} — что мы измеряем, а значение — фактическая величина в виде числа с типом float64 (Prometheus поддерживает только этот тип). В описании ключа присутствуют метаданные (labels), описываемые также парами «ключ-значение»: <имя метки>="<значение метки>",<имя метки>="<значение метки>",...


    При хранении метрик используются следующие типы данных:


    • Counter — считает количество за некоторый промежуток времени. Данный тип метрик может только увеличиваться (нельзя использовать отрицательные значения) или обнулять значение.
      Может подходить, например, для подсчёта количества запросов в минуту или количество ошибок за день, количество отправленных/принятых сетевых пакетов и т.д.
    • Gauge — хранит значения, которые со временем могут уменьшаться или увеличиваться.
      Gauge не показывает развитие метрики за период времени. Используя Gauge, можно потерять нерегулярные изменения метрики со временем.
    • Histogram — сохраняет несколько временных рядов: общая сумма всех наблюдаемых величин; количество событий, которые наблюдались;
      накопительные счетчики (buckets) — указываются в метке как le="<upper inclusive bound>".
      Значения собираются в области с настраиваемой верхней границей (buckets).
    • Summary — сохраняет несколько временных рядов: общая сумма всех наблюдаемых величин; количество событий, которые наблюдались;
      потоковые φ-квантили (0 ≤ φ ≤ 1) наблюдаемых событий — указываются в метке как quantile="<φ>".

    Как сохраняются данные?


    Prometheus рекомендует "отдавать" 2/3 оперативной памяти под работающее приложение.
    Для хранения данных в памяти Prometheus использует файлы, называемые chunk, для каждой метрики свой файл. Все файлы chunk иммутабельны, кроме последнего, в который пишутся данные. Новые данные сохраняются в chunk и каждые 2 часа фоновый поток объединяет данные и записывает их на диск. Каждый двухчасовой блок состоит из каталога, содержащего один или несколько файлов chunk, которые содержат все выборки временных рядов для этого промежутка времени, а также файл метаданных и индексный файл (который индексирует названия метрик и метки для временных рядов в файлах chunk). Если в течение одного часа Prometheus не пишет данные в chunck, то он будет сохранён на диск и будет создан новый chunck для записи данных. Максимальный срок хранения данных в Prometheus составляет ~21 день.


    Т.к. размер памяти фиксирован, производительность записи и чтения системы будет ограничена этим объемом памяти. Объем памяти PTSDB определяется минимальным периодом времени, периодом сбора и количеством временных метрик.


    Также в Prometheus реализован механизм WAL для предотвращения потери данных.


    Write ahead log (WAL) сериализует запоминаемые операции на постоянном носителе в виде файлов журнала. В случае сбоя файлы WAL могут быть использованы для восстановления базы данных до ее согласованного состояния путем восстановления из журналов.


    Файлы журнала хранятся в wal-каталоге в сегментах 128 МБ. Эти файлы содержат необработанные данные, которые еще не были сжаты, поэтому они значительно больше обычных файлов фрагментов.


    Prometheus будет хранить как минимум 3 файла журнала, однако серверы с высоким трафиком могут видеть более трех файлов WAL, поскольку ему необходимо хранить не менее двух часов необработанные данные.


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


    Prometheus сохраняет периодические контрольные точки, которые по умолчанию добавляются каждые 2 часа путем сжатия журналов за прошедший период и сохранения их на диск.


    Все контрольные точки хранятся в одном каталоге с именем checkpoint.ddd, где ddd является числом, монотонно увеличивающимся. Поэтому, при восстановлении после сбоя, он может восстановить контрольные точки из каталога контрольных точек с указанием порядка (.ddd).
    С помощью записи журналов WAL поддерживается возврат к любой контрольной точке, для которой доступен журнал данных.


    Что получилось у нас на практике?


    При добавлении в проект (.Net Framework) для сбора метрик мы использовали пакет Prometheus.Client.3.0.2. Для сбора метрик в проект были добавлены необходимые методы и классы, позволяющие хранить метрики, пока они не будут получены Prometheus.


    Первоначально был определён интерфейс IMetricsService, содержащий методы таймера для измерения времени работы методов:


    public interface IMetricsService
    {
        Stopwatch StartTimer();
    
        void StopTimer(Stopwatch timer, string controllerName, string actionName, string methodName = "POST");
    }

    Добавляем класс MetricsService, который реализует интерфейс IMetricsService и временно хранит метрики.


    public class MetricsService : IMetricsService
    {
        private static Histogram _histogram;
    
        static MetricsService()
        {
            _histogram = CreateHistogram();
        }
    
        public Stopwatch StartTimer()
        {
            try
            {
                var timer = new Stopwatch();
                timer.Start();
                return timer;
            }
            catch (Exception exception)
            {
                Logger.Error(exception);
            }
    
            return null;
        }
    
        public void StopTimer(Stopwatch timer, string controllerName, string actionName, string methodName = "POST")
        {
            try
            {
                if (timer == null)
                {
                    throw new ArgumentException($"{nameof(timer)} can't be null.");
                }
    
                timer.Stop();
                _histogram
                    .WithLabels(controllerName, actionName, methodName)
                    .Observe(timer.ElapsedMilliseconds, DateTimeOffset.UtcNow);
            }
            catch (Exception exception)
            {
                Logger.Error(exception);
            }
        }
    
        public static List<string> GetAllLabels()
        {
            var metricsList = new List<string>();
            try
            {
                foreach (var keyValuePair in _histogram.Labelled)
                {
                    var controllerName = keyValuePair.Key.Labels[0].Value;
                    var actionName = keyValuePair.Key.Labels[1].Value;
                    var methodName = keyValuePair.Key.Labels[2].Value;
                    var requestDurationSum = keyValuePair.Value.Value.Sum;
                    var requestCount = keyValuePair.Value.Value.Count;
    
                    metricsList.Add($"http_request_duration_widget_sum{{controller={controllerName},action={actionName},method={methodName}}} {requestDurationSum}");
                    metricsList.Add($"http_request_duration_widget_count{{controller={controllerName},action={actionName},method={methodName}}} {requestCount}");
                }
    
                _histogram = CreateHistogram();
            }
            catch (Exception exception)
            {
                Logger.Error(exception);
            }
    
            return metricsList;
        }
    
        private static Histogram CreateHistogram()
        {
            var newMetrics = Metrics
                .WithCustomRegistry(new CollectorRegistry())
                .CreateHistogram(name: "http_request_duration_web_api",
                    help: "Histogram metrics of Web.Api",
                    includeTimestamp: true,
                    labelNames: new[] { "controller", "action", "method" });
    
            var oldValue = _histogram;
            for (var i = 0; i < 10; i++)
            {
                            var oldValue = Interlocked.Exchange<Histogram>(ref oldValue, newMetrics);
                if (oldValue != null) 
                            {
                                return oldValue;
                            }
            }
    
            return null;
        }
    }

    Теперь мы можем использовать наш класс для сохранения метрик, которые мы планируем собирать, в методах Application_BeginRequest, Application_Error, Application_EndRequest. В классе Global.cs добавляем сбор метрик в вышеуказанные методы.


    private IMetricsService _metricsService;
    
    protected virtual void Application_BeginRequest(object sender, EventArgs e)
    {
        var context = new HttpContextWrapper(HttpContext.Current);
        var metricServiceTimer = _metricsService.StartTimer();
        context.Items.Add("metricsService", _metricsService);
        context.Items.Add("metricServiceTimer", metricServiceTimer);
    }
    
    protected virtual void Application_EndRequest(object sender, EventArgs e)
    {
        WriteMetrics(new HttpContextWrapper(HttpContext.Current));
    }
    
    protected void Application_Error(object sender, EventArgs e)
    {
         WriteMetrics(new HttpContextWrapper(HttpContext.Current));
    }
    
    private void WriteMetrics(HttpContextBase context)
    {
        try
        {
            _metricsService = context.Items["metricsService"] as IMetricsService;
            if (_metricsService != null)
            {
                var timer = context.Items["metricServiceTimer"] as Stopwatch;
    
                string controllerName = null;
                string actionName = null;
    
                var rd = RouteTable.Routes.GetRouteData(context);
                if (rd != null)
                {
                    controllerName = rd.GetRequiredString("controller");
                    actionName = rd.GetRequiredString("action");
                }
    
                _metricsService.StopTimer(timer, controllerName, actionName, context.Request.HttpMethod);
            }
        }
        catch (Exception exception)
        {
            Logger.Error("Can't write metrics.", exception);
        }
    }
    

    Добавляем новый контроллер, который будет являться контрольной точкой для отправки метрик нашего API в Prometheus:


    public class MetricsController : Controller
    {
        [HttpGet]
        public string[] GetAllMetrics()
        {
            try
            {
                var metrics = MetricsService.GetAllLabels();
                return metrics.ToArray();
            }
            catch (Exception exception)
            {
                Logger.Error(exception);
            }
    
            return new string[] { };
        }
    }

    Последним шагом будет являться настройка конфига Prometheus для сбора метрик в секции scrape_configs, после чего мы можем увидеть собираемые метрики уже в UI Prometheus или Grafana.


    Ключевые функции, которые нам были интересны в Prometheus:


    Многомерная модель данных: метрики и метки.
    Гибкий язык запросов PromQL. В одном и том же операторе запроса можем использовать такие операции, как умножение, сложение, конкатенация и др.; могут выполняться с несколькими метриками.
    Собирает данные на основе HTTP, используя метод pull.
    Совместим с методом push через Pushgateway.
    Существует возможность собирать метрики с других приложений через Exporters.
    Предоставляет механизм, предотвращающий потерю данных.
    Поддерживает различное графическое представление данных.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 3

      –1
      клиентские библиотеки для взаиможействия с Prometheus по средствам Pull-запросов;

      Вы это серьёзно?
        0

        Почему не воспользовались AppMetrics?

          0
          Не нашли перевеса в их пользу.

        Only users with full accounts can post comments. Log in, please.