Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Telemetry — телеметрия
telemetry

Telemetry — телеметрия

Метрики, распределённый трейсинг и observability в 12-Factor приложениях

Telemetry: Телеметрия

Метрики, распределённый трейсинг и observability в 12-Factor приложениях

#Суть принципа

Telemetry — современное расширение 12-Factor App (13-й фактор). Принцип гласит:

Приложение должно предоставлять телеметрию: метрики производительности, распределённый трейсинг и health checks для полной наблюдаемости (observability) в облачных средах.**

┌─────────────────────────────────────────────────────────────────┐
│                    Три столпа Observability                     │
│                                                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐             │
│  │   Метрики   │  │    Логи     │  │   Трейсы    │             │
│  │  (Metrics)  │  │   (Logs)    │  │  (Traces)   │             │
│  │             │  │             │  │             │             │
│  │ - Задержка  │  │ - События   │  │ - Путь      │             │
│  │ - Ошибки    │  │ - Контекст  │  │   запроса   │             │
│  │ - Трафик    │  │ - Детали    │  │ - Спаны     │             │
│  └─────────────┘  └─────────────┘  └─────────────┘             │
│                                                                  │
│  Health Checks: readiness, liveness, startup                    │
└─────────────────────────────────────────────────────────────────┘

#Метрики (Metrics)

#Типы метрик

from prometheus_client import Counter, Histogram, Gauge, Summary # Counter — счётчик (только увеличивается) # Пример: количество запросов, ошибок http_requests_total = Counter( 'http_requests_total', 'Total HTTP requests', ['method', 'endpoint', 'status'] ) # Histogram — гистограмма (распределение значений) # Пример: время ответа, размер ответа http_request_duration_seconds = Histogram( 'http_request_duration_seconds', 'HTTP request duration in seconds', ['method', 'endpoint'], buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0] ) # Gauge — измеритель (может увеличиваться и уменьшаться) # Пример: количество активных соединений, использование памяти active_connections = Gauge( 'active_connections', 'Number of active connections' ) # Summary — сводка (квантили) # Пример: p50, p95, p99 времени ответа http_request_duration_summary = Summary( 'http_request_duration_summary_seconds', 'HTTP request duration summary', ['method', 'endpoint'] )

#Инструментирование приложения

from flask import Flask, request, g from prometheus_client import generate_latest, CONTENT_TYPE_LATEST import time import functools app = Flask(__name__) # Middleware для сбора метрик @app.before_request def before_request(): g.start_time = time.time() g.request_id = request.headers.get('X-Request-ID', str(uuid.uuid4())) @app.after_request def after_request(response): if hasattr(g, 'start_time'): duration = time.time() - g.start_time http_request_duration_seconds.labels( method=request.method, endpoint=request.endpoint or 'unknown' ).observe(duration) http_requests_total.labels( method=request.method, endpoint=request.endpoint or 'unknown', status=response.status_code ).inc() response.headers['X-Request-ID'] = getattr(g, 'request_id', '') return response # Endpoint для Prometheus @app.route('/metrics') def metrics(): return generate_latest(), 200, {'Content-Type': CONTENT_TYPE_LATEST} # Health check endpoint @app.route('/health') def health(): return {'status': 'healthy'}, 200

#RED-метрики для сервисов

# RED: Rate, Errors, Duration from prometheus_client import Counter, Histogram # Rate — количество запросов в секунду requests_per_second = Counter( 'service_requests_total', 'Total requests', ['service', 'method', 'status'] ) # Errors — количество ошибок errors_per_second = Counter( 'service_errors_total', 'Total errors', ['service', 'error_type'] ) # Duration — время ответа duration = Histogram( 'service_request_duration_seconds', 'Request duration', ['service', 'method'], buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0] ) # Использование def track_request(service, method): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.time() try: result = func(*args, **kwargs) requests_per_second.labels( service=service, method=method, status='success' ).inc() return result except Exception as e: requests_per_second.labels( service=service, method=method, status='error' ).inc() errors_per_second.labels( service=service, error_type=type(e).__name__ ).inc() raise finally: duration.labels( service=service, method=method ).observe(time.time() - start) return wrapper return decorator

#USE-метрики для инфраструктуры

# USE: Utilization, Saturation, Errors # Utilization — процент использования ресурса cpu_utilization = Gauge( 'node_cpu_utilization', 'CPU utilization percentage', ['node'] ) memory_utilization = Gauge( 'node_memory_utilization', 'Memory utilization percentage', ['node'] ) # Saturation — степень заполненности очереди disk_saturation = Gauge( 'node_disk_saturation', 'Disk saturation percentage', ['node', 'device'] ) # Errors — ошибки оборудования disk_errors = Counter( 'node_disk_errors_total', 'Total disk errors', ['node', 'device'] )

#Распределённый трейсинг (Distributed Tracing)

#OpenTelemetry интеграция

from opentelemetry import trace from opentelemetry.exporter.jaeger.thrift import JaegerExporter from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.instrumentation.flask import FlaskInstrumentor from opentelemetry.instrumentation.requests import RequestsInstrumentor # Настройка трейсера trace.set_tracer_provider(TracerProvider()) # Экспорт в Jaeger jaeger_exporter = JaegerExporter( agent_host_name='jaeger', agent_port=6831, ) span_processor = BatchSpanProcessor(jaeger_exporter) trace.get_tracer_provider().add_span_processor(span_processor) # Автоматическое инструментирование Flask app = Flask(__name__) FlaskInstrumentor().instrument_app(app) # Автоматическое инструментирование HTTP-запросов RequestsInstrumentor().instrument() # Ручное создание спанов tracer = trace.get_tracer(__name__) @app.route('/api/users/<int:user_id>') def get_user(user_id): with tracer.start_as_current_span('get_user') as span: span.set_attribute('user.id', user_id) # Вложенный спан для запроса к БД with tracer.start_as_current_span('database_query') as db_span: db_span.set_attribute('db.table', 'users') user = db.query('SELECT * FROM users WHERE id = ?', user_id) if not user: span.set_attribute('user.found', False) return {'error': 'User not found'}, 404 span.set_attribute('user.found', True) return user

#Контекст трейсинга

from opentelemetry import context, baggage #Propagation контекста между сервисами from opentelemetry.propagate import inject, extract # Извлечение контекста из входящих заголовков @app.before_request def extract_context(): ctx = extract(request.headers) context.attach(ctx) # Инъекция контекста в исходящие запросы import requests def make_request(url): headers = {} inject(headers) # Добавляет traceparent, tracestate return requests.get(url, headers=headers) # Работа с baggage (дополнительные данные) @app.route('/api/process') def process(): # Добавление данных в baggage ctx = baggage.set_baggage('user.id', '123') ctx = baggage.set_baggage('tenant.id', 'acme') token = context.attach(ctx) try: # Все спаны в этом контексте будут иметь baggage with tracer.start_as_current_span('process'): # ... pass finally: context.detach(token)

#Визуализация в Jaeger

# docker-compose.yml с Jaeger version: '3.8' services: jaeger: image: jaegertracing/all-in-one:1.52 ports: - "5775:5775/udp" # Thrift compact - "6831:6831/udp" # Thrift binary - "6832:6832/udp" # Thrift binary - "5778:5778" # Config - "16686:16686" # UI - "14268:14268" # HTTP - "14250:14250" # gRPC environment: - COLLECTOR_ZIPKIN_HOST_PORT=:9411
┌─────────────────────────────────────────────────────────────────┐
│                    Jaeger UI Trace                              │
│                                                                  │
│  Trace ID: abc123def456                                         │
│  Service: myapp                                                 │
│  Operation: GET /api/users                                      │
│                                                                  │
│  ┌────────────────────────────────────────────────────────┐    │
│  │ myapp: GET /api/users                      [████████]  │    │
│  │   ├─ database: SELECT users                [████]      │    │
│  │   ├─ redis: GET session                  [██]          │    │
│  │   └─ external-api: GET /data             [██████]      │    │
│  │       └─ database: SELECT data           [████]        │    │
│  └────────────────────────────────────────────────────────┘    │
│                                                                  │
│  Duration: 245ms                                                │
│  Spans: 5                                                       │
└─────────────────────────────────────────────────────────────────┘

#Health Checks

#Типы health checks

from flask import Flask, jsonify from datetime import datetime import psycopg2 import redis app = Flask(__name__) @app.route('/health/live') def liveness(): """ Liveness probe — приложение живо? Kubernetes убивает под, если liveness fails. Используется для обнаружения deadlock. """ return jsonify({ 'status': 'alive', 'timestamp': datetime.utcnow().isoformat() }), 200 @app.route('/health/ready') def readiness(): """ Readiness probe — приложение готово принимать трафик? Kubernetes не отправляет трафик, если readiness fails. Используется во время startup и shutdown. """ checks = {} # Проверка БД try: conn = psycopg2.connect(app.config['DATABASE_URL']) conn.close() checks['database'] = 'ok' except Exception as e: checks['database'] = f'error: {str(e)}' # Проверка Redis try: r = redis.from_url(app.config['REDIS_URL']) r.ping() checks['redis'] = 'ok' except Exception as e: checks['redis'] = f'error: {str(e)}' # Определение статуса all_ok = all(v == 'ok' for v in checks.values()) status_code = 200 if all_ok else 503 return jsonify({ 'status': 'ready' if all_ok else 'not_ready', 'checks': checks, 'timestamp': datetime.utcnow().isoformat() }), status_code @app.route('/health/startup') def startup(): """ Startup probe — приложение завершило инициализацию? Kubernetes не проверяет liveness/readiness, пока startup fails. Используется для медленной инициализации. """ # Проверка завершения миграций if not migrations_complete(): return jsonify({ 'status': 'starting', 'reason': 'migrations in progress' }), 503 return jsonify({ 'status': 'started', 'timestamp': datetime.utcnow().isoformat() }), 200 def migrations_complete(): # Логика проверки миграций return True

#Kubernetes health checks

# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: web image: myapp:latest ports: - containerPort: 5000 # Liveness probe livenessProbe: httpGet: path: /health/live port: 5000 initialDelaySeconds: 10 # Ждать 10с перед первой проверкой periodSeconds: 10 # Проверять каждые 10с timeoutSeconds: 5 # Таймаут 5с failureThreshold: 3 # 3 неудачи = перезапуск # Readiness probe readinessProbe: httpGet: path: /health/ready port: 5000 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 successThreshold: 1 # 1 успех = готов # Startup probe (для медленной инициализации) startupProbe: httpGet: path: /health/startup port: 5000 initialDelaySeconds: 0 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 30 # До 150с на инициализацию

#Prometheus конфигурация

#Scrape config

# prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'myapp' static_configs: - targets: ['myapp:5000'] metrics_path: /metrics scrape_interval: 5s # Более частый скрейп для приложения - job_name: 'kubernetes-pods' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: true - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] action: replace target_label: __metrics_path__ regex: (.+)

#Alert rules

# alerting_rules.yml groups: - name: myapp rules: # Высокая задержка - alert: HighLatency expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1 for: 5m labels: severity: warning annotations: summary: "High latency detected" description: "95th percentile latency is {{ $value }}s" # Высокий уровень ошибок - alert: HighErrorRate expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05 for: 2m labels: severity: critical annotations: summary: "High error rate detected" description: "Error rate is {{ $value | humanizePercentage }}" # Под не готов - alert: PodNotReady expr: kube_pod_status_ready{condition="true"} == 0 for: 1m labels: severity: warning annotations: summary: "Pod {{ $labels.pod }} is not ready" # Мало реплик - alert: LowReplicas expr: kube_deployment_status_replicas_available < kube_deployment_spec_replicas for: 5m labels: severity: warning annotations: summary: "Deployment {{ $labels.deployment }} has fewer replicas than desired"

#Grafana дашборды

#Пример дашборда

{ "dashboard": { "title": "MyApp Overview", "panels": [ { "title": "Request Rate", "targets": [ { "expr": "rate(http_requests_total[5m])", "legendFormat": "{{ method }} {{ endpoint }}" } ], "type": "graph" }, { "title": "Error Rate", "targets": [ { "expr": "rate(http_requests_total{status=~\"5..\"}[5m]) / rate(http_requests_total[5m]) * 100", "legendFormat": "Error %" } ], "type": "graph", "alert": { "conditions": [ { "evaluator": {"params": [5], "type": "gt"}, "query": {"params": ["A", "5m", "now"]}, "reducer": {"type": "avg"} } ] } }, { "title": "Latency (p50, p95, p99)", "targets": [ { "expr": "histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))", "legendFormat": "p50" }, { "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))", "legendFormat": "p95" }, { "expr": "histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))", "legendFormat": "p99" } ], "type": "graph" }, { "title": "Active Connections", "targets": [ { "expr": "active_connections", "legendFormat": "Connections" } ], "type": "stat" } ] } }

#Антипаттерны

#❌ Метрики без лейблов

# ❌ Неправильно: нет лейблов для фильтрации request_count = Counter('request_count', 'Total requests') # Невозможно разбить по endpoint, method, status

Решение:

# ✅ Правильно: лейблы для измерения request_count = Counter( 'http_requests_total', 'Total HTTP requests', ['method', 'endpoint', 'status'] )

#❌ Высокая кардинальность лейблов

# ❌ Неправильно: user_id как лейбл (высокая кардинальность) requests_by_user = Counter( 'requests_by_user_total', 'Requests by user', ['user_id'] # Миллионы пользователей! )

Проблема: Миллионы уникальных user_id создают миллионы временных рядов, что перегружает Prometheus.

Решение:

# ✅ Правильно: агрегация без высокой кардинальности requests_total = Counter( 'http_requests_total', 'Total requests', ['method', 'endpoint'] # user_id логируется, не метрикуется )

#❌ Отсутствие health checks

# ❌ Неправильно: нет health check endpoint # Kubernetes не может определить готовность пода

Решение:

# ✅ Правильно: все три типа health checks @app.route('/health/live') def liveness(): ... @app.route('/health/ready') def readiness(): ... @app.route('/health/startup') def startup(): ...

#Проверка соответствия принципу

Задайте себе вопросы:

  • Инструментировано ли приложение метриками (RED/USE)?
  • Настроен ли распределённый трейсинг (OpenTelemetry)?
  • Есть ли health check endpoints (liveness, readiness, startup)?
  • Настроены ли алерты в Prometheus?
  • Есть ли Grafana дашборды для мониторинга?
  • Экспортируются ли метрики в Prometheus формате?

#Связь с другими факторами

  • Logs — телеметрия дополняет логи для полной observability
  • Processes — метрики собираются для каждого процесса
  • Disposability — health checks обеспечивают graceful shutdown
  • Dev/Prod Parity — одинаковая телеметрия во всех средах

#Пример из практики

#До: нет наблюдаемости

# ❌ Приложение без телеметрии from flask import Flask app = Flask(__name__) @app.route('/api/users') def get_users(): # Нет метрик, нет трейсинга users = db.query("SELECT * FROM users") return users # Нет health checks # Нет метрик # Нет трейсинга

Проблемы:

  • Невозможно определить причину медленных запросов
  • Нет алертов при росте ошибок
  • Kubernetes не знает о готовности пода

#После: полная observability

# ✅ Приложение с телеметрией from flask import Flask, g from prometheus_client import Counter, Histogram from opentelemetry import trace import time app = Flask(__name__) # Метрики http_requests = Counter('http_requests_total', 'Total requests', ['method', 'endpoint', 'status']) http_duration = Histogram('http_request_duration_seconds', 'Duration', ['method', 'endpoint']) # Трейсинг tracer = trace.get_tracer(__name__) @app.before_request def before_request(): g.start_time = time.time() @app.after_request def after_request(response): duration = time.time() - g.start_time http_requests.labels( method=request.method, endpoint=request.endpoint, status=response.status_code ).inc() http_duration.labels( method=request.method, endpoint=request.endpoint ).observe(duration) return response @app.route('/api/users') def get_users(): with tracer.start_as_current_span('get_users'): users = db.query("SELECT * FROM users") return users @app.route('/health/live') def liveness(): return {'status': 'alive'}, 200 @app.route('/health/ready') def readiness(): # Проверка зависимостей try: db.health_check() redis.ping() return {'status': 'ready'}, 200 except Exception: return {'status': 'not_ready'}, 503

Результат:

  • ✅ Метрики для мониторинга производительности
  • ✅ Трейсинг для отладки распределённых запросов
  • ✅ Health checks для Kubernetes
  • ✅ Алерты при проблемах

Ключевой вывод: Телеметрия (метрики, трейсинг, health checks) обеспечивает полную наблюдаемость приложения в облачных средах. Это позволяет быстро обнаруживать и диагностировать проблемы, автоматически масштабироваться и обеспечивать высокую доступность.