Публичное собеседование по System design
Сегодня мы разберём публичное собеседование по проектированию системы мониторинга масштаба «условного микро-Google» — с 10 тысячами сервисов, разнообразным стеком технологий и требованиями к высокой доступности. Интервьюер Вова и кандидат Виталий последовательно обсуждают архитектуру сбора метрик, хранение данных (VictoriaMetrics, ClickHouse, M3), визуализацию через Grafana, настройку алертинга с Alert Manager, а также сбор и безопасность логов с учётом требований регуляторов. В ходе обсуждения участники балансируют между использованием готовых решений и необходимостью глубже понимать их внутреннее устройство, а также затрагивают вопросы автоматизации, масштабирования и защиты персональных данных.
Вопрос 1. Как спроектировать систему мониторинга, которую можно разворачивать где угодно, которая собирает метрики с сервисов, визуализирует их, показывает средние значения и перцентили, является отказоустойчивой, поддерживает большую нагрузку, масштабируется и позволяет настраивать алерты с отправкой уведомлений по почте, в Slack и другие каналы?
Таймкод: 00:01:51
Ответ собеседника: Правильный. Предложена полная архитектура системы мониторинга для масштаба ~10000 сервисов (~30000 реплик). Сбор метрик: на каждую ноду/VM/контейнер ставятся экспортёры (Prometheus exporters) — адаптеры, собирающие данные из ОС, приложений, Kubernetes и представляющие в едином формате. Для каждой группы ~1000 сервисов (10 хранилищ) используется пуллер метрик, опрашивающий экспортёры. Нагрузка ~50 запросов/сек на пуллер — очень лёгкая. Хранение: VictoriaMetrics или ClickHouse — специализированные TSDB с горизонтальным масштабированием, поддержкой downsampling (сжатие старых метрик с сохранением средних/медиан). Визуализация: Grafana с множественными инстансами за балансировщиком, подключёнными к общей реплицированной базе настроек. Поддержка ACL, авторизации, отображения логов вместе с метриками. Алертинг: Prometheus Alert Manager или Grafana Alerting с Infrastructure as Code подходом (файлы алертов в репозиториях сервисов, автоматический деплой). Система алертов реплицирована с нечётным количеством инстансов для кворума и предотвращения дубликатов. Интеграция с Slack: вебхуки хранятся в Vault, CI/CD подхватывает переменные окружения при деплое. Безопасность: HTTPS, разграничение доступа через Grafana ACL. Если лейблы содержат персональные данные — доступ только для ограниченной команды. Логи: подход 12-factor app (stdout, структурированный JSON). Агенты (fluentd/filebeat/rsyslog) на каждой ноде собирают, парсят и отправляют логи в ELK-стек. Буферизация при недоступности хранилища. Маскирование чувствительных данных через middleware на стороне сервиса перед записью в лог. Упомянут Falco для обнаружения аномальной активности в Kubernetes. Затронуты вопросы регуляторики (152-ФЗ, GDPR), хранения и удаления персональных данных, оптимизации хранения логов.
Правильный ответ:
Ответ собеседника является полным и проработанным, охватывает все ключевые аспекты проектирования системы мониторинга и демонстрирует глубокое понимание предметной области. Тем не менее, для дополнительной полноты и структурирования информации, а также для тех, кто готовится к интервью, ниже приведено развёрнутое описание архитектуры с акцентом на ключевые решения и нюансы.
1. Общая архитектурная парадигма — модульность и переносимость
Система мониторинга должна быть спроектирована как набор слабо связанных компонентов, каждый из которых можно заменить или масштабировать независимо. Ключевой принцип — «разворачиваемость где угодно» означает, что все компоненты должны быть контейнеризованы (Docker), оркестрированы (Kubernetes / Docker Compose для простых случаев), а конфигурация вынесена в переменные окружения и/или конфигурационные файлы (ConfigMap, HashiCorp Vault). Ни один компонент не должен быть привязан к конкретному облаку или инфраструктуре.
2. Сбор метрик (Metrics Collection)
А. Push vs Pull модели
Pull-модель (Prometheus-стиль) предпочтительна для сервисов, которые можно опросить: экспортёр предоставляет HTTP-эндпоинт /metrics, и пуллер (scraper) периодически опрашивает его. Это даёт контроль над частотой опроса и упрощает обнаружение недоступных сервисов.
Push-модель необходима для короткоживущих задач (cron jobs, serverless-функции) и для агентов на уровне инфраструктуры. Здесь используются агенты, которые сами отправляют метрики в коллектор.
На практике для масштаба ~10000 сервисов применяется гибридный подход: pull для long-running сервисов через Prometheus exporters, push для batch-задач и инфраструктурных метрик через Telegraf / Vector.
Б. Экспортёры (Exporters)
На каждой ноде / VM / Pod разворачиваются экспортёры:
- Node Exporter — метрики ОС (CPU, RAM, диск, сеть)
- cAdvisor / kubelet metrics — метрики контейнеров в Kubernetes
- Process Exporter — метрики отдельных процессов
- Blackbox Exporter — проверка доступности HTTP/TCP/DNS эндпоинтов
- Кастомные экспортёры — для бизнес-метрик из приложений
Кастомные метрики в Go-сервисах реализуются через клиентскую библиотеку prometheus/client_golang:
package main
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
requestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests",
Buckets: prometheus.DefBuckets,
}, []string{"method", "endpoint", "status"})
requestCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
}, []string{"method", "endpoint", "status"})
activeConnections = promauto.NewGauge(prometheus.GaugeOpts{
Name: "active_connections",
Help: "Number of active connections",
})
)
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
duration := time.Since(start).Seconds()
requestDuration.WithLabelValues(r.Method, r.URL.Path, "200").Observe(duration)
requestCount.WithLabelValues(r.Method, r.URL.Path, "200").Inc()
})
}
func main() {
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
В. Агрегация и маршрутизация метрик (Metrics Gateway / Router)
Для масштаба в тысячи сервисов прямой scraping каждого экспортёра одним Prometheus-сервером невозможен. Используется иерархическая или федеративная архитектура:
- Федерация: множество Prometheus-серверов (shards) собирают метрики со своих групп сервисов, а центральный Prometheus агрегирует с них.
- Remote Write: каждый Prometheus shard пишет в централизованное хранилище через протокол remote write (VictoriaMetrics, Thanos, Cortex).
- Service Discovery: автоматическое обнаружение целей через Kubernetes API, Consul, DNS, файловые конфигурации.
3. Хранение метриц (Time-Series Database)
А. Выбор TSDB
Для масштабируемой системы мониторинга подходят:
- VictoriaMetrics — высокая производительность записи и чтения, горизонтальное масштабование через vmcluster, встроенная поддержка downsampling, PromQL-совместимость.
- Thanos — надстройка над Prometheus, обеспечивающая глобальный вид, long-term хранение в object storage (S3), downsampling.
- Cortex — аналогично Thanos, но с другой архитектурой (отдельные компоненты для ingestion, storage, querying).
- ClickHouse — универсальное решение, хорошо подходит для метрик и логов одновременно, но требует больше ручной работы для реализации TSDB-паттернов.
- TimescaleDB — PostgreSQL-расширение, удобно если уже есть PostgreSQL-инфраструктура.
Б. Downsampling и retention
Для экономии места старые данные агрегируются:
- Исходные данные (15 секунд интервал) хранятся 7 дней
- Агрегированные (1 минута, avg/median/p99) хранятся 30 дней
- Ещё более агрегированные (5 минут) хранятся 1 год
VictoriaMetrics поддерживает downsampling на уровне конфигурации:
# vmagent конфигурация с downsampling
global:
scrape_interval: 15s
# В VictoriaMetrics через recording rules
rule_groups:
- name: downsampling_1m
interval: 1m
rules:
- record: job:http_request_duration_seconds:rate1m
expr: rate(http_request_duration_seconds[1m])
- record: job:http_request_duration_seconds:p99_1m
expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[1m]))
В. Горизонтальное масштабование хранилища
VictoriaMetrics в кластерном режиме (vmcluster) разделяет данные по ингестерам (vminsert) и хранилищам (vmstorage), что позволяет масштабировать каждый слой независимо. Thanos использует object storage (S3, GCS) как основное хранилище, что делает его фактически бесконечным по объёму.
4. Визуализация (Visualization)
А. Grafana как стандарт индустрии
Grafana поддерживает множество источников данных (Prometheus, VictoriaMetrics, ClickHouse, PostgreSQL, Elasticsearch), имеет мощный движок дашбордов, систему пользователей и ролей (ACL).
Для отказоустойчивости:
- Несколько инстансов Grafana за балансировщиком (L4/L7)
- Общая база данных настроек (PostgreSQL / MySQL с репликацией)
- Дашборды как код (Grafana Dashboard JSON в Git, автоматический деплой через CI/CD или Grafana Provisioning)
Б. Расчёт средних и перцентилей
Grafana напрямую использует PromQL / MetricsQL для вычисления:
# Среднее время ответа за 5 минут
rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m])
# 99-й перцентиль
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))
# Медиана
histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))
В. Единый интерфейс для метрик и логов
Grafana поддерживает связку с Loki (для логов) и Tempo (для трейсов), что позволяет переходить от графика метрики к соответствующим логам одним кликом. Это реализуется через коррелированные лейблы (trace_id, service_name, pod_name).
5. Алертинг (Alerting)
А. Движок алертов
- Prometheus Alertmanager — стандартный инструмент, поддерживает группировку, ингибирование, тишину (silences), маршрутизацию по лейблам.
- Grafana Alerting — встроенный в Grafana, поддерживает multi-dimensional alerting (один алерт на множество инстансов), уведомления через множество каналов.
- Cortex Ruler / Thanos Ruler — для централизованных правил алертинга в распределённых системах.
Б. Infrastructure as Code для алертов
Правила алертов хранятся в Git-репозиториях сервисов и автоматически применяются при деплое. Это обеспечивает:
- Версионирование алертов
- Code review изменений
- Откат при проблемах
- Единообразие формата
Пример правила алерта (Prometheus format):
groups:
- name: service_alerts
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
> 0.05
for: 2m
labels:
severity: critical
team: backend
annotations:
summary: "High error rate on {{ $labels.service }}"
description: "Error rate is {{ $value | humanizePercentage }} for {{ $labels.service }}"
runbook_url: "https://wiki.internal/runbooks/high-error-rate"
- alert: HighLatency
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "P99 latency > 2s for {{ $labels.service }}"
В. Отказоустойчивость алертинга
Alertmanager разворачивается кластером из нечётного числа инстансов (3, 5) с протоколом gossip для синхронизации состояния. Это предотвращает:
- Дублирование уведомлений (deduplication)
- Потерю алертов при падении одного инстанса
- Split-brain ситуации
Г. Каналы уведомлений (Notification Channels)
Alertmanager / Grafana Alerting поддерживают:
- Email — через SMTP-сервер (Gmail, SendGrid, корпоративный SMTP)
- Slack — через Incoming Webhooks, канал определяется лейблом алерта
- Telegram — через Bot API
- PagerDuty / Opsgenie — для escalation policy
- Webhook — для произвольных интеграций (SMS-шлюз, внутренний мессенджер)
Конфигурация маршрутизации в Alertmanager:
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'
route:
receiver: 'default-slack'
group_by: ['alertname', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- match:
severity: critical
receiver: 'pagerduty-critical'
repeat_interval: 1h
- match:
severity: warning
receiver: 'slack-warnings'
- match_re:
team: (payments|billing)
receiver: 'slack-payments-team'
receivers:
- name: 'default-slack'
slack_configs:
- channel: '#alerts-general'
send_resolved: true
title: '{{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key_file: '/etc/alertmanager/pagerduty_key'
- name: 'slack-warnings'
slack_configs:
- channel: '#alerts-warnings'
Д. Хранение секретов
Вебхуки, API-клюды, пароли хранятся в HashiCorp Vault, AWS Secrets Manager, или Kubernetes Secrets. CI/CD-пайплайн подставляет их как переменные окружения при деплое. Никогда не коммитить секреты в Git.
6. Сбор и хранение логов (Logging)
А. 12-Factor App подход
Приложения пишут логи в stdout/stderr в структурированном формате (JSON). Оркестратор (Kubernetes) собирает stdout и передаёт агенту на ноде.
Б. Агенты сбора логов
- Fluentd / Fluent Bit — легковесные, поддерживают множество input/output плагинов
- Filebeat (часть Elastic Stack) — специализирован для отправки в Elasticsearch / Logstash
- Vector — высокопроизводительный, написан на Rust, замена Fluentd/Filebeat
- Promtail (часть Loki) — если используется Grafana Loki для хранения логов
В. Хранение логов
- Elasticsearch + Kibana (ELK) — полнотекстовый поиск, мощная агрегация
- Grafana Loki — оптимизирован для логов, индексирует только лейблы (как Prometheus для метрик), значительно дешевле по ресурсам
- ClickHouse — если уже используется для метрик, логи можно хранить там же
Г. Маскирование чувствительных данных
Перед записью в лог чувствительные поля маскируются на уровне middleware в приложении:
func maskSensitiveFields(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Логируем запрос без чувствительных данных
log.Info().
Str("method", r.Method).
Str("path", r.URL.Path).
Str("user_id", r.Header.Get("X-User-ID")).
// НЕ логируем Authorization, Cookie с токенами
Msg("incoming request")
next.ServeHTTP(w, r)
})
}
7. Безопасность
- HTTPS/TLS везде: между компонентами, для внешних эндпоинтов
- mTLS (mutual TLS) между сервисами в кластере (Istio, Linkerd)
- Авторизация и ACL: Grafana поддерживает RBAC, LDAP/OAuth интеграцию
- Разграничение доступа к метрикам: если лейблы содержат PII (персональные данные), доступ к этим метрикам ограничивается через прокси (nginx с auth) или через отдельные datasources в Grafana
- Compliance: 152-ФЗ, GDPR — обеспечение права на удаление, ограничение срока хранения, шифрование данных в rest и transit
8. Обнаружение аномалий и безопасность инфраструктуры
- Falco — runtime security для Kubernetes, обнаружение аномальной активности (неожиданный доступ к файлам, сетевые подключения, shell в контейнере)
- Prometheus + ML-алерты — использование алгоритмов обнаружения аномалий на основе исторических данных (например, через Prometheus Anomaly Detector или внешние сервисы)
9. Масштабирование и отказоустойчивость — сводная таблица
| Компонент | Стратегия масштабирования | Отказоустойчивость |
|---|---|---|
| Экспортёры | DaemonSet на каждой ноде | Не критичны, данные теряются только за scraping interval |
| Prometheus shards | Шардирование по группам сервисов | Федерация / remote write в общее хранилище |
| VictoriaMetrics / Thanos | Горизонтальное масштабирование vmstorage / ingesters | Репликация данных (replication factor ≥ 2) |
| Grafana | Горизонтальное масштабирование за LB | Общая БД настроек, сессии в Redis |
| Alertmanager | Кластер с gossip-протоколом | Минимум 3 инстанса, кворум |
| Elasticsearch | Шардирование индексов, реплики | Минимум 3 мастер-ноды, реплики шардов |
| Fluentd / Vector | DaemonSet на каждой ноде | Буферизация на диск при недоступности приёмника |
10. Порядок развёртывания (Deployment)
Для обеспечения «разворачиваемости где угодно»:
- Helm Charts — для Kubernetes-развёртывания (prometheus-community/kube-prometheus-stack, victoria-metrics-k8s-stack)
- Docker Compose — для локальной разработки и тестирования
- Terraform / Pulumi — для провижининга инфраструктуры
- Ansible — для bare-metal / VM развёртывания
Минимальный набор для старта (Docker Compose):
version: '3'
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:latest
environment:
- GF_SECURITY_ADMIN_PASSWORD=secret
ports:
- "3000:3000"
depends_on:
- prometheus
alertmanager:
image: prom/alertmanager:latest
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
ports:
- "9093:9093"
node-exporter:
image: prom/node-exporter:latest
ports:
- "9100:9100"
11. Мониторинг самой системы мониторинга
Критически важно мониторить саму систему мониторина (meta-monitoring):
- Доступность каждого компонента (up/up метрики Prometheus)
- Задержка алертов (alertmanager_notifications_total)
- Объём хранимых данных (storage_usage_bytes)
- Lag реплик баз данных
- Использование ресурсов самими компонентами мониторинга
