From a8d3499bdc56e7fcbad06a023959a056505f44e3 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Wed, 16 Sep 2026 22:23:58 +0300 Subject: [PATCH] =?UTF-8?q?fix(metrics):=20=D0=B4=D0=B8=D1=81=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B5=20=D1=82=D1=80=D0=B5=D0=B2=D0=BE=D0=B3=D0=B8?= =?UTF-8?q?=20=D0=B2=20=D0=B0=D0=B1=D1=81=D0=BE=D0=BB=D1=8E=D1=82=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D0=93=D0=91=20+=20=D0=B0=D0=BB=D0=B5=D1=80?= =?UTF-8?q?=D1=82=20=D0=BD=D0=B0=20=D1=81=D0=BA=D0=BE=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=20=D1=83=D1=82=D0=B5=D1=87=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Инцидент 16.09: кэш архивов Forgejo ел 2 ГБ/ч, а проценты на диске 145 ГБ (host=infra) давали слишком короткое окно реакции (85% = 21 ГБ запаса, 93% = 10 ГБ). На диске host=apps (~910 ГБ) те же проценты значат совсем другой запас в ГБ — переведено на абсолютные пороги (30/15 ГБ свободного места), одинаково осмысленные независимо от размера диска. Сужено до mountpoint="/" — на /boot и /boot/efi (меньше 1 ГБ целиком) абсолютный порог горел бы постоянно. Добавлен DiskSpaceDepletingFast: deriv() по 15-минутному окну, >1 ГБ/ч устойчиво 30 минут — ловит именно быструю течь независимо от текущего уровня заполнения, пока DiskSpaceLow/Critical ещё молчат (места много, но утечка есть). DiskWillFillIn24h остаётся отдельным сигналом для более медленных, но устойчивых трендов (предупреждает позже за счёт 6h окна сглаживания шума) — роли не дублируют друг друга, разделение описано комментариями в файле. Проверено promtool check rules (24 rules found) и живыми запросами к Prometheus на Beget — ни одно из трёх правил сейчас не горит. --- ops/metrics/prometheus/rules/infra.yml | 71 +++++++++++++++++++++----- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/ops/metrics/prometheus/rules/infra.yml b/ops/metrics/prometheus/rules/infra.yml index c064cd4a..84f61a2d 100644 --- a/ops/metrics/prometheus/rules/infra.yml +++ b/ops/metrics/prometheus/rules/infra.yml @@ -63,40 +63,85 @@ groups: - name: host interval: 60s rules: - # Диск. На Beget уже был случай, когда занято 79 % и никто не смотрел; - # порог 85 % даёт запас на реакцию, а не сообщает о свершившемся факте. + # Диск. Инцидент 16.09: кэш архивов Forgejo ел 2 ГБ/ч на диске 145 ГБ + # (host=infra). Проценты на дисках разного размера значат разное — + # host=apps держит /-раздел ~910 ГБ, те же 85 % там это ещё ~135 ГБ + # запаса, а на infra (145 ГБ) — почти ничего. Порог переведён в + # АБСОЛЮТНЫЕ ГБ свободного места: он одинаково осмыслен на любом диске, + # потому что напрямую отвечает на вопрос «сколько времени есть до нуля + # при текущей скорости утечки», а не «какая доля занята». + # + # `mountpoint="/"` — намеренное сужение с прежнего «все fstype кроме + # tmpfs/overlay»: /boot и /boot/efi по обоим хостам меньше 1 ГБ + # целиком, с порогом 30/15 ГБ они бы горели ПОСТОЯННО (проверено живыми + # метриками 16.09 — infra:/boot/efi 98 МБ, apps:/boot 553 МБ). Данные + # приложения и Postgres лежат на "/", туда и целится алерт. - alert: DiskSpaceLow expr: | - (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} - / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) > 0.85 + node_filesystem_avail_bytes{fstype!~"tmpfs|overlay", mountpoint="/"} + / 1073741824 < 30 for: 15m labels: severity: warning annotations: - summary: "Диск занят больше 85 %" - description: "{{ $labels.host }} {{ $labels.mountpoint }}: занято {{ $value | humanizePercentage }}." + summary: "Свободно на диске меньше 30 ГБ" + description: "{{ $labels.host }} {{ $labels.mountpoint }}: свободно {{ printf \"%.1f\" $value }} ГБ." - alert: DiskSpaceCritical expr: | - (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} - / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) > 0.93 + node_filesystem_avail_bytes{fstype!~"tmpfs|overlay", mountpoint="/"} + / 1073741824 < 15 for: 5m labels: severity: critical annotations: - summary: "Диск почти кончился" - description: "{{ $labels.host }} {{ $labels.mountpoint }}: занято {{ $value | humanizePercentage }}. Postgres при заполнении диска останавливается." + summary: "Свободно на диске меньше 15 ГБ" + description: "{{ $labels.host }} {{ $labels.mountpoint }}: свободно {{ printf \"%.1f\" $value }} ГБ. Postgres при заполнении диска останавливается." - # Прогноз важнее порога: он ловит утечку до того, как она упрётся в стену. + # Скорость, а не уровень. Ловит именно ту утечку, что была 16.09: пока + # DiskSpaceLow/Critical ещё не сработали (места вагон), но оно тает + # быстрее ~1 ГБ/ч устойчиво — это уже течь, а не органический рост. + # `deriv()` — линейная регрессия по 15-минутному окну (не мгновенная + # разница двух точек, ту дёргает шум). Знак минус спереди и деление + # переносят результат из "Б/с, отрицательное при убыли" в "ГБ/ч, + # положительное когда тает" — чтобы $value в тексте читался нормально + # (не "-2.1 ГБ/ч"). `for: 30m` поверх 15-минутного окна регрессии + # требует НЕПРЕРЫВНОЙ убыли около часа, чтобы не будить на разовый + # скачок (бэкап, ротация логов). + - alert: DiskSpaceDepletingFast + expr: | + -deriv(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay", mountpoint="/"}[15m]) + * 3600 / 1073741824 > 1 + for: 30m + labels: + severity: warning + annotations: + summary: "Диск пустеет быстрее ГБ в час" + description: "{{ $labels.host }} {{ $labels.mountpoint }}: свободное место убывает на {{ printf \"%.1f\" $value }} ГБ/час устойчиво последние ~30 минут — независимо от того, сколько места осталось сейчас." + + # Прогноз важнее порога: он ловит утечку до того, как она упрётся в + # стену — но роль другая, чем у DiskSpaceDepletingFast выше. Тот ловит + # БЫСТРУЮ (>1 ГБ/ч) утечку рано, за счёт короткого 15-минутного окна. + # Этот ловит УМЕРЕННУЮ утечку (может быть медленнее 1 ГБ/ч), которую + # короткое окно не поймает, но которая всё равно приведёт к нулю в + # течение суток при текущем уровне занятости — окно 6h усредняет шум + # ценой более позднего срабатывания. На одной и той же быстрой утечке + # оба правила могут сработать (сначала это, следом то) — это + # ЗАДУМАННАЯ эскалация двумя разными сигналами (скорость сейчас → + # подтверждённый тренд на сутки), а не дублирующее письмо: тексты и + # время срабатывания разные. DiskSpaceLow/Critical выше добавляют + # третий, независимый от скорости сигнал — «места мало» само по себе, + # даже если утечки нет и ничего не тает быстро. - alert: DiskWillFillIn24h expr: | - predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}[6h], 24*3600) < 0 + (0 - predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay", mountpoint="/"}[6h], 24*3600)) + / 1073741824 > 0 for: 30m labels: severity: warning annotations: summary: "По текущему темпу диск кончится за сутки" - description: "{{ $labels.host }} {{ $labels.mountpoint }}: экстраполяция по последним 6 часам." + description: "{{ $labels.host }} {{ $labels.mountpoint }}: по экстраполяции последних 6 часов через сутки не хватит ~{{ printf \"%.1f\" $value }} ГБ." - alert: MemoryPressure expr: |