$30 off During Our Annual Pro Sale. View Details »

Moscow Python Meetup №88. Иван Елфимов (Ostrovo...

Moscow Python Meetup №88. Иван Елфимов (Ostrovok.ru, тимлид). Переход от Statsd к Prometheus в большом Django-проекте

Про то, как структурировать метрики прометея в коде, выстроить удобные абстракции. Как меняется парадигма при переходе со statsd на прометей. Подводные камни при работе с прометеем.

Видео: https://moscowpython.ru/meetup/88/statsd-to-prometheus/

Moscow Python: http://moscowpython.ru
Курсы Learn Python: http://learn.python.ru
Moscow Python Podcast: http://podcast.python.ru
Заявки на доклады: https://bit.ly/mp-speaker

Moscow Python Meetup

February 28, 2024
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. 2 Компания в цифрах 350+ технических сотрудников 30000+ бронирований в

    день Сотрудничаем с Aviasales, Tutu, Аэрофлотом, Победой +50% новых технических сотрудников в 2023 Компания основана в 2010 году
  2. 4 Предпосылки живем со StatsD + InfluxDB + Kapacitor уже

    300+ сервисов StatsD перестал справляться и начал терять до 40% метрик (отчасти UDP, отчасти аггрегатор) DevOps завезли Prometheus для инфры решили перейти с push на pull модель
  3. 1 1 0 1 0 1 0 0 1 0

    1 1 0 0 1 1 0 0 0 0 1 1 1 0 1 0 1 1 7 Теория есть несколько типов метрик гистограммы + summary (request time) счетчики датчики/gauge (free memory, temperature) бакеты для гистограмм есть дефолтные (слишком большая разбивка до секунды) 0.1, 0.25, 0.5, 1, 1.5, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, INF нас не интересуют слишком быстрые API самое мясо с 0.1с до 5с, нужно чтобы было поточнее больше 10с - надо оптимизировать (или забить)
  4. 8 Инфраструктура используем VictoriaMetrics Consul знает о всех сервисах vmagent

    скрейпит подмешиваем лейблы на стороне скрейпера (датацентр, название контейнера и др.)
  5. 9 Практика строим графики в Grafana источник один на всех

    - VictoriaMetrics думали как глобально организовать метрики большая часть экспортеров уже с норм префиксами ... go_gc_forced_count go_gc_gomemlimit_bytes ... pg_database_size_bytes ...
  6. 10 Практика объединять метрики библиотек или нет? например rq-exporter или

    django-exporter могут быть в нескольких сервисах специфичные метрики сервисов точно со своими префиксами 1 rq_workers_success_total{container_name="app1"} 0.5 2 3 partner_foo_errors{error="test"} 5 4 partner_custom_metric{bar="buz"} 1.5 5
  7. 11 Библиотеки prometheus/client_python базовый пакет, альтернатив, как таковых, нет умеет

    всё, но и настраивать всё самому korfuri/django-prometheus много всего из коробки (под капотом тот же client_python ) привязан к внутрянке Django (мидлвари, кэш, запросы в бд) mdawar/rq-exporter для rq другого живого нет есть дашборд для Grafana осторожней с кардинальностью (cardinality)
  8. 12 Кодирование 1. Как организуем модули 2. Инициализация 3. Определения

    метрик 4. Определения метрик 5. Методы для отправки 6. Использование 7. Как было и как стало 8. Зачем написали обёртку для клиента 9. Переходный период 10. Ещё раз про бакеты
  9. 13 Как организуем модули 1 app 2 └──clients 3 ├──

    intranet 4 │ └── __init__.py 5 ├── statsd 6 │ └── __init__.py 7 ├── prometheus 8 │ └── __init__.py 9 └── ... 10
  10. 14 Инициализация 1 from prometheus_client import CONTENT_TYPE_LATEST, CollectorRegistry, generate_latest, multiprocess

    2 3 4 class PrometheusClient: 5 HTTP_RESPONSE_TIME_BUCKETS = ( 6 0.1, 0.25, 0.5, 1, 1.5, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, INF 7 ) 8 # определения метрик 9 # ... 10 11 def __init__(self, path: str) -> None: 12 self.content_type = CONTENT_TYPE_LATEST 13 self.registry = CollectorRegistry() 14 multiprocess.MultiProcessCollector( 15 registry=self.registry, 16 path=path, 17 ) 18 19 def expose(self) -> bytes: 20 return generate_latest(self.registry) 21 22 # где-то в самом конце __init__.py 23 prometheus = PrometheusClient(path=settings.PROMETHEUS_MULTIPROC_DIR) 24
  11. 15 Определения метрик # HELP partner_reports_count Counter the amount of

    reports by manager # TYPE partner_reports_count counter partner_reports_count_total{status="ok",...} 25.0 1 # пример Counter 2 reports_count = Counter( 3 name='partner_reports_count', 4 documentation='Counter the amount of reports by manager', 5 labelnames=['tool', 'status', 'error', 'manager'], 6 ) 7
  12. 16 Определения метрик # HELP partner_api_view API View Timing #

    TYPE partner_api_view histogram partner_api_view_sum{result="ok",...,view="my_view"} 1.794705 partner_api_view_bucket{result="ok",...,view="my_view",le="0.1"} 47.0 partner_api_view_bucket{result="ok",...,view="my_view",le="0.25"} 49.0 partner_api_view_count{result="ok",...,view="my_view"} 49.0 1 # пример Histogram 2 api_view_timing = Histogram( 3 name='partner_api_view', 4 documentation='API View Timing', 5 labelnames=['step', 'view', 'consumer', 'result'], 6 buckets=HTTP_RESPONSE_TIME_BUCKETS, 7 ) 8
  13. 17 Методы для отправки 1 def send_api_view_timing(self, value: float, **labels)

    -> None: 2 self.api_view_timing.labels(**labels).observe(value) 3 4 def incr_reports_count(self, **labels) -> None: 5 self.special_reports_count.labels(**labels).inc() 6 7 def send_api_client_timing(self, value: float, **labels) -> None: 8 self.api_client_timing.labels(**labels).observe(value) 9
  14. 18 Использование 1 from app.client.prometheus import prometheus 2 3 #

    где-то в глубинах нашего самописного фреймворка 4 5 # конечно же фича-флаги 6 if settings.IS_PROMETHEUS_ENABLED: 7 prometheus.send_api_view_timing( 8 value=rate.total_seconds(), 9 step='method', 10 view=_view_name, 11 result=status, 12 consumer=None, 13 ) 14
  15. 19 Использование 1 # где-то в глубинах BaseClient 2 def

    _send_metrics( 3 self, 4 request_start_time: float, 5 tags: dict | None = None, 6 ): 7 if self.prometheus_client and settings.IS_PROMETHEUS_ENABLED: 8 labels = { 9 # **tags, self.cls_name, etc. 10 } 11 value = time.time() - request_start_time 12 self.prometheus_client.send_api_client_timing(value=value, **labels) 13 14 if not self.statsd_client: 15 return 16 17 # и дальше отправка метрики в statsd 18
  16. 20 Как было и как стало 2 f'validation_error,' 1 statsd_client.incr(

    3 f'field={field},' 4 f'endpoint={request.path},' 5 ) 6 1 prometheus.incr_validation_error( 2 field=field, 3 endpoint=request.path, 4 ) 5
  17. 21 Зачем написали обёртку для клиента привычный для нас подход

    можно быстро заменить клиент прометея все методы отправки на одной странице
  18. 22 Переходный период начали отправлять и туда и туда строили

    графики долго привыкали к новому синтаксису
  19. 1 1 0 1 0 1 0 0 1 0

    1 1 0 0 1 1 0 0 0 0 1 1 1 0 1 0 1 1 25 Размер файлов метрик проблема большой файл > /metrics отвечает дольше > таймауты > нет метрик решения в экспортерах часто есть настройка автоочистки по интервалу изобретательность с logrotate, но провалы на графиках индивидуальный подход к каждой метрике
  20. 26 Особенности rq-exporter пробовали хаки с названием воркеров ( partner-worker-1-b123123-12

    , и чем тогда uuid хуже?) спрашивали у автора и решением было дропать лейблы на экспортере rq_workers_success_total{name="b3d20124e37a4efb9ea0d71d8e2e7382",queues="sync"} 5.0 | дропнуть
  21. 27 Особенности gunicorn есть настройка max_requests , после достижения которой

    процесс перезапускается (чтобы не ловить OOM) перезапуск процесса - это новый pid multiprocess collector раскладывает метрики в файлики с суффиксом _<pid>.db файлов становится много, сжигает место на диске (1) в uwsgi такого нет ( process_identifier ) root@1a292fd2d229:/var/metrics# ls -S gauge_all_694.db summary_1275.db summary_1533.db summary_178.db ... summary_1012.db summary_1294.db summary_1551.db summary_1792.db ... summary_1031.db summary_1312.db summary_1569.db summary_1811.db ... ... summary_1256.db summary_1515.db summary_1771.db summary_2020.db ...
  22. 28 Особенности gunicorn нужно мониторить кол-во файлов, потому что чтение

    /metrics может тормозить (1, 2) в multiprocess режиме были мысли попробовать logrotate cron-job которая чистит файлики с несуществующими pid набрасывали подчистку файликов в child_exit и worker_exit
  23. 29 Особенности gunicorn отличная статья на хабре от Домклик используем

    filelock теперь кол-во файлов метрик == кол-ву воркеров гуника по-сравнению с Домкликом добавили имя воркера root@e4c2354b0c92:/var/partner/filelock# ls external_api_1.lock external_api_2.lock ... internal_api_1.lock internal_api_2.lock ... 1 ValueClass = prometheus_client.values.MultiProcessValue( 2 process_identifier=lambda: f'{proc_name}_{worker_id}', 3 ) 4 prometheus_client.values.ValueClass = ValueClass 5
  24. 30 Выводы теперь (скорее всего) не теряем метрики пока что

    привыкаем к новым концепциям прометея отправка метрик в одном месте открытые возможности к более гибкому алертингу (VMAlert)
  25. Q&A 💼 Карьера в Ostrovok.ru 🎬 Tech.Ostrovok на YouTube 🎙️

    Подкаст "Два Ивана" 📝 t.me/biozz_dev 31