Slide 1

Slide 1 text

Prometheus в нагруженном Django-проекте Иван Елфимов Тимлид партнёрки, Ostrovok.ru Как мы знакомились с Prometheus и дружили его с Django

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

3 План 1. Предпосылки 2. Prometheus 3. Кодирование 4. Подводные камни

Slide 4

Slide 4 text

4 Предпосылки живем со StatsD + InfluxDB + Kapacitor уже 300+ сервисов StatsD перестал справляться и начал терять до 40% метрик (отчасти UDP, отчасти аггрегатор) DevOps завезли Prometheus для инфры решили перейти с push на pull модель

Slide 5

Slide 5 text

5 Подходы к сбору метрик

Slide 6

Slide 6 text

6 Prometheus 1. Теория 2. Инфраструктура 3. Практика 4. Библиотеки

Slide 7

Slide 7 text

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с - надо оптимизировать (или забить)

Slide 8

Slide 8 text

8 Инфраструктура используем VictoriaMetrics Consul знает о всех сервисах vmagent скрейпит подмешиваем лейблы на стороне скрейпера (датацентр, название контейнера и др.)

Slide 9

Slide 9 text

9 Практика строим графики в Grafana источник один на всех - VictoriaMetrics думали как глобально организовать метрики большая часть экспортеров уже с норм префиксами ... go_gc_forced_count go_gc_gomemlimit_bytes ... pg_database_size_bytes ...

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

11 Библиотеки prometheus/client_python базовый пакет, альтернатив, как таковых, нет умеет всё, но и настраивать всё самому korfuri/django-prometheus много всего из коробки (под капотом тот же client_python ) привязан к внутрянке Django (мидлвари, кэш, запросы в бд) mdawar/rq-exporter для rq другого живого нет есть дашборд для Grafana осторожней с кардинальностью (cardinality)

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

13 Как организуем модули 1 app 2 └──clients 3 ├── intranet 4 │ └── __init__.py 5 ├── statsd 6 │ └── __init__.py 7 ├── prometheus 8 │ └── __init__.py 9 └── ... 10

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

21 Зачем написали обёртку для клиента привычный для нас подход можно быстро заменить клиент прометея все методы отправки на одной странице

Slide 22

Slide 22 text

22 Переходный период начали отправлять и туда и туда строили графики долго привыкали к новому синтаксису

Slide 23

Slide 23 text

23 Ещё раз про бакеты StatsD Prometheus

Slide 24

Slide 24 text

24 Подводные камни 1. Размер файлов метрик 2. Особенности rq-exporter 3. Особенности gunicorn

Slide 25

Slide 25 text

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, но провалы на графиках индивидуальный подход к каждой метрике

Slide 26

Slide 26 text

26 Особенности rq-exporter пробовали хаки с названием воркеров ( partner-worker-1-b123123-12 , и чем тогда uuid хуже?) спрашивали у автора и решением было дропать лейблы на экспортере rq_workers_success_total{name="b3d20124e37a4efb9ea0d71d8e2e7382",queues="sync"} 5.0 | дропнуть

Slide 27

Slide 27 text

27 Особенности gunicorn есть настройка max_requests , после достижения которой процесс перезапускается (чтобы не ловить OOM) перезапуск процесса - это новый pid multiprocess collector раскладывает метрики в файлики с суффиксом _.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 ...

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

30 Выводы теперь (скорее всего) не теряем метрики пока что привыкаем к новым концепциям прометея отправка метрик в одном месте открытые возможности к более гибкому алертингу (VMAlert)

Slide 31

Slide 31 text

Q&A 💼 Карьера в Ostrovok.ru 🎬 Tech.Ostrovok на YouTube 🎙️ Подкаст "Два Ивана" 📝 t.me/biozz_dev 31

Slide 32

Slide 32 text

No content