Модуль 1.1: Поглиблене вивчення PromQL
Напрямок PCA | Складність:
[СКЛАДНО]| Час: 50-60 хв | Цільова версія Kubernetes: 1.35 і новіша для лабораторних прикладів та операційних припущень.
Передумови
Розділ «Передумови»Перед початком цього модуля переконайтеся, що наведені нижче основи засвоєні достатньо впевнено, аби ви могли зосередитися на міркуваннях про PromQL замість налаштування середовища:
- Модуль Prometheus — архітектура, pull-модель, базовий PromQL
- Теорія спостережуваності — концепції метрик
- Базові знання Kubernetes
- Запущений екземпляр Prometheus (kind/minikube з kube-prometheus-stack)
Що ви зможете робити
Розділ «Що ви зможете робити»Після завершення цього модуля ви зможете виконувати наведені нижче завдання з PromQL над даними спостережуваності Kubernetes і пояснювати компроміси, що стоять за кожним запитом:
- Будувати запити PromQL із застосуванням діапазонних векторів, операторів агрегації та бінарних операцій, щоб відповідати на виробничі запитання про затримку, частоту помилок та насиченість.
- Застосовувати
histogram_quantileтаrate()коректно, щоб обчислювати перцентильні затримки та посекундні частоти з метрик-лічильників і гістограм. - Створювати правила запису (recording rules), що попередньо обчислюють витратні запити задля продуктивності дашбордів та відстеження SLO.
- Діагностувати оманливу поведінку метрик, зокрема скидання лічильників, вибух кардинальності міток, маркери застарілості та порожні з’єднання, міркуючи про механіку обчислення PromQL.
Під час сплесків трафіку середні значення можуть залишатися в межах SLO, тоді як перцентильна затримка та окремі когорти запитів суттєво деградують. Аналіз хвоста (tail analysis) засобами PromQL — це перший крок до виявлення того, які саме мітки несуть на собі біль і чому. Тримайте дослідження явним і прив’язаним до міток, щоб уловити вузькі місця, які приховують агреговані графіки.
histogram_quantile(0.99, sum by (le, payment_method)(rate(http_request_duration_seconds_bucket{service="checkout"}[5m])))Чому цей модуль важливий
Розділ «Чому цей модуль важливий»Гіпотетичний сценарій: сервіс оформлення замовлення отримує звичайний сумарний трафік, його графік CPU виглядає буденно, а середня тривалість запиту все ще нижча за ціль обслуговування. Проте звернення до підтримки зростають, бо один спосіб оплати дає тайм-аути під час авторизації, і ця відмова прихована, коли всі маршрути, коди статусу, Поди та способи оплати усереднюються разом. Під час такого інциденту корисний оператор не запитує у Prometheus «затримку»; він запитує розподіл затримки за мітками, що відображають користувацький досвід, а тоді звужує запит, доки пошкоджена когорта не стане видимою.
PromQL — це мова, якою ви ставите ці операційні запитання під тиском. Вона також є вагомою частиною іспиту Prometheus Certified Associate, адже Prometheus без PromQL — це здебільшого сховище, повне семплів, які ви не можете інтерпретувати. Важлива навичка — це не запам’ятовування окремих функцій; це розуміння того, як селектори векторів, селектори діапазону, функції, агрегації та з’єднання складаються в відповіді, що можуть керувати рішенням під час збою або огляду архітектури.
Цей модуль вибудовує цю навичку зсередини назовні, крок за кроком. Ви почнете з вибору потрібних часових рядів, потім перетворите сирі лічильники на частоти, агрегуватимете мітки без знищення сенсу, обчислите перцентилі гістограм, безпечно з’єднаєте метрики одна з одною та вирішите, коли саме витратний вираз має стати правилом запису. У прикладах використано конвенції Kubernetes і Prometheus, актуальні для Kubernetes 1.35 і новіших версій, але самі ці міркування переносяться на будь-яку систему на базі Prometheus, бо модель обчислення в усіх таких системах однакова.
Читання PromQL як конвеєра даних
Розділ «Читання PromQL як конвеєра даних»PromQL виглядає набагато менш загадково, коли ви читаєте кожен вираз як конвеєр, що поетапно перетворює набори мічених семплів. Селектор метрики дістає ряди з бази часових рядів, селектор діапазону змінює кожен відповідний ряд на коротку історію семплів, функція перетворює цю історію на одне значення, а агрегація вирішує, які саме мітки виживуть на виході. Якщо ви здатні чітко сказати, що кожен етап отримує на вході й що повертає на виході, то зможете налагодити більшість зламаних запитів, не вгадуючи навмання синтаксис.
Перша ментальна модель — це різниця між миттєвим вектором (instant vector) та діапазонним вектором (range vector). Миттєвий вектор містить один семпл на кожен відповідний ряд у момент обчислення, тоді як діапазонний вектор містить багато семплів на кожен відповідний ряд протягом вікна перегляду назад. Дашбордам та бінарним операторам зазвичай потрібні миттєві вектори, але такі функції, як rate(), increase() та avg_over_time(), потребують діапазонного вектора, бо мають дослідити, як значення змінювалося або поводилося протягом часу.
# Вибрати всі ряди з цією назвою метрикиhttp_requests_total
# Фільтрувати за точним збігом міткиhttp_requests_total{method="GET"}
# Фільтрувати за кількома мітками (логіка AND)http_requests_total{method="GET", status="200"}
# Збіг за регулярним виразом (синтаксис RE2)http_requests_total{status=~"2.."}
# Заперечний збігhttp_requests_total{status!="500"}
# Заперечний збіг за регулярним виразомhttp_requests_total{method!~"OPTIONS|HEAD"}Селектори — це ваше перше рішення щодо кардинальності, тож сприймайте їх як щось більше за фільтри. Широкий селектор на кшталт http_requests_total може повернути кожен сервіс, статус, маршрут, Под та екземпляр, що випромінює цю метрику, — це корисно для дослідження й витратно для виробничих дашбордів. Вузький селектор на кшталт {service="checkout", status=~"5.."} несе намір, зменшує обсяг роботи й робить результат легшим для пояснення іншому інженерові під час огляду.
| Оператор збігу | Значення | Приклад |
|---|---|---|
= | Точний збіг | {job="api"} |
!= | Не дорівнює | {job!="test"} |
=~ | Збіг за регулярним виразом | {status=~"5.."} |
!~ | Заперечний регулярний вираз | {path!~"/health|/ready"} |
Кожен селектор повинен мати щонайменше один оператор збігу, який не збігається з порожнім рядком, бо Prometheus потребує обмеженої початкової точки для обчислення. Селектор на кшталт {job=~".*"} є некоректним, бо міг би збігтися з відсутньою міткою, тоді як {job=~".+"} означає, що мітка job мусить існувати й містити щонайменше один символ. Це невелике правило запобігає випадковим скануванням усієї бази, замаскованим під нешкідливі регулярні вирази.
# Останні 5 хвилин семплівhttp_requests_total{method="GET"}[5m]
# Остання 1 годинаhttp_requests_total[1h]
# Допустимі тривалості часу: ms, s, m, h, d, w, y# 5m = 5 хвилин, 1h30m = 90 хвилин, 1d = 1 деньВи не можете побудувати графік діапазонного вектора напряму, бо це колекція історій семплів, а не одне значення на кожен ряд. Саме тому найпоширеніша помилка початківця — це введення http_requests_total[5m] у вкладку графіка з очікуванням лінії. Діапазонний вектор — це інгредієнт; функція на кшталт rate() чи increase() — це те, що готує з нього миттєвий вектор, який графік може відобразити.
# НЕПРАВИЛЬНО: це не можна побудувати на графікуhttp_requests_total[5m]
# ПРАВИЛЬНО: rate() перетворює діапазонний вектор на миттєвийrate(http_requests_total[5m])Модифікатор offset дозволяє порівняти поточний вираз із раніше обчисленим періодом, що корисно для огляду інцидентів та сезонних патернів трафіку. Він не змінює форму виразу; він змінює час, з якого читаються семпли. Якщо дашборд порівнює цю годину з минулим тижнем, використання offset 7d часто зрозуміліше за експорт значень у таблицю та ручне вирівнювання позначок часу.
# Поточна частота запитівrate(http_requests_total[5m])
# Частота запитів 1 годину томуrate(http_requests_total[5m] offset 1h)
# Частота запитів 1 тиждень тому (для порівняння тиждень-до-тижня)rate(http_requests_total[5m] offset 7d)
# Наскільки змінилася частота порівняно з годиною тому?rate(http_requests_total[5m])-rate(http_requests_total[5m] offset 1h)Модифікатор @ закріплює обчислення за певною позначкою часу, початком відображуваного діапазону або його кінцем. Це допомагає, коли запит виглядав підозріло під час минулого інциденту і вам потрібно відтворити значення, яке побачило б правило сповіщення або дашборд. Зробіть паузу й передбачте: якщо ви поєднаєте offset 1h з @ end(), яка частина керує відображуваною позначкою часу обчислення, а яка керує семплами, що їх читає селектор?
# Значення на конкретну позначку часу Unixhttp_requests_total @ 1704067200
# Значення на початку діапазону запитуhttp_requests_total @ start()
# Значення в кінці діапазону запитуhttp_requests_total @ end()Перетворення лічильників на операційні частоти
Розділ «Перетворення лічильників на операційні частоти»Лічильники за своєю конструкцією призначені відображати накопичену роботу, а не поточну швидкість цієї роботи. Сирий лічильник запитів, що зростає з одного мільйона до двох мільйонів, сам по собі не каже вам, чи сервіс зараз спокійний, чи перевантажений, бо саме це число залежить лише від того, як довго працює процес. Тому в PromQL ви майже завжди спершу перетворюєте лічильники на частоти або на прирости, перш ніж будувати за ними графіки, налаштовувати сповіщення чи порівнювати їх з іншими сигналами.
# Середня кількість запитів за секунду протягом останніх 5 хвилинrate(http_requests_total[5m])# Якщо лічильник зріс з 1000 до 1300 за 5 хв:# rate = (1300 - 1000) / 300 секунд = 1.0 запит/с
# Частота використання CPU (секунди CPU на секунду реального часу)rate(process_cpu_seconds_total[5m])# Результат 0.25 означає 25% одного ядра CPUrate() обчислює посекундне середнє по вибраному діапазону, опрацьовує невирівняність пропущених сканувань і компенсує скидання лічильників. Це опрацювання скидань важливе у Kubernetes, бо Поди перезапускаються, контейнери переміщуються, а експортери замінюються під час звичайних операцій. Якби ви використали наївне віднімання, перезапущений Под міг би виглядати як від’ємна частота запитів; Prometheus виявляє падіння й трактує його як скидання, а не як реальне зменшення.
ОПРАЦЮВАННЯ СКИДАННЯ ЛІЧИЛЬНИКА──────────────────────────────────────────────────────────────
Норма: 100 → 200 → 300 → 400 rate = (400 - 100) / час = звичайне обчислення
Зі скиданням: 100 → 200 → 50 → 150 Prometheus виявляє 200 → 50 (зменшення = скидання) Припускає: попередній підсумок був 200, новий лічильник стартує з 0 Ефективний приріст: 200 + 150 = 350 rate = 350 / час
Саме тому rate() безпечно використовувати навіть під час перезапуску Подів!Практичний компроміс у rate() — це довжина діапазону. Короткі вікна реагують швидко, але можуть бути шумними або порожніми, коли сканування пропущено; довші вікна згладжують шум, але приховують різкі зміни. Для сповіщень та обчислень SLO стабільне значення зазвичай цінніше за драматичний сплеск, бо сповіщення має відображати сталий вплив на користувачів, а не одне незвичне сканування.
# Миттєва частота запитівirate(http_requests_total[5m])# Використовує лише останні 2 семпли в межах вікна 5m# Набагато мінливіша за rate()irate() існує для іншого завдання: показати, що відбувається на гострому краю графіка. Оскільки вона використовує лише останні два семпли всередині діапазону, вона може виявити сплеск, який rate() згладжує, але також може перебільшити аномалію одного сканування. Використовуйте irate() під час дослідження живого збою, а потім переносьте підтверджені висновки назад у rate() чи правила запису для дашбордів і сповіщень.
РІШЕННЯ RATE vs IRATE──────────────────────────────────────────────────────────────
rate(metric[5m])├── Згладжене середнє по діапазону├── Стабільні, передбачувані значення├── ВИКОРИСТОВУЙТЕ ДЛЯ: правил сповіщень, обчислень SLO, правил запису├── ВИКОРИСТОВУЙТЕ ДЛЯ: панелей дашбордів, що показують тренди└── Діапазон [5m] має значення — це вікно усереднення
irate(metric[5m])├── Миттєва частота (лише останні 2 точки)├── Мінлива, показує сплески й падіння├── ВИКОРИСТОВУЙТЕ ДЛЯ: налагодження під час інцидентів├── ВИКОРИСТОВУЙТЕ ДЛЯ: панелей «що відбувається просто зараз?»└── Діапазон [5m] лише задає перегляд назад для пошуку 2 точокincrease() відповідає на запитання про людський підрахунок, а не про швидкість. Коли власник продукту запитує, скільки запитів було оброблено сьогодні, посекундна частота незручна, але приріст за день дає сумарну кількість. Усередині increase() тісно пов’язаний з rate(), помноженим на довжину діапазону, тож ті самі застереження щодо скидання лічильників та вибору діапазону все ще застосовуються.
# Загальна кількість запитів за останню годинуincrease(http_requests_total[1h])# Якщо rate був 100 запит/с, increase ≈ 100 * 3600 = 360 000
# Загальна кількість помилок за останні 24 годиниincrease(http_errors_total[24h])
# Корисно для зрозумілих людині підрахунків:# «Ми обробили 1,2 мільйона запитів сьогодні»increase(http_requests_total[24h])Зв’язок між rate() та increase() корисний для перевірки власної роботи. Якщо increase(http_requests_total[1h]) каже, що сервіс обробив приблизно триста шістдесят тисяч запитів, то rate(http_requests_total[1h]) має бути близьким до ста запитів за секунду. Коли ці два значення дико розходяться, ви, можливо, запитуєте інший набір міток, агрегуєте інакше або читаєте лічильник, що скидається надто часто.
# Ці значення приблизно еквівалентні:increase(http_requests_total[1h]) ≈ rate(http_requests_total[1h]) * 3600Кількість скидань лічильника — теж операційний сигнал. Сервіс, що неодноразово перезапускається, може все ще мати нормальну частоту запитів, але кількість його скидань може виявити нестабільність раніше, ніж користувачі помітять повний збій. Перш ніж це запустити, який вивід ви очікуєте від стабільного Пода за останню годину і як ця очікуваність зміниться для Пода у циклі аварійних перезапусків (crash loop)?
# Скільки разів цей лічильник скидався за останню годину?resets(http_requests_total[1h])
# Висока кількість перезапусків може вказувати на crash loop (використовуйте лічильник, не gauge)increase(kube_pod_container_status_restarts_total[1h]) > 5Поширене правило для вікон частоти — щонайменше вчетверо більше за інтервал сканування. З інтервалом сканування у п’ятнадцять секунд діапазон в одну хвилину зазвичай дає достатньо точок для значущого нахилу, тоді як діапазон у тридцять секунд може звестися до двох семплів або стати порожнім після одного пропущеного сканування. Це правило не магічне, але це практична нижня межа, що утримує лінії дашборда від перетворення на артефакти вирівнювання сканувань.
ПРАВИЛО 4x──────────────────────────────────────────────────────────────
Якщо scrape_interval = 15s, мінімальний корисний діапазон для rate() = 60s (4 × 15s)
Чому? rate() потребує щонайменше 2 точки даних у діапазоні. З інтервалом 15s діапазон 30s може містити лише 1-2 точки. Діапазон 60s гарантує щонайменше 4 точки для надійного обчислення.
Правило: range >= 4 × scrape_interval
rate(metric[15s]) ← ПОГАНО: можливо лише 1 точкаrate(metric[30s]) ← РИЗИКОВАНО: можливо лише 2 точкиrate(metric[1m]) ← ОК: зазвичай 4 точкиrate(metric[5m]) ← БЕЗПЕЧНО: ~20 точок, добре згладжуванняАгрегація та форма міток
Розділ «Агрегація та форма міток»Саме на агрегації PromQL перетворюється з простого оглядача метрик на повноцінну операційну мову. Кластер Kubernetes може випромінювати одну й ту саму метрику одразу в розрізі просторів імен, Подів, контейнерів, Нод, завдань, кінцевих точок та кодів статусу; і саме агрегація вирішує, які з цих багатьох вимірів залишаються видимими, а які згортаються. Небезпека в тому, що кожна агрегація також відкидає інформацію, тож запит має зберегти мітки, потрібні для запитання, і прибрати мітки, що лише додають шум чи кардинальність.
# SUM: підсумок по всіх рядахsum(rate(http_requests_total[5m]))# Результат: одне число — загальна кількість запитів/с по всіх Подах
# AVG: середнє по всіх рядахavg(rate(http_requests_total[5m]))# Результат: середня кількість запитів/с на Под
# MIN / MAX: екстремумиmin(node_filesystem_avail_bytes)max(container_memory_usage_bytes)
# COUNT: кількість рядівcount(up == 1)# Результат: скільки цілей працює
# STDDEV / STDVAR: статистичний розкидstddev(rate(http_requests_total[5m]))# Високий stddev = нерівномірний розподіл навантаження
# TOPK / BOTTOMK: найвищі/найнижчі N рядівtopk(5, rate(http_requests_total[5m]))# Топ-5 Подів за частотою запитів
# QUANTILE: обчислити квантиль по рядахquantile(0.95, rate(http_requests_total[5m]))# 95-й перцентиль частоти запитів по всіх Подах# (НЕ histogram_quantile — це працює по рядах, а не по бакетах)
# COUNT_VALUES: підрахувати унікальні значенняcount_values("version", build_info)# Скільки екземплярів запущено на кожній версіїРізниця між sum, avg, topk та quantile не лише математична; вона змінює історію, яку ви розповідаєте. sum відповідає на сумарний попит, avg — на типову поведінку одного ряду, topk знаходить найгірших винуватців, а stddev каже вам, чи рівномірно розподілене навантаження. Під час сортування інцидентів поширено починати з суми на рівні сервісу, а потім використовувати topk чи мітки групування, щоб знайти Поди або маршрути, відповідальні за симптом на рівні сервісу.
# GROUP BY за конкретними мітками (зберегти лише ці)sum by (method)(rate(http_requests_total[5m]))# Результат: один ряд на метод (GET, POST, PUT тощо)
sum by (method, status)(rate(http_requests_total[5m]))# Результат: один ряд на комбінацію метод+статус
# EXCLUDE конкретні мітки (зберегти всі інші)sum without (instance)(rate(http_requests_total[5m]))# Результат: видаляє мітку instance, зберігає все інше
# Еквівалентні форми:sum by (method)(metric) = sum without (instance, job, ...)(metric)by та without — це взаємодоповнювальні способи описати форму міток, яку ви хочете після агрегації. sum by (service) — це білий список: зберегти лише service й відкинути кожну іншу мітку. sum without (instance, pod) — це чорний список: прибрати шумні мітки рівня екземпляра, зберігаючи будь-які інші виміри, що потужно, але може зберегти більше міток, ніж ви хотіли, коли інструментування змінюється.
# Частота запитів на сервісsum by (service)(rate(http_requests_total[5m]))
# Частота помилок на сервіс (відсоток)sum by (service)(rate(http_requests_total{status=~"5.."}[5m]))/sum by (service)(rate(http_requests_total[5m]))* 100
# Топ-10 Подів за використанням пам'ятіtopk(10, container_memory_usage_bytes{container!=""})
# Середнє використання CPU на простір іменavg by (namespace)(rate(container_cpu_usage_seconds_total[5m]))
# Загальний обсяг отриманого трафіку на Нодуsum by (node)(rate(node_network_receive_bytes_total[5m]))Запити частоти помилок показують, чому узгоджена форма міток важлива. Чисельник і знаменник обидва мають агрегуватися за service, бо Prometheus зіставляє ряди за мітками під час ділення. Якщо чисельник зберігає status, а знаменник — ні, ділення може повернути порожній результат або результат з несподіваними мітками, навіть якщо обидві половини виглядають розумно, коли їх запускати окремо.
Агрегація — це також перший захист від вибуху кардинальності міток. Панель дашборда, що групує за pod, container, path, status та user_id, не просто важка для читання; вона може створити достатньо роботи з рядами, щоб уповільнити Prometheus. У виробничих дашбордах тримайте мітки високої кардинальності для панелей деталізації, а для оглядів першої сторінки використовуйте мітки низької кардинальності, як-от service, namespace, job та status_class.
Бінарні оператори та зіставлення векторів
Розділ «Бінарні оператори та зіставлення векторів»Бінарні оператори дозволяють перетворити сирі метрики на співвідношення, відсотки, порівняння та збагачені метаданими вектори. Найважливіше правило тут полягає в тому, що Prometheus за замовчуванням має зіставити один ряд ліворуч рівно з одним сумісним рядом праворуч, якщо тільки ви явно не скажете йому, як саме опрацьовувати іншу, відмінну кардинальність. Користувачі SQL часто шукають ключове слово JOIN, але PromQL виражає з’єднання через модифікатори зіставлення, як-от on(), ignoring(), group_left() та group_right().
# Проста арифметика зі скалярамиnode_memory_MemTotal_bytes / 1024 / 1024 / 1024# Перетворити байти на ГіБ
# Арифметика між двома векторамиcontainer_memory_usage_bytes / container_spec_memory_limit_bytes * 100# Відсоток використання пам'яті# Мітки мають збігатися з обох боків!Скалярна арифметика проста, бо кожен ряд перетворюється незалежно. Векторна арифметика крихкіша, бо набори міток мають збігатися. Якщо використання пам’яті містить namespace, pod та container, але ліміт пам’яті містить ще й додаткову мітку resource, ділення може мовчки повернути менше рядів, ніж очікувалося, — саме тому зламане з’єднання часто виглядає як відсутні дані, а не як синтаксична помилка.
# Фільтр: лише ряди, де значення > порогуhttp_requests_total > 1000# Повертає лише ряди зі значенням > 1000
# Булевий режим: повертає 1 або 0 замість фільтраціїhttp_requests_total > bool 1000# Повертає 1 (true) або 0 (false) для кожного ряду
# Корисно у сповіщеннях:rate(http_requests_total{status=~"5.."}[5m])/ rate(http_requests_total[5m])> 0.05# Повертає лише ряди, де частота помилок перевищує 5%Оператори порівняння за замовчуванням є фільтрами, а не булевими виразами. Це означає, що metric > 0 прибирає ряди, чиє значення не більше за нуль, тоді як metric > bool 0 зберігає кожен зіставлений ряд і змінює значення на одиницю або нуль. Ця відмінність корисна для виразів сповіщень, бо фільтрація зберігає лише активних кандидатів на сповіщення, а булевий режим корисний, коли ви хочете побудувати графік стану умови в часі.
# AND: повертає лівий бік там, де правий бік теж має збігиup == 1 and on(job) rate(http_requests_total[5m]) > 100# Цілі, що працюють І мають високу частоту запитів
# OR: об'єднання обох боківrate(http_requests_total{status="500"}[5m]) > 10orrate(http_requests_total{status="503"}[5m]) > 10# Ряди, що відповідають будь-якій умові
# UNLESS: повертає лівий бік там, де правий бік НЕ має збігуup == 1 unless on(job) alerts{alertname="Maintenance"}# Цілі, що працюють, але НЕ на обслуговуванніЛогічні та множинні оператори працюють з ідентичністю рядів, а не з числовим поєднанням. and зберігає ряди лівого боку, що мають відповідний ряд правого боку, or утворює об’єднання, а unless прибирає ряди лівого боку, що мають збіг праворуч. Ці оператори корисні для придушень, вікон обслуговування або перевірок існування, де наявність іншого часового ряду важливіша за його числове значення.
# ЗА ЗАМОВЧУВАННЯМ: усі мітки мають збігатисяcontainer_memory_usage_bytes / container_spec_memory_limit_bytes# Працює, якщо обидва боки мають однакові набори міток
# ON: зіставляти лише за конкретними міткамиcontainer_memory_usage_bytes / on(container, namespace) container_spec_memory_limit_bytes# Зіставляти лише за container + namespace, ігнорувати інші мітки
# IGNORING: зіставляти за всім, КРІМ конкретних мітокhttp_requests_total / ignoring(status) group_left http_requests_total_sum# Ігнорувати мітку "status" під час зіставленняВикористовуйте on(), коли знаєте точні ключі, що визначають зв’язок, як-от namespace, pod та container. Використовуйте ignoring(), коли більшість міток мають збігатися й виключити треба лише невеликий набір, наприклад ігнорувати status, щоб поділити помилки за статусом на загальну кількість запитів. Який підхід ви обрали б тут і чому: дашборд, що підтримується кількома командами, де нові мітки можуть з’являтися з часом, чи жорстко контрольоване правило запису, що визначає власні вихідні мітки?
# ПРОБЛЕМА: node_info має мітки (node, os, kernel_version)# node_memory_MemTotal_bytes має мітки (node)# Багато info-рядів на ноду проти одного ряду пам'яті на ноду
# РІШЕННЯ: group_left приносить мітки з боку «один»node_memory_MemTotal_bytes* on(node) group_left(os, kernel_version)node_info# Результат має байти пам'яті з доданими мітками os та kernel_version
# Приклад з реального світу: додати мітки власника сервісу до метрикrate(http_requests_total[5m])* on(service) group_left(team, oncall)service_owner_info# Тепер ваша частота запитів має мітки team та oncall!group_left() та group_right() мають сприйматися як попереджувальні наліпки, бо вони дозволяють зіставлення «один-до-багатьох» чи «багато-до-одного». Вони потужні для збагачення метрик метаданими, але також можуть розмножити ряди, якщо зв’язок неунікальний. Перш ніж використовувати будь-який модифікатор, перевірте кардинальність правого й лівого боків за допомогою count by (...), щоб знати, чи кожен ключ з’єднання відображається саме в ту кількість рядів, яку ви очікуєте.
ВІЗУАЛІЗАЦІЯ ЗІСТАВЛЕННЯ ВЕКТОРІВ──────────────────────────────────────────────────────────────
ОДИН-ДО-ОДНОГО (за замовчуванням): Лівий: {method="GET", status="200"} → збіг → Правий: {method="GET", status="200"} Лівий: {method="POST", status="200"} → збіг → Правий: {method="POST", status="200"}
БАГАТО-ДО-ОДНОГО (group_left): Лівий: {node="a", cpu="0"} ─┐ Лівий: {node="a", cpu="1"} ─┼── on(node) group_left ──→ Правий: {node="a"} Лівий: {node="a", cpu="2"} ─┘
ОДИН-ДО-БАГАТЬОХ (group_right): Лівий: {node="a"} ──── on(node) group_right ──┬─ Правий: {node="a", disk="sda"} └─ Правий: {node="a", disk="sdb"}Коли бінарний вираз нічого не повертає, не припускайте одразу, що метрика відсутня. Запустіть лівий бік окремо, запустіть правий бік окремо й порівняйте їхні мітки. Порожні з’єднання зазвичай є проблемами форми міток, і виправлення полягає в тому, щоб зробити зв’язок зіставлення явним, а не додавати ширші селектори, що створюють більше неоднозначності.
Гістограми, перцентилі та користувацький досвід
Розділ «Гістограми, перцентилі та користувацький досвід»Гістограми — це саме той механізм, яким Prometheus зберігає цілі розподіли на кшталт тривалості запиту, розміру відповіді та часу очікування в черзі. Один-єдиний спостережений запит збільшує одразу кілька кумулятивних лічильників бакетів, а на додачу ще й ряди _sum та _count. Така конструкція робить гістограми придатними для запитів звичайними інструментами для лічильників, але також означає, що точність перцентилів залежить від меж бакетів, обраних до того, як дані було зібрано.
СТРУКТУРА ГІСТОГРАМИ──────────────────────────────────────────────────────────────
Метрика: http_request_duration_seconds
Згенеровані ряди: http_request_duration_seconds_bucket{le="0.005"} = 24054 (≤5ms) http_request_duration_seconds_bucket{le="0.01"} = 33444 (≤10ms) http_request_duration_seconds_bucket{le="0.025"} = 100392 (≤25ms) http_request_duration_seconds_bucket{le="0.05"} = 129389 (≤50ms) http_request_duration_seconds_bucket{le="0.1"} = 133988 (≤100ms) http_request_duration_seconds_bucket{le="0.25"} = 144320 (≤250ms) http_request_duration_seconds_bucket{le="0.5"} = 144700 (≤500ms) http_request_duration_seconds_bucket{le="1"} = 144838 (≤1s) http_request_duration_seconds_bucket{le="+Inf"} = 144927 (усі) http_request_duration_seconds_sum = 53423.4 (усього секунд) http_request_duration_seconds_count = 144927 (усього запитів)
Ключове розуміння: бакети КУМУЛЯТИВНІ. le="0.1" включає усе від le="0.005" до le="0.1"Оскільки бакети є кумулятивними лічильниками, ви застосовуєте rate() перед обчисленням недавнього перцентиля. Мітка le — не оздоба; це межа бакета, що каже Prometheus, якою є форма розподілу. Якщо ви агрегуєте, прибравши le, histogram_quantile() більше не знає, де краї бакетів, тож не може інтерполювати значущий перцентиль.
# P50 (медіана) затримкиhistogram_quantile(0.5, rate(http_request_duration_seconds_bucket[5m]))
# P90 затримкиhistogram_quantile(0.90, rate(http_request_duration_seconds_bucket[5m]))
# P99 затримки на сервісhistogram_quantile(0.99, sum by (le, service)(rate(http_request_duration_seconds_bucket[5m])))# ВАЖЛИВО: завжди тримайте "le" у конструкції by()!# histogram_quantile() потребує мітки le для роботи.
# P99.9 затримки (перцентиль «три дев'ятки»)histogram_quantile(0.999, sum by (le)(rate(http_request_duration_seconds_bucket[5m])))Перцентилі відповідають на інше запитання, ніж середні значення. Середня тривалість каже вам загальний час, поділений на загальну кількість запитів, що може залишатися низьким, поки менша когорта потерпає від суворої хвостової затримки. Запит P99 запитує тривалість, нижче за яку опинилися дев’яносто дев’ять відсотків спостережень, що робить його значно кращим для SLO, спрямованих на користувача, де невелика частка повільних запитів усе ще може зашкодити досвіду.
# НЕПРАВИЛЬНО: відкидає мітку le — histogram_quantile не може працюватиhistogram_quantile(0.99, sum by (service)(rate(http_request_duration_seconds_bucket[5m])))
# ПРАВИЛЬНО: зберігає мітку lehistogram_quantile(0.99, sum by (le, service)(rate(http_request_duration_seconds_bucket[5m])))Середня затримка все ще корисна, особливо для аналізу потужності та міркувань про вартість. Вона походить від суми гістограми, поділеної на її кількість, з тим самим вікном частоти на обох лічильниках. Якщо середнє значення та P99 різко розходяться, система каже вам, що загальна робота керована, але розподіл має довгий хвіст, який потребує аналізу маршрутів, залежностей чи когорт.
# Середня тривалість запиту (сума всіх тривалостей / кількість запитів)rate(http_request_duration_seconds_sum[5m])/rate(http_request_duration_seconds_count[5m])
# На сервісsum by (service)(rate(http_request_duration_seconds_sum[5m]))/sum by (service)(rate(http_request_duration_seconds_count[5m]))Apdex — це компактний спосіб перетворити розподіл затримки на оцінку у стилі задоволеності. Він рахує запити, що не перевищують ціль, як задоволені, запити, що не перевищують поріг толерування, як частково задоволені, а повільніші запити — як незадоволені. Формула має сенс лише тоді, коли ваші бакети включають цільовий та толерувальний пороги, бо інакше оцінка залежить від інтерполяції через межі, що можуть бути надто широкими.
# Apdex з ціллю = 300ms (задоволені ≤ 300ms, толерування ≤ 1.2s)( sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m])) + sum(rate(http_request_duration_seconds_bucket{le="1.2"}[5m])))/2/sum(rate(http_request_duration_seconds_count[5m]))
# Інтерпретація результату:# 1.0 = усі користувачі задоволені# 0.85+ = відмінно# 0.7-0.85 = добре# < 0.5 = поганоДизайн бакетів — це рішення щодо інструментування з наслідками для запитів. Більше бакетів довкола цілі SLO дає кращу точність перцентилів там, де вам найважливіше, тоді як надто багато бакетів через надто багато міток розмножують кількість часових рядів. Для вебAPI з ціллю у двісті мілісекунд щільні бакети довкола швидкого шляху та значень SLO цінніші за дрібну роздільність по дуже повільних викидах.
ВИБІР БАКЕТІВ ГІСТОГРАМИ──────────────────────────────────────────────────────────────
Стандартні бакети Prometheus: [.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10]
Власні бакети (для вебAPI з SLO 200ms): [.01, .025, .05, .1, .2, .3, .5, .75, 1, 2, 5] ^^^ ^^^ Дрібна роздільність Бакет НА вашій цілі SLO для швидкого шляху для точного звітування по SLO
Правила:1. Завжди майте бакет на вашій цілі SLO або поруч із нею2. Більше бакетів біля очікуваних значень = краща точність3. Ширші проміжки на екстремумах (>1s) — це нормально4. Надто багато бакетів = висока кардинальність (кожен бакет — це ряд)5. Бакет +Inf завжди створюється автоматичноНативні гістограми (native histograms) додають ще одну опцію в новіших версіях Prometheus, але класичні бакетні гістограми залишаються поширеними в екосистемах Kubernetes та екзаменаційному матеріалі. Операційний принцип залишається тим самим: знайте, чи ваш запит читає лічильники бакетів, лічильники суми й кількості, чи семпли нативної гістограми. Якщо перцентиль виглядає неможливо точним, перевірте розкладку бакетів, перш ніж довіряти результату.
Підзапити та правила запису
Розділ «Підзапити та правила запису»Підзапити (subqueries) обчислюють вираз миттєвого вектора повторно, раз за разом, протягом усього діапазону, і так утворюють діапазонний вектор, який потім можна подати у функцію по часу (over-time). Вони корисні саме тоді, коли запитання стосується поведінки вже обчисленого виразу, а не сирої метрики самої по собі. Наприклад, «яким був піковий коефіцієнт помилок за шість годин» вимагає обчислити коефіцієнт помилок багато разів, а потім узяти максимум з цих обчислених значень.
# Базовий синтаксис: <миттєвий_запит>[<діапазон>:<роздільність>]
# Середнє від максимуму за останню годину, з вибіркою кожні 5 хвилинavg_over_time(max by (instance)(rate(http_requests_total[5m]))[1h:5m])
# Стандартне відхилення коефіцієнта помилок за останні 6 годинstddev_over_time( ( sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) )[6h:1m])
# Мінімальне значення за останню годину (корисно для виявлення провалів)min_over_time(up[1h:1m])# Повертає 0, якщо ціль була недоступна в будь-який момент за останню годинуПідзапити виразні, але вони зміщують роботу на час запиту. Дашборд, що обчислює складний підзапит при кожному оновленні, може змусити Prometheus повторно сканувати й переобчислювати дані, які можна було б зберегти один раз як правило запису. Використовуйте підзапити для дослідження, разової діагностики та малонавантажених панелей; підвищуйте їх до правил запису, коли вони стають спільними операційними інтерфейсами.
| Функція | Призначення | Приклад використання |
|---|---|---|
avg_over_time() | Середнє по діапазону | Згладити мінливу метрику |
min_over_time() | Мінімум у діапазоні | Виявити будь-який простій у вікні |
max_over_time() | Максимум у діапазоні | Знайти пік використання |
sum_over_time() | Сума всіх семплів | Загальне накопичення |
count_over_time() | Кількість семплів | Виявити пропущені сканування |
quantile_over_time() | Перцентиль у часі | P95 gauge за 1 годину |
stddev_over_time() | Стандартне відхилення | Виявити незвичну варіацію |
last_over_time() | Найновіше значення | Заповнити прогалини у розріджених метриках |
present_over_time() | 1, якщо існує будь-який семпл | Перевірити існування метрики |
Функції по часу допомагають із gauge, перевірками існування та обчисленими співвідношеннями, але вони не замінюють функцій для лічильників. avg_over_time(http_requests_total[5m]) усереднює сирі значення лічильника й зазвичай каже вам дуже мало; rate(http_requests_total[5m]) каже вам швидкість запитів. Коли запит здається незграбним, спершу назвіть тип метрики, а потім оберіть функцію, що відповідає цьому типу.
ІМЕНУВАННЯ ПРАВИЛ ЗАПИСУ: level:metric:operations──────────────────────────────────────────────────────────────
level = рівень агрегації (наприклад, job, instance, cluster)metric = початкова назва метрикиoperations = перелік застосованих операцій (наприклад, rate5m)
Приклади: job:http_requests:rate5m instance:node_cpu:ratio cluster:http_errors:rate5m_ratio
ВАЖЛИВО: Використовуйте двокрапки (:) як роздільники. Сирі метрики використовують підкреслення (_). Правила запису використовують двокрапки (:). Це робить миттєво зрозумілим, які метрики обчислені.Назви правил запису мають кодувати рівень агрегації, базову ідею метрики та вже виконану операцію. Ця конвенція робить дашборди легшими для аудиту, бо метрика на кшталт job:http_requests:rate5m каже вам, що це вже п’ятихвилинна частота на рівні завдання. Вона також запобігає помилкам подвійного rate, коли хтось випадково застосовує rate() до записаної частоти, бо назва виглядала як сирий лічильник.
groups: - name: http_recording_rules interval: 30s rules: # Частота запитів на job - record: job:http_requests:rate5m expr: sum by (job)(rate(http_requests_total[5m]))
# Частота помилок на job - record: job:http_errors:rate5m expr: sum by (job)(rate(http_requests_total{status=~"5.."}[5m]))
# Коефіцієнт помилок на job (для дашбордів SLO) - record: job:http_error_ratio:rate5m expr: | job:http_errors:rate5m / job:http_requests:rate5m
# P99 затримки на job - record: job:http_latency_p99:rate5m expr: | histogram_quantile(0.99, sum by (job, le)(rate(http_request_duration_seconds_bucket[5m])) )
# Використання пам'яті на простір імен - record: namespace:container_memory_utilization:ratio expr: | sum by (namespace)(container_memory_usage_bytes{container!=""}) / sum by (namespace)(container_spec_memory_limit_bytes{container!=""} > 0)
# Використання CPU на ноду - record: node:node_cpu_utilization:ratio_rate5m expr: | 1 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))Правила також створюють контракт між командами. Команда застосунку може володіти сирим інструментуванням, платформенна команда може володіти правилами запису рівня кластера, а автори дашбордів можуть покладатися на форму записаної метрики, не копіюючи складні вирази всюди. Цей контракт працює лише тоді, коли правило переглядають як код: перевіряють мітки, кардинальність, інтервал обчислення та чи не приховує вираз режим відмови, який сповіщенню все ще потрібно бачити.
СТВОРІТЬ ПРАВИЛО ЗАПИСУ, КОЛИ:──────────────────────────────────────────────────────────────
1. Запит дашборда виконується > 1 секунди2. Той самий запит використовується в кількох дашбордах3. Запит використовується у правилах сповіщень (попереднє обчислення = швидше)4. Вам потрібно агрегувати метрики високої кардинальності5. Ви хочете узгоджені значення в різних споживачів6. Вам потрібні довші діапазони часу на витратному запиті
НЕ створюйте правило запису, коли:1. Запит простий і швидкий (наприклад, up == 0)2. Використовується лише в одному місці3. Метрика вже має низьку кардинальністьЦе рішення частково технічне, а частково соціальне. Правило запису пришвидшує повторні читання, але також створює ще одну назву метрики, яку люди використовуватимуть і неправильно розумітимуть, якщо вона погано названа. Сприймайте правила запису як публічні API для спостережуваності, а спонтанний PromQL залиште для дослідження, доки запит не доведе свою цінність.
Розбір прикладу: від симптому до запиту
Розділ «Розбір прикладу: від симптому до запиту»Сценарій вправи: платформенний черговий отримує виклик про те, що затримка оформлення замовлення порушує свою ціль, але перший дашборд показує лише середню затримку й загальну частоту запитів. Лінія середнього трохи вища за звичну, не тривожно, а частота запитів нормальна. Слабкою відповіддю було б повторне перезавантаження дашборда; сильнішою відповіддю є перетворення симптому на послідовність запитань PromQL, що зберігають виміри, найімовірніші для пояснення впливу на користувачів.
Почніть з розподілу, бо інциденти затримки рідко однорідні. Запит P99 рівня сервісу з histogram_quantile() та sum by (le, service) відповідає на те, чи має оформлення замовлення проблему хвоста взагалі. Якщо оформлення замовлення — єдиний уражений сервіс, збережіть service і додавайте по одній мітці користувацького досвіду за раз, як-от маршрут, клас статусу, регіон чи спосіб оплати. Додавання всіх міток одразу створює шумну таблицю; додавання однієї значущої мітки за раз створює діагностичний шлях.
Щойно когорта з’являється, порівняйте затримку з коефіцієнтом помилок для тієї самої форми міток. Якщо трафік оплати карткою має високу P99 затримку, а також багато відповідей рівня п’ятсот, користувацький досвід, імовірно, відмовляє, а не просто повільний. Якщо затримка висока, але помилки залишаються нормальними, наступним запитанням може бути час очікування залежності, глибина черги чи насиченість. PromQL тут корисний, бо результат кожного запиту змінює наступне запитання замість того, щоб просто додавати ще графіки.
Форма міток має залишатися узгодженою, поки ви порівнюєте сигнали. Якщо затримка згрупована за payment_method, а коефіцієнт помилок згрупований за service, ви не порівнюєте одну й ту саму популяцію. Перепишіть чисельник і знаменник коефіцієнта помилок так, щоб обидва агрегувалися за тими самими мітками, що ідентифікували когорту затримки. Це та сама навичка конструктивного вирівнювання, яку перевіряє тест: запит має зберегти достатньо міток, щоб відповісти на операційне запитання, і відкинути мітки, що спотворили б порівняння.
Тепер перевірте, чи не може видима когорта бути артефактом даних. Запустіть чисельник і знаменник запиту коефіцієнта помилок окремо, огляньте мітки й пошукайте порожні з’єднання чи відсутні ряди. Тоді перевірте resets() для відповідних лічильників, бо хвиля перезапусків Подів може створити заплутані графіки довкола вікна інциденту. Prometheus компенсує скидання лічильників у rate(), але частота скидань усе ще є свідченням про стан робочого навантаження й поведінку розгортань.
Якщо запит затримки витратний, не перетворюйте його одразу на правило запису. Спершу вирішіть, чи цей вираз — разове дослідження, чи запит, що має стати частиною звичного огляду SLO команди. Разові дослідницькі запити можуть бути витратними, бо люди запускають їх рідко. Спільні дашборди та правила сповіщень потребують передбачуваної вартості, стабільної форми міток і назв, що точно пояснюють, яке обчислення вже відбулося.
Припустімо, когорта реальна, і команда хоче постійну панель для P99 затримки оформлення замовлення за способом оплати. Правило запису має зберегти payment_method та результат перцентиля, але не повинно зберігати мітки рівня Пода чи екземпляра, що зробили б панель нестабільною після кожного розгортання. Корисна назва правила повідомляє і рівень, і операцію, а вираз документує, чому le зберігалася, доки histogram_quantile() не завершив роботу.
Далі оцініть, чи має сповіщення спрацьовувати безпосередньо на перцентилі, чи на співвідношенні у стилі бюджету помилок. Прямі перцентильні сповіщення можуть бути корисними для простих сервісів, але вони можуть викликати чергового під час коротких сплесків трафіку, що не споживають значущого бюджету. Сповіщення SLO на основі співвідношення часто дають кращий операційний сигнал, бо пов’язують погані події з дозволеним бюджетом. Урок PromQL у тому, що синтаксис — лише точка входу; якість сповіщення походить від відповідності виразу цілі сервісу.
Під час цього дослідження уникайте спокуси використати irate() лише тому, що він робить сплеск драматичнішим. Якщо запитання — «що змінилося за останні два сканування», irate() — добра лінза налагодження. Якщо запитання — «чи треба викликати людину», стабільне вікно rate() плюс тривалість for у сповіщенні зазвичай відповідальніші. Той самий сирий лічильник може підтримати обидва робочі процеси, коли кожен запит чесний щодо свого призначення.
Застарілі ряди заслуговують на фінальну перевірку, коли ціль зникла поруч з інцидентом. Prometheus позначає ряди застарілими, коли цілі перестають їх випромінювати, а діапазонні функції все ще можуть бачити недавні семпли всередині своїх вікон. Якщо дашборд начебто показує старий Под, що додає трафік після його заміни, порівняйте діапазон графіка, мітки селектора та статус цілі сканування, перш ніж сприймати значення як поточну поведінку. Це запобігає тому, щоб застаріла діагностична гілка марнувала час дослідження.
Увесь потік навмисне механічний: спершу розподіл, далі когорта, порівняння співвідношень, перевірки артефактів даних, а тоді підвищення до дашборда чи правила запису лише після того, як запит заслужив цей статус. Досвідчені оператори виглядають швидкими, бо ці кроки стають звичками, а не тому, що вони пропускають міркування. У PromQL швидкість походить від знання того, які мітки й функції можуть відповісти на наступне запитання з найменшим спотворенням.
Цей розбір прикладу також пояснює, чому практику варто проводити в живому інтерфейсі Prometheus. Статичні приклади вчать синтаксису, але живі мітки вчать смирення. Ви побачите відсутні ліміти, перейменовані завдання, порожні з’єднання та експортери, що випромінюють трохи інші мітки, ніж очікував скопійований запит. Сприймайте ці невідповідності як частину уроку, бо виробничий PromQL — це здебільшого ремесло пристосування правильних ідей до реальності міток перед вами.
Патерни й антипатерни
Розділ «Патерни й антипатерни»Сильний патерн PromQL завжди починається саме із запитання, а вже потім обирає найменший можливий набір міток, що здатен на нього відповісти. Для оцінки здоров’я сервісу агрегуйте до рівня service й, можливо, namespace, а потім деталізуйте до pod чи route лише після того, як симптом рівня сервісу вже став видимим. Це тримає оглядові дашборди читабельними й зменшує ймовірність того, що Prometheus витрачатиме більшу частину часу на обчислення панелей, які ніхто не може швидко інтерпретувати.
Інший надійний патерн — будувати співвідношення з двох виразів з однаковими вихідними мітками. Коефіцієнт помилок, коефіцієнт насиченості, коефіцієнт успішності запитів та використання пам’яті — усі стають безпечнішими, коли чисельник і знаменник використовують ту саму конструкцію sum by (...) перед діленням. Запит стає легшим для перегляду, бо кожен бік відповідає на те саме вимірне запитання, перш ніж Prometheus зіставить вектори.
Для затримки надавайте перевагу гістограмам чи нативним гістограмам перед клієнтськими квантилями summary, коли вам потрібна агрегація по Подах чи сервісах. Бакети гістограми можна підсумувати за мітками le та service перед histogram_quantile(), що дозволяє обчислити перцентилі рівня всього парку. Клієнтські квантилі вже обчислені на кожен процес, тож усереднення чи підсумовування їх по репліках дає числа, що виглядають точними, але не відображають об’єднаного розподілу.
Відповідний антипатерн — починати з панелі дашборда, скопійованої з іншого сервісу, а потім додавати мітки, доки воно начебто не запрацює. Це заохочує випадкові з’єднання, застарілі селектори та приховані витрати кардинальності. Кращий підхід — писати запит поетапно, оглядати мітки на кожному етапі й записувати, чому кожна агрегація зберігає чи відкидає мітку.
Антипатерн сповіщень — використання irate() чи дуже короткого діапазону rate(), бо це змушує демонстраційне сповіщення спрацювати швидко. Таке сповіщення тремтітиме у виробництві, коли зміститься тайминг сканування чи один семпл стрибне. Використовуйте стабільне вікно rate(), додайте розумну тривалість for у правилі сповіщення, а гостріші запити залиште для дашбордів налагодження, де присутнє людське судження.
Антипатерн правил запису — попереднє обчислення кожного виразу, перш ніж хтось ним скористався. Правила запису споживають сховище, час обчислення та увагу до іменування, тож передчасні правила стають безладом, що виглядає авторитетним. Підвищуйте запит, коли він спільний, витратний, використовується для сповіщень чи потрібен як стабільний вхід SLO; інакше тримайте його близько до дослідження, що його породило.
Каркас прийняття рішень
Розділ «Каркас прийняття рішень»Обираючи той чи інший підхід PromQL, спершу завжди визначте тип метрики, з якою працюєте. Якщо метрика — це лічильник, запитайте себе, чи вам потрібна саме швидкість, чи радше загальна кількість, а потім оберіть rate() для посекундної поведінки або increase() для загальної зміни по діапазону. Якщо метрика — gauge, уникайте функцій для лічильників і розгляньте пряму агрегацію, avg_over_time(), max_over_time() чи порівняння залежно від того, чи значення відображає поточний стан чи історію.
Далі вирішіть форму міток відповіді. Якщо аудиторія — черговий інженер, що дивиться на оглядову сторінку, збережіть мітки володіння та сервісу низької кардинальності. Якщо аудиторія активно налагоджує, включайте мітки маршруту, Пода, статусу чи залежності лише тоді, коли вони звужують запитання. Найкращий запит — не той, що зберігає найбільше вимірів; це той, що залишає саме ті виміри, які потрібні для наступного рішення.
Тоді оберіть, чи вираз має залишитися спонтанним, стати запитом дашборда чи стати правилом запису. Спонтанним запитам дозволено бути витратними й дослідницькими. Запити дашборда мають бути передбачуваними й читабельними. Правила запису мають бути названими, переглянутими й достатньо стабільними, щоб інші команди могли будувати на них, не перечитуючи щоразу повного виразу.
Нарешті, протестуйте запит проти режимів відмови. Запитайте, чи спотворить його скидання лічильника, чи зробить відсутня мітка з’єднання порожнім, чи переживе нова мітка високої кардинальності агрегацію та чи можуть застарілі ряди зробити стару ціль начебто поточною. Навичка PromQL — це звичка перевіряти ці механіки, перш ніж графік стане доказом в інциденті.
Використовуйте цей каркас як контрольний список для перегляду, перш ніж вставити запит у сповіщення, спільний дашборд чи файл правил запису. Запит, прийнятний для дослідження, може бути надто витратним, надто шумним чи надто неоднозначним для спільного операційного використання. Крок підвищення має включати просте словесне формулювання запитання, задіяний тип метрики, навмисно збережені мітки, навмисно відкинуті мітки та вже перевірені режими відмови.
Та сама звичка перегляду допомагає під час іспитів, бо багато запитань PromQL — це замасковані запитання про форму міток. Якщо вираз ділить два вектори, перевірте, чи обидва боки утворюють сумісні мітки. Якщо вираз обчислює перцентиль, перевірте, чи le дожила до функції квантиля. Якщо вираз використовує діапазон, перевірте, чи функція очікує діапазонний вектор і чи вікно містить достатньо семплів. Ці перевірки швидші за спроби запам’ятати кожен приклад напам’ять.
У виробництві запишіть фінальне обґрунтування запиту поруч із панеллю дашборда чи правилом запису, коли система це дозволяє. Майбутнім операторам треба знати, чому було обрано п’ятихвилинний діапазон, чому мітку виключили й чому перцентиль згруповано на одному рівні, а не на іншому. Добра спостережуваність — це не лише набір правильних виразів; це набір правильних виразів, чий намір залишається зрозумілим після того, як початковий автор пішов.
Чи знали ви?
Розділ «Чи знали ви?»- PromQL теж обробляє запити шляхом обчислення у стилі pull. Коли ви робите запит, Prometheus читає дані часових рядів з диска чи пам’яті й обчислює вирази; він не обчислює довільні вирази заздалегідь, якщо ви не визначите правил запису.
- Функція
rate()опрацьовує скидання лічильників автоматично. Вона виявляє зменшення значень лічильника й компенсує їх, тому перезапуски Подів не ламають автоматично коректно сформованих запитів rate. histogram_quantile()використовує інтерполяцію між межами бакетів. Якщо ваші бакети розріджені довкола цілі SLO, перцентиль може бути технічно коректним, але все ще надто неточним для рішення рівня сервісу.- PromQL не має ключового слова
JOIN. Бінарні оператори зon(),ignoring(),group_left()таgroup_right()забезпечують подібну до з’єднання поведінку через зіставлення міток часових рядів.
Типові помилки
Розділ «Типові помилки»| Помилка | Чому вона трапляється | Як її виправити |
|---|---|---|
Використання rate() на gauge | Автор запиту пам’ятає, що частоти поширені, але забуває, що gauge можуть природно зростати й спадати. | Використовуйте rate() лише на лічильниках; для gauge використовуйте прямі значення, функції по часу чи deriv(), коли справді потрібен тренд. |
Забування rate() на лічильниках | Сирі лічильники виглядають на графіку як активний трафік, бо лінія постійно зростає. | Загортайте лічильники запитів, помилок, CPU та бакетів у rate() чи increase() перед агрегацією чи порівнянням. |
| Вибір надто короткого діапазону | Діапазон містить надто мало сканувань, тож одне пропущене сканування чи один шумний семпл домінує над результатом. | Використовуйте щонайменше вчетверо більше за інтервал сканування, а тоді подовжуйте діапазон для стабільності сповіщень чи SLO. |
Відкидання le перед histogram_quantile() | Агрегацію написано як звичайну агрегацію сервісу, що знищує межі бакетів. | Завжди тримайте le у конструкції by(), доки histogram_quantile() не обчислить перцентиль. |
Сповіщення на irate() | Запит налагодження підвищують прямо у сповіщення, бо він швидко реагує у тесті. | Використовуйте rate() для сповіщень, поєднуйте його з тривалістю for, а irate() тримайте для дослідження людиною. |
| Групування за мітками високої кардинальності на оглядових дашбордах | Мітки на кшталт path, pod, container чи ідентифікаторів користувача здаються корисними під час дослідження. | Починайте з агрегації рівня сервісу чи простору імен, а тоді надавайте панелі деталізації для вимірів вищої кардинальності. |
| Ділення векторів з невідповідними мітками | Кожен бік працює окремо, тож порожній чи частковий результат ділення виглядає несподівано. | Порівняйте мітки з обох боків і навмисно використовуйте модифікатори зіставлення на кшталт on() чи ignoring(). |
| Сприйняття правил запису як нешкідливих скорочень | Скопійований запит стає постійною метрикою без перегляду іменування, міток чи вартості. | Підвищуйте лише спільні чи витратні вирази, використовуйте назви level:metric:operations і переглядайте правила як API спостережуваності. |
Тест
Розділ «Тест»Перевірте свої знання PromQL за допомогою сценарних запитань, що віддзеркалюють рішення, які ви ухвалюєте під час операцій, особливо коли мітки, лічильники, гістограми та правила запису взаємодіють під тиском.
1. Ваша команда будує графік `http_requests_total[5m]` і не отримує корисного графіка. Що слід змінити і чому?
Використайте rate(http_requests_total[5m]), якщо вам потрібні запити за секунду, або increase(http_requests_total[5m]), якщо вам потрібна загальна кількість запитів за вікно. Селектор з [5m] повертає діапазонний вектор, тобто історію семплів на кожен ряд, а не одне значення на ряд. Побудова графіка та арифметика зазвичай потребують миттєвого вектора, тож діапазонна функція має перетворити історію семплів на поточне значення. Це також перевіряє результат щодо побудови запитів PromQL з правильного типу вектора.
2. Коефіцієнт помилок рівня сервісу повертає порожній результат, хоча чисельник і знаменник кожен окремо повертають дані. Що ви перевіряєте першим?
Перевірте мітки, що їх утворює кожен бік ділення, особливо мітки, які залишаються після sum by (...) чи sum without (...). Prometheus зіставляє вектори за мітками, тож чисельник, згрупований за service,status, не поділиться чисто на знаменник, згрупований лише за service. Виправлення зазвичай полягає в тому, щоб обидва боки утворювали той самий набір вихідних міток перед діленням, або використати ignoring(status) лише тоді, коли збереження мітки status є навмисним. Не розширюйте селектори першими, бо це може приховати ваду форми міток і додати кардинальність.
sum by (service)(rate(http_requests_total{status=~"5.."}[5m]))/sum by (service)(rate(http_requests_total[5m]))* 1003. Запит P99 затримки використовує `sum by (service)(rate(http_request_duration_seconds_bucket[5m]))` усередині `histogram_quantile()`. Що тут не так?
Агрегація відкидає мітку le, що прибирає межі бакетів, потрібні histogram_quantile() для інтерполяції. Виправлений запит має зберегти і le, і бажану мітку групування, як-от sum by (le, service)(rate(http_request_duration_seconds_bucket[5m])). Без le лічильники бакетів зливаються у значення, що більше не описує розподіл. Саме тому запити гістограм треба переглядати на збереження міток, а не лише на синтаксис.
# НЕПРАВИЛЬНО — le відкинуто, бакети злито:histogram_quantile(0.99, sum by (service)(rate(metric_bucket[5m])))
# ПРАВИЛЬНО — le збережено, бакети залишаються окремими:histogram_quantile(0.99, sum by (le, service)(rate(metric_bucket[5m])))4. Під час живого інциденту `irate()` показує сплеск, що його `rate()` ледь рухає. Який запит має стати умовою сповіщення?
Умова сповіщення зазвичай має використовувати rate(), а не irate(), бо сповіщення потребують сталих і стабільних сигналів. irate() корисний для налагодження того, що змінилося між двома останніми семплами, але ця чутливість робить його схильним до тремтіння, коли зміщується тайминг сканування чи один семпл незвичний. Добрий робочий процес — використати irate(), щоб побачити гострий край, підтвердити вплив на користувача за допомогою rate() на відповідному вікні й додати тривалість for у сповіщенні. Це утримує сповіщення зосередженим на операційному впливі, а не на шумі графіка.
5. Дашборд оформлення замовлення усереднює затримку й показує, що SLO в нормі, але користувачі все ще повідомляють про тайм-аути. Який підхід PromQL спробувати далі?
Використайте перцентилі гістограми, згруповані за мітками, що відображають користувацькі когорти, як-от маршрут, сервіс, клас статусу чи спосіб оплати, зберігаючи le для обчислення перцентиля. Середнє може приховати малу, але болісну повільну когорту, бо ділить загальну тривалість на загальну кількість запитів. Запит на кшталт histogram_quantile(0.99, sum by (le, payment_method)(rate(http_request_duration_seconds_bucket{service="checkout"}[5m]))) виявляє поведінку хвоста за когортою. Якщо P99 високий для одного значення мітки, у вас є сфокусований шлях для перевірок залежностей та насиченості.
6. Дашборд повторює шестигодинний підзапит у кількох панелях, і затримка запитів Prometheus зростає. Яку зміну дизайну варто оцінити?
Оцініть перетворення повторюваного виразу на правило запису з назвою на кшталт job:http_error_ratio:rate5m чи іншою формою level:metric:operations, що відповідає запиту. Підзапити обчислюються в час запиту, тож повторні оновлення дашборда можуть змусити Prometheus переобчислювати той самий витратний вираз багато разів. Правило запису зберігає попередньо обчислений результат на інтервалах обчислення, роблячи дашборди швидшими, а значення узгодженими між споживачами. Компроміс у тому, що записана метрика стає публічним контрактом спостережуваності, тож уважно перегляньте мітки та іменування.
max_over_time( ( sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) )[6h:5m])7. Запит використання пам'яті з'єднує використання з лімітами й раптом подвоює кількість вихідних рядів. Яка ймовірна причина?
Ймовірна причина — зв’язок зіставлення «багато-до-одного» чи «один-до-багатьох», що неунікальний для обраних міток з’єднання. Модифікатор group_left() чи group_right() може законно розширити ряди, але його слід використовувати лише тоді, коли ви розумієте кардинальність з обох боків. Перевірте count by (namespace, pod, container) чи відповідні ключі з’єднання для кожної метрики, перш ніж довіряти результату. Тоді звузьте зіставлення за допомогою on(...), приберіть дубльовані ряди метаданих чи агрегуйте один бік до унікальної форми перед діленням.
sum by (node)( container_memory_usage_bytes{container!=""} * on(namespace, pod) group_left(node) kube_pod_info)8. Запит використовує `rate(metric[30s])`, тоді як Prometheus сканує кожні п'ятнадцять секунд. Чому це може ввести в оману і що використати замість цього?
Тридцятисекундний діапазон може містити лише два семпли, і одне пропущене сканування може залишити надто мало даних для надійної частоти. У такому разі rate() поводиться багато в чому як дуже гострий миттєвий нахил, що крихко для дашбордів і гірше для сповіщень. Використовуйте щонайменше одну хвилину для інтервалу сканування у п’ятнадцять секунд, а часто й п’ять хвилин для стабільних операційних оглядів. Точне вікно має відповідати тому, наскільки швидко сигнал має реагувати й скільки шуму може стерпіти споживач.
Час: 0s 15s 30s 45s 60sСкан: | | | | | [--- вікно 30s ---]
Найкращий випадок: 3 точки (0s, 15s, 30s)Найгірший випадок: 2 точки (якщо вікно не вирівняне ідеально)rate(metric[1m]) # мінімум 4 точки — прийнятноrate(metric[5m]) # ~20 точок — добре згладжування, стандартний вибірПрактична вправа: тренування з PromQL
Розділ «Практична вправа: тренування з PromQL»Практикуйте PromQL на живому екземплярі Prometheus з реальними метриками. Лабораторна робота використовує kube-prometheus-stack, тож ви можете запитувати метрики площини управління, Нод, Подів та контейнерів Kubernetes в одному місці.
Налаштування
Розділ «Налаштування»# Створіть кластер kind, якщо у вас його немаєkind create cluster --name promql-lab
# Встановіть kube-prometheus-stack (включає Prometheus, Grafana, node-exporter)helm repo add prometheus-community https://prometheus-community.github.io/helm-chartshelm repo update
helm install monitoring prometheus-community/kube-prometheus-stack \ --namespace monitoring --create-namespace \ --set prometheus.prometheusSpec.scrapeInterval=15s
# Дочекайтеся готовності всіх Подівkubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=monitoring \ -n monitoring --timeout=120sПерш ніж запускати запити, переконайтеся, що стек достатньо справний, аби продукувати значущі семпли. Інтерфейс Prometheus лише з одним скануванням не дає rate() достатньо історії, а кластер з відсутніми цілями node-exporter зробить деякі приклади порожніми. Зачекайте щонайменше кілька інтервалів сканування після того, як Поди стануть готовими, щоб діапазонні функції могли побачити реальні вікна семплів.
Крок 1: Доступ до інтерфейсу Prometheus
Розділ «Крок 1: Доступ до інтерфейсу Prometheus»# Port-forward до Prometheuskubectl port-forward -n monitoring svc/monitoring-kube-prometheus-prometheus 9090:9090
# Відкрийте http://localhost:9090 у браузеріТримайте одну вкладку на оглядачі виразів, а іншу — на перегляді графіка. Оглядач виразів допомагає інспектувати мітки й сирі значення, тоді як перегляд графіка робить поведінку вікна частоти та irate() легшою для бачення. Коли запит нічого не повертає, використовуйте оглядач виразів, щоб запустити менші частини, перш ніж припускати, що метрика відсутня.
Крок 2: Практика селекторів
Розділ «Крок 2: Практика селекторів»Введіть ці запити в оглядач виразів інтерфейсу Prometheus і робіть паузу після кожного результату, щоб оглянути повернуті назви міток, перш ніж продовжити:
# 1. Знайти всі ціліup
# 2. Фільтрувати за jobup{job="kubelet"}
# 3. Regex: знайти всі ряди kube-state-metrics, що починаються з "kube_pod"{__name__=~"kube_pod.*"}
# 4. Заперечний фільтр: усі HTTP-метрики, крім перевірок здоров'я{__name__=~"http.*", handler!~"/health|/ready"}Для кожного селектора клацніть повернутий ряд і прочитайте його мітки, перш ніж рухатися далі. Ця звичка тренує вас бачити форму міток як частину даних, а не як другорядну деталь. У критеріях успіху сприймайте «писати селектори» як таке, що включає здатність передбачити, які мітки залишаться доступними для подальшої агрегації чи зіставлення.
Крок 3: Практика функцій частоти
Розділ «Крок 3: Практика функцій частоти»# 5. Частота використання CPU на нодуrate(node_cpu_seconds_total{mode!="idle"}[5m])
# 6. Порівняйте rate vs irate (перемикайтеся на вкладку Graph, щоб побачити різницю)rate(node_cpu_seconds_total{mode="user", cpu="0"}[5m])irate(node_cpu_seconds_total{mode="user", cpu="0"}[5m])
# 7. Отримані байти мережі (приріст за 1 годину)increase(node_network_receive_bytes_total{device="eth0"}[1h])Побудуйте графіки виразів rate() та irate() на тому самому діапазоні часу, а потім змініть діапазон графіка з короткого вікна на довше. Ви маєте побачити, що irate() більш зубчастий, бо використовує лише два останні семпли, тоді як rate() згладжує по обраному діапазону. Ця візуальна різниця — причина, чому ці дві функції належать до різних операційних контекстів.
Крок 4: Практика агрегації
Розділ «Крок 4: Практика агрегації»# 8. Загальне використання CPU на ноду (агрегувати по ядрах CPU)sum by (instance)(rate(node_cpu_seconds_total{mode!="idle"}[5m]))
# 9. Топ-3 Поди за використанням пам'ятіtopk(3, container_memory_usage_bytes{container!=""})
# 10. Підрахувати запущені Поди на простір іменcount by (namespace)(kube_pod_status_phase{phase="Running"})
# 11. Середнє використання пам'яті на простір іменavg by (namespace)(container_memory_usage_bytes{container!=""})Після кожної агрегації порівняйте набір міток з неагрегованим запитом. Значення змінилося, але важливіший навчальний момент — які мітки вижили. Якщо подальшій бінарній операції чи легенді дашборда потрібна мітка, прибрана тут, запит треба переробити перед точкою агрегації.
Крок 5: Бінарні оператори та з’єднання
Розділ «Крок 5: Бінарні оператори та з’єднання»# 12. Відсоток використання пам'яті на контейнерcontainer_memory_usage_bytes{container!=""}/ on(namespace, pod, container)container_spec_memory_limit_bytes{container!=""}* 100
# 13. Використання CPU ноди (1 - коефіцієнт простою)1 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))
# 14. Відсоток використання файлової системи на ноду(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"})/node_filesystem_size_bytes{mountpoint="/"}* 100Якщо запит використання пам’яті повертає менше рядів, ніж очікувалося, запустіть обидва боки окремо й огляньте їхні мітки. Ви практикуєте діагностику, а не лише синтаксис, тож мета — пояснити, чи спричинили результат відсутні ліміти, невідповідні мітки чи нульові ліміти. Цей самий робочий процес застосовується до з’єднань володіння сервісом та збагачення метаданими Нод.
Крок 6: Запити гістограм
Розділ «Крок 6: Запити гістограм»# 15. P99 затримки запитів API-сервераhistogram_quantile(0.99, sum by (le)(rate(apiserver_request_duration_seconds_bucket[5m])))
# 16. Порівняння P50 vs P99 — додайте обидва на вкладку Graphhistogram_quantile(0.5, sum by (le)(rate(apiserver_request_duration_seconds_bucket[5m])))
# 17. Середня тривалість запиту з гістограмиrate(apiserver_request_duration_seconds_sum[5m])/rate(apiserver_request_duration_seconds_count[5m])Порівняйте P50, P99 та середню тривалість запиту на тому самому відображуваному діапазоні часу. Якщо P50 та середнє спокійні, а P99 стрибає, у площини управління проблема хвостової затримки, а не загальне сповільнення. Ця відмінність — саме те, чому механіка запитів гістограм важлива і для готовності до іспиту, і для реальних операцій Kubernetes.
Критерії успіху
Розділ «Критерії успіху»Ви завершили цю вправу, коли можете задовольнити кожен пункт контрольного списку з пам’яті й пояснити механіку обчислення PromQL, що стоїть за вашою відповіддю:
- Будувати запити PromQL з селекторами миттєвого вектора, селекторами діапазонного вектора та всіма чотирма типами зіставників.
- Застосовувати
rate(),irate()таincrease()до лічильників і пояснювати, коли кожна функція доречна. - Агрегувати за допомогою
sum by,avg byтаtopk, пояснюючи різницю міжbyтаwithout. - Обчислювати перцентилі гістограми з
histogram_quantile(), зберігаючи міткуle. - З’єднувати дві метрики за допомогою
on()таgroup_left()після перевірки кардинальності міток. - Діагностувати оманливу поведінку від скидання лічильників, застарілих чи відсутніх рядів, коротких діапазонів та порожніх зіставлень векторів.
- Будувати правило запису зі складного запиту за конвенцією іменування
level:metric:operations.
Джерела
Розділ «Джерела»- Основи запитів Prometheus
- Оператори Prometheus
- Функції Prometheus
- Правила запису Prometheus
- Найкращі практики правил запису Prometheus
- Типи метрик Prometheus
- Гістограми та summary у Prometheus
- Специфікація нативних гістограм Prometheus
- Конфігурація Prometheus
- Довідник API Prometheus Operator
- Репозиторій Helm-чартів для kube-prometheus-stack
- Шпаргалка PromQL
- Блог PromLabs
- Блог Robust Perception
Наступний модуль
Розділ «Наступний модуль»Перейдіть до Модуля 1.2: Інструментування та сповіщення, щоб дізнатися, як клієнтські бібліотеки, іменування метрик, експортери та Alertmanager перетворюють знання PromQL на виробничу спостережуваність.