У Прометеуса є багато готових до використання експортерів, але інколи може з’явитися потреба у зборі своїх власних метрик.
Для цього Прометеус надає клієнтські бібліотеки, які можемо використати для генерації метрик з потрібними лейблами.
Експортер можна включити прямо в код вашого додатку, або можна запускати окремим сервісом, який буде звертатися до якогось вашого сервісу і отримувати від нього дані, які потім буде конвертувати в Prometheus-формат та віддавати серверу Prometheus.
Зміст
Prometheus Metrics
Загальна схема роботи Prometheus-серверу та його експортерів у Kubernetes виглядає так:
Тут маємо:
- Prometheus Server, який в нашому випадку розгортається за допомогою Kube Promeheus Stack та Prometheus Operator
- за допомогою ServiceMonitor через Operator ми створюємо Scrape Job, яка має один чи декілька Targets, тобто сервісів, які буде опитувати Prometheus для отримання метрик, які він зберігає у своїй Time Series Database
- за URL, які вказані в Target, Prometheus звертається до ендпоінту Prometheus Exporter
- а Prometheus Exporter збирає метрики з вашого додатку, які потім віддає до Prometheus
Типи метрик Prometheus
Коли плануємо писати свій експортер, необхідно знати які типи метрик ми можемо в ньому використовувати. Основні типи:
Counter
: може тільки збільшувати своє значення, наприклад для підрахунку кількості HTTP-запитівEnum
: має попередньо задані значення, використовується наприклад для моніторингу кількості подів у статус Running або FailedHistograms
: зберігає значення за проміжок часу, можна використовавти для, наприклад, отримання часу відповіді веб-северу за період часу –rate(metric_name{}[5m])
Gauges
: може приймати будь-яке значення, можемо використовувати для, наприклад, зберігання значень нагрузки на CPUInfo
: key-value storage, наприклад для Build information, Version information, або metadata
У кожного типу є свої методи, тож варто подивитись документацію, там ще й приклади є. Див. Prometheus Python Client, або в документації самої бібліотеки:
[simterm]
>>> import prometheus_client >>> help(prometheus_client.Enum)
[/simterm]
Python Custom Prometheus Exporter
Python HTTPServer Exporter з Counter
Для початку, давайте подивимось як воно взагалі працює – напишимо скрипт на Python, в якому на порту 8080 буде звичайний HTTP-сервер, а на порту 9000 – експортер, який буде збирати статистику по запитах з кодами відповідей і створювати метрику http_requests
з двома лейблами – в одній будемо зберігати код відповіді, а в іншій – ім’я хоста, з якого метрика була отримана.
Встановлюємо бібліотеку:
[simterm]
$ pip install prometheus_client
[/simterm]
Пишемо скрипт:
#!/usr/bin/env python import os import re import platform from time import sleep from http.server import BaseHTTPRequestHandler, HTTPServer from prometheus_client import start_http_server, Counter, REGISTRY class HTTPRequestHandler(BaseHTTPRequestHandler): def do_GET(self): if self.path == '/': self.send_response(200) self.send_header('Content-type','text/html') self.end_headers() self.wfile.write(bytes("<b> Hello World !</b>", "utf-8")) request_counter.labels(status_code='200', instance=platform.node()).inc() else: self.send_error(404) request_counter.labels(status_code='404', instance=platform.node()).inc() if __name__ == '__main__': start_http_server(9000) request_counter = Counter('http_requests', 'HTTP request', ["status_code", "instance"]) webServer = HTTPServer(("localhost", 8080), HTTPRequestHandler).serve_forever() print("Server started")
Тут ми:
start_http_server()
– запускаємо HTTP-сервер самого експортеру на порту 9000request_counter
– створюємо метрику з ім’ямhttp_requests
, типомCounter
, і додаємо їй дві labels –status_code
таinstance
webServer
– запускаємо звичайний HTTP-сервер на Python на порту 8080
Далі, коли ми робитимо HTTP-запит на localhost:8080, він буде попадати до do_GET()
, в якому буде перевірятися URI. Якщо йдемо на /
– то отримаємо код 200, якщо будь-який інший – то 404.
І там же оновлюємо значення метрики http_requests
– додаємо код відповіді та ім’я хоста, і викликаємо метод Counter.inc()
, який інкрементить значення метрики на одиницю. Таким чином кожен запит, який буде оброблено веб-сервером webServer буде додавати +1 в нашу метрику, а в залежності від коду відповіді – ми отримаємо цю метрику з двома різними лейблами – 200 та 404.
Перевіряємо – запускаємо сам скрипт:
[simterm]
$ ./py_http_exporter.py
[/simterm]
Робимо декілька запитів з різними URI:
[simterm]
$ curl -I -X GET localhost:8080/ HTTP/1.0 200 OK $ curl -I -X GET localhost:8080/ HTTP/1.0 200 OK $ curl -I -X GET localhost:8080/ HTTP/1.0 200 OK $ curl -I -X GET localhost:8080/blablabla HTTP/1.0 404 Not Found $ curl -I -X GET localhost:8080/blablabla HTTP/1.0 404 Not Found $ curl -I -X GET localhost:8080/blablabla HTTP/1.0 404 Not Found
[/simterm]
А тепер перевіримо, що маємо на ендпоінті експортеру:
[simterm]
$ curl -X GET localhost:9000 ... # HELP http_requests_total HTTP request # TYPE http_requests_total counter http_requests_total{instance="setevoy-wrk-laptop",status_code="200"} 3.0 http_requests_total{instance="setevoy-wrk-laptop",status_code="404"} 3.0
[/simterm]
Чудово – маємо три запроси з кодом 200, та три – з кодом 404.
Jenkins Jobs Exporter з Gauage
Або інший варіант – коли експортер буде звертатися до якось зовнішнього ресурсу, отримувати значення, і вносити їх до метрики.
Наприклад, ми можемо звертатись до якогось API, і від нього отримати дані, в цьому прикладі це буде Jenkins:
#!/usr/bin/env python import time import random from prometheus_client import start_http_server, Gauge from api4jenkins import Jenkins jenkins_client = Jenkins('http://localhost:8080/', auth=('admin', 'admin')) jenkins_jobs_counter = Gauge('jenkins_jobs_count', "Number of Jenkins jobs") def get_metrics(): jenkins_jobs_counter.set(len(list(jenkins_client.iter_jobs()))) if __name__ == '__main__': start_http_server(9000) while True: get_metrics() time.sleep(15)
Тут ми за допомогою бібліотеки api4jenkins
створюємо об’єкт jenkins_client
, який підключається до інстансу Jenkins та отримує кількість його jobs. Потім в функції get_metrics()
ми рахуємо кількість об’єктів із jenkins_client.iter_jobs()
, та вносимо їх до метрики jenkins_jobs_counter
.
Запускаємо в Docker:
[simterm]
$ docker run -p 8080:8080 jenkins:2.60.3
[/simterm]
Створюємо тестову задачу:
В результаті отримуємо такий результат:
[simterm]
$ curl localhost:9000 ... # HELP jenkins_jobs_count Number of Jenkins jobs # TYPE jenkins_jobs_count gauge jenkins_jobs_count 1.0
[/simterm]
Prometheus Exporter та Kubernetes
І давайте протестимо якийсь більш реальний приклад.
У нас є API-сервіс, який використовує базу даних PostgreSQL. Для перевірки підключення девелопери створили ендпоінт, на який ми можемо звертатися для отримання поточного статусу – є чи нема підключення до серверу баз даних.
Зараз для його моніторингу ми використовуємо Blackbox Exporter, але згодом хочется трохи розширити можливості, тож спробуємо створити експортер, котрий поки що буде просто перевіряти код відповіді він цього ендпоінту.
Exporter з Enum
та Histogram
#!/usr/bin/env python import os import requests import time from prometheus_client import start_http_server, Enum, Histogram hitl_psql_health_status = Enum("hitl_psql_health_status", "PSQL connection health", states=["healthy", "unhealthy"]) hitl_psql_health_request_time = Histogram('hitl_psql_health_request_time', 'PSQL connection response time (seconds)') def get_metrics(): with hitl_psql_health_request_time.time(): resp = requests.get(url=os.environ['HITL_URL']) print(resp.status_code) if not (resp.status_code == 200): hitl_psql_health_status.state("unhealthy") if __name__ == '__main__': start_http_server(9000) while True: get_metrics() time.sleep(1)
Задаємо змінну оточення з URL:
[simterm]
$ export HITL_URL=https://hitl.qa.api.example.com/api/v1/postgres/health-check
[/simterm]
Та запускаємо скрипт:
[simterm]
$ ./py_hitl_exporter.py 500 500 500
[/simterm]
Ммм… Чудово – ендпоінт лежить 🙂 То в понеділок нехай девелопери перевірять, нам підходить і це.
Docker-образ
Далі, збираємо Docker-образ – спочатку створимо requirements.txt
з залежностями:
requests prometheus_client
Теперь Dockerfile
:
FROM python:3.8 COPY py_hitl_exporter.py /app/py_hitl_exporter.py COPY requirements.txt /app/requirements.txt WORKDIR /app RUN pip install -r requirements.txt ENV HITL_URL $HITL_URL CMD ["python3", "/app/py_hitl_exporter.py"]
Збираємо образ:
[simterm]
$ docker build -t setevoy/test-exporter .
[/simterm]
Запускаємо локально для перевірки:
[simterm]
$ docker run -p 9000:9000 -e HITL_URL=https://hitl.qa.api.example.com/api/v1/postgres/health-check setevoy/test-exporter
[/simterm]
Перевіряємо метрики:
[simterm]
$ curl localhost:9000 ... # HELP hitl_psql_health_status PSQL connection health # TYPE hitl_psql_health_status gauge hitl_psql_health_status{hitl_psql_health_status="healthy"} 0.0 hitl_psql_health_status{hitl_psql_health_status="unhealthy"} 1.0 # HELP hitl_psql_health_request_time PSQL connection response time (seconds) # TYPE hitl_psql_health_request_time histogram hitl_psql_health_request_time_bucket{le="0.005"} 0.0 hitl_psql_health_request_time_bucket{le="0.01"} 0.0 hitl_psql_health_request_time_bucket{le="0.025"} 0.0 hitl_psql_health_request_time_bucket{le="0.05"} 0.0 hitl_psql_health_request_time_bucket{le="0.075"} 0.0 hitl_psql_health_request_time_bucket{le="0.1"} 0.0 hitl_psql_health_request_time_bucket{le="0.25"} 0.0 hitl_psql_health_request_time_bucket{le="0.5"} 0.0 hitl_psql_health_request_time_bucket{le="0.75"} 0.0 hitl_psql_health_request_time_bucket{le="1.0"} 0.0 hitl_psql_health_request_time_bucket{le="2.5"} 0.0 hitl_psql_health_request_time_bucket{le="5.0"} 0.0 hitl_psql_health_request_time_bucket{le="7.5"} 0.0 hitl_psql_health_request_time_bucket{le="10.0"} 0.0 hitl_psql_health_request_time_bucket{le="+Inf"} 9.0 hitl_psql_health_request_time_count 9.0 hitl_psql_health_request_time_sum 96.56228125099824
[/simterm]
Добре.
Пушимо його в Docker Hub:
[simterm]
$ docker login $ docker push setevoy/test-exporter
[/simterm]
Kubernetes Pod, Service та ServiceMonitor для Prometheus
Далі – треба цей образ запустити в Kubernetes, та створити ServiceMonitor для Prometheus, який там вже запущено.
Створюємо маніфест з Pod, Service та ServiceMonitor:
apiVersion: v1 kind: Pod metadata: name: hitl-exporter-pod labels: app: hitl-exporter spec: containers: - name: hitl-exporter-container image: setevoy/test-exporter env: - name: HITL_URL value: https://hitl.qa.api.example.com/api/v1/postgres/health-check ports: - name: metrics containerPort: 9000 protocol: TCP --- apiVersion: v1 kind: Service metadata: name: hitl-exporter-service labels: app: hitl-exporter spec: selector: app: hitl-exporter ports: - name: metrics port: 9000 targetPort: metrics --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: release: kps app: hitl-exporter name: hitl-exporter-monitor spec: endpoints: - port: metrics interval: 15s scrapeTimeout: 10s namespaceSelector: matchNames: - monitoring selector: matchLabels: app: hitl-exporter
Запускаємо:
[simterm]
$ kubectl -n monitoring apply -f hitl_exporter.yaml pod/hitl-exporter-pod created service/hitl-exporter-service created servicemonitor.monitoring.coreos.com/hitl-exporter-monitor created
[/simterm]
І за хвилину-дві перевіряємо Service Discovery:
Targets:
Та Jobs:
Переходимо до графіків – і маємо наші метрики:
Готово.