Введение: Почему мониторинг — основа production-стабильности
В современной разработке написать код — лишь половина дела. Чтобы приложение на Kotlin работало стабильно под нагрузкой, необходимо понимать его внутреннее состояние. Базовые логи и health checks дают лишь поверхностную картину. Продвинутый мониторинг с метриками и алертами позволяет не просто фиксировать сбои, а предсказывать их, выявлять узкие места и оперативно реагировать на аномалии. В этой статье мы разберем, как построить production-ready систему мониторинга для Kotlin-приложений на стеке Micrometer + Prometheus + Alertmanager + Grafana.
1. Сбор метрик с помощью Micrometer и Prometheus
Micrometer — это фасад для сбора метрик, который де-факто стал стандартом в мире JVM и Kotlin. Он поддерживает множество бэкендов: Prometheus, Datadog, New Relic и другие. Для Kotlin-приложений (Spring Boot, Ktor, http4k) интеграция тривиальна.
1.1. Подключение и настройка
Добавьте в build.gradle.kts необходимые зависимости:
// Для Spring Boot
implementation("io.micrometer:micrometer-registry-prometheus")
// Для Ktor (ручная настройка)
implementation("io.micrometer:micrometer-core:1.12.0")
implementation("io.micrometer:micrometer-registry-prometheus:1.12.0")
Для Spring Boot автоматически создается бин MeterRegistry. В Ktor вам нужно инициализировать его вручную:
import io.micrometer.prometheus.PrometheusMeterRegistry
import io.micrometer.core.instrument.Clock
import io.prometheus.client.CollectorRegistry
val registry = PrometheusMeterRegistry(
PrometheusConfig.DEFAULT,
CollectorRegistry.defaultRegistry,
Clock.SYSTEM
)
// Экспорт эндпоинта /metrics
embeddedServer(Netty, port = 8080) {
routing {
get("/metrics") {
call.respondText(registry.scrape())
}
}
}.start(wait = true)
1.2. Кастомные метрики
Помимо стандартных (JVM, CPU, память), добавляйте бизнес-метрики. Пример счетчика обработанных заказов:
val orderCounter = Counter.builder("orders.processed")
.description("Total processed orders")
.register(registry)
fun processOrder(order: Order) {
// бизнес-логика
orderCounter.increment()
}
Или гистограмма для времени выполнения запроса:
val requestTimer = Timer.builder("http.requests.duration")
.description("Duration of HTTP requests")
.publishPercentiles(0.5, 0.95, 0.99)
.sla(Duration.ofMillis(100), Duration.ofMillis(500))
.register(registry)
fun handleRequest(block: () -> Response): Response {
return requestTimer.record(block)
}
2. Продвинутые алерты: от пороговых значений к предсказаниям
Алерты на основе простых порогов (например, CPU > 90%) часто дают ложные срабатывания. На продвинутом уровне мы используем Alertmanager и правила PromQL с агрегацией и прогнозированием.
2.1. Настройка Alertmanager
Конфигурация alertmanager.yml для отправки уведомлений в Telegram и Slack:
route:
receiver: 'team-alerts'
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
receivers:
- name: 'team-alerts'
telegram_configs:
- bot_token: 'YOUR_BOT_TOKEN'
chat_id: -123456789
parse_mode: 'HTML'
slack_configs:
- api_url: 'https://hooks.slack.com/services/...'
channel: '#alerts'
2.2. Правила PromQL для умных алертов
В rules.yml опишем правило, которое срабатывает, если 95-й перцентиль времени ответа превышает 500 мс в течение 5 минут:
groups:
- name: http_latency
rules:
- alert: HighLatencyP95
expr: |
histogram_quantile(0.95,
sum(rate(http_requests_duration_seconds_bucket[5m])) by (le, endpoint)
) > 0.5
for: 5m
annotations:
summary: "Высокая задержка на {{ $labels.endpoint }}"
Предсказательный алерт на основе скорости роста очереди задач:
- alert: QueueGrowthPrediction
expr: |
predict_linear(job_queue_size[1h], 3600) > 1000
for: 2m
annotations:
summary: "Очередь задач превысит 1000 через час"
3. Интеграция с Grafana: дашборды и визуализация
Grafana — стандартный инструмент для визуализации метрик Prometheus. Создадим дашборд с ключевыми панелями.
3.1. Базовый дашборд для Kotlin-приложения
Пример JSON для импорта в Grafana (фрагмент панели "JVM Heap"):
{
"panels": [
{
"title": "JVM Heap Memory",
"type": "graph",
"targets": [
{
"expr": "jvm_memory_used_bytes{area=\"heap\"} / 1024^3",
"legendFormat": "Used (GB)",
"refId": "A"
},
{
"expr": "jvm_memory_max_bytes{area=\"heap\"} / 1024^3",
"legendFormat": "Max (GB)",
"refId": "B"
}
]
}
]
}
3.2. Продвинутые панели: heatmap и статистика
Heatmap для распределения времени ответа:
{
"title": "HTTP Request Duration Heatmap",
"type": "heatmap",
"targets": [
{
"expr": "sum(increase(http_requests_duration_seconds_bucket[5m])) by (le)",
"format": "heatmap",
"legendFormat": "{{ le }}"
}
]
}
Панель "Health Score" — агрегированный показатель здоровья:
- expr: |
(
(1 - rate(http_requests_errors_total[5m]) / rate(http_requests_total[5m])) * 0.4 +
(1 - rate(jvm_gc_pause_seconds_sum[5m]) / 60) * 0.3 +
(jvm_memory_used_bytes{area="heap"} / j