Перейти к основному содержимому

Шаг 7. Сбор метрик с помощью Prometheus

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

Метрики приложений и запросов являются важными показателями доступности и производительности. Пользовательские метрики могут дать представление о том, как показатели доступности влияют на опыт пользователей или бизнес. Собранные данные могут быть использованы для оповещения об отключении или принятия решений по планированию для автоматического расширения развертывания при высоком спросе.

API OpenTelemetry метрик описывает классы, используемые для генерации метрик. MeterProvider предоставляет пользователям доступ к Meter, который, в свою очередь, используется для создания объектов, фиксирующие метрики. Таким образом, OpenTelemetry предоставляет инструменты для сборки метрик из коробки.

Prometheus — это система мониторинга и оповещений, хранящая и обрабатывающая метрики, собираемые из экспортеров в Time Series Database (TSDB). В отличие от SQl-like СУБД, Prometheus сам собирает метрики по указанным хостам.

В Python есть свой клиент для сборки метрик. Однако мы попробуем использовать метрики, которые собираются с помощью OpenTelemetry, и вернемся через интерфейс экспортера Prometheus. Делать это будем, опираясь на официальную документацию.

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

Сначала создайте конфигурационный файл prometheus.yml в директории prometheus. Файл prometheus/prometheus.yml выглядит следующим образом:

scrape_configs:
- job_name: 'otel-iu5devops-app'
scrape_interval: 5s
static_configs:
- targets: ['iu5devops-app:5000']

Затем установите пакет Prometheus exporter:

pip install opentelemetry-exporter-prometheus

Не забываем обновлять файл app/requirements.txt:

Flask==3.0.0
opentelemetry-sdk==1.21.0
opentelemetry-exporter-prometheus==1.12.0rc1
opentelemetry-exporter-otlp-proto-grpc==1.21.0

Затем мы настроим экспортер при инициализации показателей. Также воспользуемся примером из документации SDK, который счетчик для периодического отображения системного процессорного времени. Не забудем поддержать метод-экспортер /metrics для отдачи метрик Prometheus.

В конечном итоге файл app/app.py будет выглядеть следующим образом:

import os
from typing import Iterable
from prometheus_client import generate_latest

from flask import Flask
from random import randint
from opentelemetry import trace, metrics
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.metrics import get_meter, Observation, CallbackOptions
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from opentelemetry.sdk.resources import SERVICE_NAME, Resource

resource = Resource.create({SERVICE_NAME: os.environ.get(
'APP_SERVICE_NAME', "my-python-service")})

tracer_provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(OTLPSpanExporter(
endpoint=os.environ.get('TRACE_ENDPOINT', "http://localhost:4317")))
tracer_provider.add_span_processor(processor)
trace.set_tracer_provider(tracer_provider)
tracer = trace.get_tracer(__name__)

reader = PrometheusMetricReader()
meter_provider = MeterProvider(resource=resource, metric_readers=[reader])
metrics.set_meter_provider(meter_provider)

app = Flask(__name__)


def roll():
return randint(1, 6)


@app.route("/rolldice")
def roll_dice():
with tracer.start_as_current_span("server_request"):
return str(roll())


@app.route('/metrics')
def metrics():
return generate_latest()


def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
observations = []
with open("/proc/stat") as procstat:
procstat.readline() # skip the first line
for line in procstat:
if not line.startswith("cpu"):
break
cpu, *states = line.split()
observations.append(Observation(
int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
observations.append(Observation(
int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
observations.append(Observation(
int(states[2]) // 100, {"cpu": cpu, "state": "system"}))
return observations


meter = get_meter("example-meter")
counter = meter.create_counter("example-counter")
meter.create_observable_counter(
"system.cpu.time",
callbacks=[cpu_time_callback],
unit="s",
description="CPU time"
)


if __name__ == "__main__":
host = os.environ.get('APP_HOST_NAME', "0.0.0.0")
port = int(os.environ.get('APP_PORT', 5000))
app.run(debug=True, host=host, port=port)

Также мы укажем в docker-compose.yaml то, как поднимать Prometheus:

services:
app:
image: iu5devops/app
build:
context: ./app
dockerfile: Dockerfile
container_name: iu5devops-app
networks:
- iu5devops
ports:
- 8080:5000
environment:
- APP_SERVICE_NAME=iu5devops-app
- TRACE_ENDPOINT=http://iu5devops-jaeger:4317

jaeger:
image: jaegertracing/all-in-one
container_name: iu5devops-jaeger
networks:
- iu5devops
ports:
- 16686:16686

prometheus:
image: prom/prometheus
container_name: iu5devops-prometheus
networks:
- iu5devops
ports:
- 9090:9090
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus

volumes:
prometheus-data: {}

networks:
iu5devops: {}

Соберите контейнер снова и запустите полученную конфигурацию:

docker compose down
docker compose build
docker compose up -d

Теперь у нас стал доступен графический интерфейс Prometheus (http://localhost:9090/).

Сначала проверьте, что экспортер нашего приложения работает нормально. Откройте в браузере http://localhost:8080/metrics в своем веб-браузере. Вы можете увидеть, например, следующее:

Просмотр примера выходных данных
# HELP python_gc_objects_collected_total Objects collected during gc
# TYPE python_gc_objects_collected_total counter
python_gc_objects_collected_total{generation="0"} 2177.0
python_gc_objects_collected_total{generation="1"} 735.0
python_gc_objects_collected_total{generation="2"} 0.0
# HELP python_gc_objects_uncollectable_total Uncollectable objects found during GC
# TYPE python_gc_objects_uncollectable_total counter
python_gc_objects_uncollectable_total{generation="0"} 0.0
python_gc_objects_uncollectable_total{generation="1"} 0.0
python_gc_objects_uncollectable_total{generation="2"} 0.0
# HELP python_gc_collections_total Number of times this generation was collected
# TYPE python_gc_collections_total counter
python_gc_collections_total{generation="0"} 110.0
python_gc_collections_total{generation="1"} 10.0
python_gc_collections_total{generation="2"} 0.0
# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="12",patchlevel="0",version="3.12.0"} 1.0
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 1.653633024e+09
# HELP process_resident_memory_bytes Resident memory size in bytes.
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 5.656576e+07
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1.70008076238e+09
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 2.62
# HELP process_open_fds Number of open file descriptors.
# TYPE process_open_fds gauge
process_open_fds 11.0
# HELP process_max_fds Maximum number of open file descriptors.
# TYPE process_max_fds gauge
process_max_fds 1.048576e+06
# HELP system_cpu_time_s_total CPU time
# TYPE system_cpu_time_s_total counter
system_cpu_time_s_total{cpu="cpu0",state="user"} 36.0
system_cpu_time_s_total{cpu="cpu0",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu0",state="system"} 26.0
system_cpu_time_s_total{cpu="cpu1",state="user"} 39.0
system_cpu_time_s_total{cpu="cpu1",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu1",state="system"} 29.0
system_cpu_time_s_total{cpu="cpu2",state="user"} 47.0
system_cpu_time_s_total{cpu="cpu2",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu2",state="system"} 28.0
system_cpu_time_s_total{cpu="cpu3",state="user"} 41.0
system_cpu_time_s_total{cpu="cpu3",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu3",state="system"} 27.0
system_cpu_time_s_total{cpu="cpu4",state="user"} 38.0
system_cpu_time_s_total{cpu="cpu4",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu4",state="system"} 25.0
system_cpu_time_s_total{cpu="cpu5",state="user"} 36.0
system_cpu_time_s_total{cpu="cpu5",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu5",state="system"} 25.0
system_cpu_time_s_total{cpu="cpu6",state="user"} 36.0
system_cpu_time_s_total{cpu="cpu6",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu6",state="system"} 27.0
system_cpu_time_s_total{cpu="cpu7",state="user"} 47.0
system_cpu_time_s_total{cpu="cpu7",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu7",state="system"} 26.0
system_cpu_time_s_total{cpu="cpu8",state="user"} 38.0
system_cpu_time_s_total{cpu="cpu8",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu8",state="system"} 27.0
system_cpu_time_s_total{cpu="cpu9",state="user"} 37.0
system_cpu_time_s_total{cpu="cpu9",state="nice"} 0.0
system_cpu_time_s_total{cpu="cpu9",state="system"} 25.0

Если метрика отдается через экспортер, то самое время проверить как дела на принимающей стороне. Откройте в браузере UI Prometheus http://localhost:9090/ и перейдите через верхнее меню на страницу Targets (Status>Targets). Если все в порядке, то статус цели нашего endpoint-а нашего приложения будет up:

Targets Prometheus

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

Prometheus query example

Выполните несколько запросов:

Prometheus query example

Prometheus query example