Наблюдаемость

Журналы, метрики, трассировки и оповещения: узнать об отказе раньше пользователей

Вячеслав Федоров

Лекция 19

Разработка и применение ПО

в физических исследованиях

p99

Содержание

От события в программе к оповещению дежурного

01

Зачем наблюдать

02

Журналы

03

Метрики

04

Сбор и хранение

05

Трассировки

06

Дашборды

07

Оповещения

Цель лекции

Делать программу наблюдаемой заранее, чтобы отказ обнаруживался и локализовался за минуты, а дежурного вызывали только по делу

Журнал, метрика и трасса отвечают на разные вопросы об одной и той же службе. Prometheus собирает метрики и вычисляет правила, а оповещение по скорости расходования бюджета ошибок срабатывает на долгий отказ средней силы и не срабатывает на короткий всплеск.

Сигналы

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

СигналВопросИнструмент курса
журналчто произошло с этим запросом или цикломlogging, journald, jq
метрикасколько, как быстро, сколько ошибокprometheus_client, Prometheus
трассана каком участке запрос провёл времяOpenTelemetry
профилькакие функции расходуют процессорpy-spy (лекция 20)
пробадоступна ли служба снаруживнешняя проверка

Пояснение

Метрики описывают программы; физические величины установки хранит архив измерений, например EPICS Archiver Appliance (часть 7).

Для физика

Сквозной пример: служба вакуумной системы и архив запусков, выдающий данные по HTTP.

Зачем наблюдать

Время восстановления, цена надёжности, симптомы и причины

01

Зачем наблюдать

Журналы

Метрики

Сбор и хранение

Трассировки

Дашборды

Оповещения

Из чего складывается

простой

сокращает наблюдаемость

Обнаружение

отказ → первый сигнал

Реакция

сигнал → дежурный за работой

Локализация

работа → найден компонент

Восстановление

причина → служба работает

01

Горячая фаза

вернуть работу: обнаружить, локализовать, исправить

02

Холодная фаза

разбор причин и меры против повторения — лекция 20

03

Восстановление

зависит от выкатки и отката, а не от наблюдения

Пояснение

MTTD, MTTA и MTTR — средние времена обнаружения, подтверждения и восстановления; буква R означает то восстановление работы, то устранение причины.

Цена

надёжности

бюджет простоя = 1 − SLO
 
99 %      14 мин 24 с в сутки
99,9 %    1 мин 26 с в сутки
99,99 %   8,6 с в сутки
99,999 %  0,86 с в сутки

Пояснение

Таблица девяток за 30 дней и за год, SLI, SLO и SLA — лекция 14 и глава «Системный дизайн».

01

Резервирование

основной способ повышения надёжности

02

Девятка

каждая уменьшает допустимый простой в 10 раз

03

Цена

растёт быстрее надёжности: девятку обосновывают выгодой

04

Не каждой программе

три девятки нужны не всем службам

Для физика

Ночной пересчёт калибровок, отложенный на час, ничего не нарушает; выдача данных смене во время сеанса требует большего.

Симптомы

и причины

СимптомПричина
что этовидит пользовательобъясняет симптом
примерыошибки, долгие ответы, устаревшие данныедиск, память, блокировка, отставшая реплика
скольконемногомного, чаще без последствий
сигналвызов дежурногопанель, заявка

Для физика

Симптом — «запуск 42 не открывается в архиве», причина — «реплика базы метаданных отстала на час».

01

Чёрный ящик

проверка снаружи, как у пользователя: отказ уже случился

02

Белый ящик

метрики изнутри: приближение отказа и место причины

03

Правило

вызов — по симптому, причины — на панели

Демонстрация:

MTTR по кварталам

01

python3 incidents.py

Что наблюдается

Средние по кварталам: 29, 27, 15 и 21 мин до улучшения, 33, 45, 51 и 25 мин после, хотя каждый инцидент стал на 20 % быстрее. В седьмом квартале медиана 11 мин, а максимум 218 мин. Из 10 000 повторений опыта в 30 % среднее не улучшилось.

root@lab — запись со стенда

Журналы

Структурированная запись, уровни и порог, контекст запроса, журнал systemd, цена хранения

02

Зачем наблюдать

Журналы

Метрики

Сбор и хранение

Трассировки

Дашборды

Оповещения

Журнал:

запись события

# строка: поля извлекают регулярным выражением
WARNING vacuum: VG04 6.2e-06 Pa above threshold
 
# запись JSON: отбор по полям
{"t":"12:00:06","lvl":"WARNING","cyc":3,
 "vg":"VG04","p":6.2e-06,"msg":"порог"}
 
# трассировка стека — в одном поле
{"lvl":"ERROR","msg":"нет ответа",
 "exc":"Traceback …\nTimeoutError: VG06"}

01

Событие

момент времени, источник, уровень, сообщение

02

Строка

удобна человеку, программе нужен разбор текста

03

JSON

одна строка на событие: jq, Loki, Elasticsearch

04

Поля

одинаковые имена во всех службах системы

Уровни

и порог

УровеньЧислоПример
DEBUG10коды АЦП, подробности отладки
INFO20изменение состояния: цикл завершён
WARNING30требует внимания: давление выше порога
ERROR40операция не выполнена: датчик не ответил
CRITICAL50служба не может продолжать работу

01

Служба

порог INFO, отладка — по требованию

02

Без перезапуска

перезапуск сбрасывает состояние неисправности

log.setLevel(logging.INFO)  # порог
log.debug("коды %s", adc)   # отброшено сразу
log.debug(f"коды {adc}")    # строка вычислена

Важно

Порог отбрасывает записи ниже себя: при INFO выводятся INFO, WARNING, ERROR и CRITICAL, а DEBUG — нет.

Пояснение

Повторяющуюся ошибку выводят раз в интервал с числом повторений, частое событие заменяют счётчиком.

Контекст

запроса

cycle = contextvars.ContextVar("cycle", default=0)
 
class JsonFormatter(logging.Formatter):
    def format(self, record):
        doc = {"t": ..., "lvl": record.levelname,
               "cyc": cycle.get()}    # контекст
        doc.update(getattr(record, "fields", {}))
        doc["msg"] = record.getMessage()
        return json.dumps(doc, ensure_ascii=False)
 
cycle.set(3)              # для всех записей цикла
log.warning("порог", extra={"fields": {"vg": "VG04"}})

01

Идентификатор

номер цикла, запроса или trace_id в каждой записи

02

contextvars

значение видно всему коду текущего контекста

03

asyncio

задача получает копию контекста при создании

04

threading

новый поток — с пустым контекстом: copy_context().run

Пояснение

Аналитические события — кто и какой запуск открыл — пишут отдельным потоком или полем, а не уровнем.

Демонстрация:

журнал JSON

01

python3 jsonlog.py

02

python3 jsonlog.py | jq -c 'select(.lvl!="INFO")|{t,cyc,vg,msg}'

03

python3 jsonlog.py | jq -r .vg | sort | uniq -c

Что наблюдается

Семь записей трёх циклов, время модельное с 12:00:00. Отбор оставляет две: VG06 «нет ответа» в цикле 2 и VG04 «порог» в цикле 3. По датчикам: VG01 3, VG04 3, VG06 1.

root@lab — запись со стенда

Демонстрация:

порог на ходу

01

cat toggle.sh

02

bash toggle.sh

Что наблюдается

Циклы 1–4 — только INFO. После первого SIGUSR1 запись на 1011 мс сообщает о пороге DEBUG, и в циклах 5–6 появляются коды АЦП; второй сигнал на 1517 мс возвращает INFO. Процесс работает без перезапуска.

root@lab — запись со стенда

stdout

и журнал systemd

SYSLOG = {DEBUG: 7, INFO: 6, WARNING: 4, ERROR: 3,
          CRITICAL: 2}
 
class PrefixFormatter(logging.Formatter):
    def format(self, record):
        prefix = f"<{SYSLOG[record.levelno]}>"
        return prefix + super().format(record)
 
# stdout службы:
<4>vacuum: цикл 4: VG04 давление 7.5e-06 Па выше порога
# journald: PRIORITY=4, префикс снят

01

Поток событий

stdout, по The Twelve-Factor App

02

Среда

journald, драйвер журналов Docker, агент Kubernetes

03

Префикс <N>

уровень syslog: SyslogLevelPrefix, по умолчанию включён

04

Поля

_PID, _SYSTEMD_UNIT, PRIORITY: отбор по полям

Пояснение

Объём и срок хранения задаёт среда: SystemMaxUse в journald.conf, logrotate для файлов.

Демонстрация:

журнал systemd

01

systemd-run -q -u l19-vacuum --collect python3 $PWD/journal_svc.py

02

sleep 4; journalctl -u l19-vacuum -o cat | tail -6

03

journalctl -u l19-vacuum -p warning -S -1min -o cat

04

journalctl -u l19-vacuum -p 4 -S -1min -o json | jq -r .PRIORITY

Что наблюдается

Служба отработала шесть циклов и завершилась. Отбор по уровню warning оставляет две записи: VG06 не отвечает, VG04 выше порога. Поле PRIORITY — 3 и 4, префиксов в тексте нет.

root@lab — запись со стенда

Цена

журналов

Политика записиЗаписей в суткиВ суткиЗа годГод, gzip
каждый отсчёт: 200 каналов × 10 Гц1,73·10⁸14,6 ГБ5,3 ТБ438,9 ГБ
цикл опроса раз в секунду8,64·10⁴7,3 МБ2,7 ГБ219,4 МБ
только изменения состоянияоколо 10³84,7 кБ30,9 МБ2,5 МБ

01

Оценка

записей в сутки × размер записи × срок хранения

02

Запись

85 байт JSON, gzip сжимает в 12,2 раза

03

Срок

по требованию: PCI DSS 10.5.1 — 12 месяцев, три из них с немедленным доступом

Для физика

Отсчёты каналов являются данными и хранятся в архиве измерений; журнал предназначен для событий.

Практики

журналирования

01

Одна структура

одинаковые поля во всех службах

02

Идентификатор

запроса, цикла или trace_id в каждой записи

03

Порог без перезапуска

сигнал или внешний ключ

04

stdout или journald

сбор и ротация — дело среды

05

Срок хранения

по требованию и объёму, старое — сжатым

06

Наблюдение за доставкой

число принятых записей в минуту

Метрики

Модель данных, типы метрик, экспортёр, метки и кардинальность, гистограммы и квантили

03

Зачем наблюдать

Журналы

Метрики

Сбор и хранение

Трассировки

Дашборды

Оповещения

Модель данных

Prometheus

# HELP vacuum_up Опрос контроллера удался (1) или нет (0)
# TYPE vacuum_up gauge
vacuum_up 1.0
# HELP vacuum_pressure_pascals Давление
# TYPE vacuum_pressure_pascals gauge
vacuum_pressure_pascals{gauge="VG01",sector="linac"} 3.133e-07
vacuum_pressure_pascals{gauge="VG02",sector="ring"} 2.095e-07
# TYPE vacuum_gauge_errors_total counter
vacuum_gauge_errors_total{gauge="VG01"} 0.0

Пояснение

Ряды VG01 и VG02 относятся к одной метрике, но хранятся отдельно.

01

Временной ряд

имя метрики и набор меток

02

Отсчёт

время с точностью до мс и число float64

03

Опрос

Prometheus сам читает /metrics: pull

04

Единицы

базовые по руководству: _seconds, _bytes, _pascals

05

Счётчик

имя оканчивается на _total

Типы

метрик

ТипПоведениеПримерОбработка
counterтолько растёт; при перезапуске — с нулязапросы, ошибки, срабатывания триггераrate, increase
gaugeрастёт и убываетдавление, длина очереди, памятьзначение, max_over_time
histogramсчётчики корзин _bucket{le}, _sum, _countдлительность запросаhistogram_quantile
summaryквантили вычисляет сама службадлительность в одном экземплярене агрегируется

Пояснение

Счётчик не теряет событий между опросами: разность двух его значений учитывает всё, что произошло между ними.

Важно

Счётчик для убывающей величины — числа потоков или соединений — нарушает rate: каждое уменьшение выглядит как перезапуск.

Демонстрация:

счётчик и сброс

01

python3 counter.py

Что наблюдается

Опрос раз в 15 с: до перезапуска 5550, после — 750. Наивная разность 750, с учётом сброса 6300, на самом деле 6300. Значение «за секунду» во всех опросах 50, вспышка 400 не видна.

root@lab — запись со стенда

Экспортёр:

опрос при обращении

class VacuumCollector:
    def collect(self):          # при каждом GET /metrics
        t0 = time.perf_counter()
        up = GaugeMetricFamily("vacuum_up", "Опрос удался")
        p = GaugeMetricFamily("vacuum_pressure_pascals",
            "Давление", labels=["sector", "gauge"])
        try:
            for gauge, sector in GAUGES.items():
                try:
                    v = self.ctrl.read(gauge)
                    p.add_metric([sector, gauge], v)
                except TimeoutError:    # ряда нет
                    self.errors[gauge] += 1
            up.add_metric([], 1)
        except OSError:         # контроллер недоступен
            up.add_metric([], 0)
        yield from (up, p, err, dur)

01

Синхронный опрос

источник читается в момент запроса, своих таймеров нет

02

vacuum_up

0 — экспортёр работает, а источник недоступен

03

Нет ряда

вместо нуля или прежнего значения

04

Тайм-аут

меньше тайм-аута опроса Prometheus, 10 с

05

Момент события

время старта, а не время работы

Демонстрация:

экспортёр вакуума

01

python3 exporter.py & sleep 1

02

curl -s 127.0.0.1:9101/metrics | grep -v '^#'

03

curl -s 127.0.0.1:9101/metrics | grep -v '^#'

04

kill %1

Что наблюдается

Первый опрос: vacuum_up 1.0, три ряда давления от 1,8 до 3,1·10^−7 Па, ошибок нет, опрос занял 36 мс. Во втором опросе ряда VG03 нет, а счётчик ошибок VG03 равен 1.0.

root@lab — запись со стенда

Метки

и кардинальность

REQS = Counter("archive_requests", "Запросы к архиву",
               ["method", "route", "code"])
 
REQS.labels("GET", "/runs/{id}", "200").inc()
# шаблон маршрута: один ряд на все запуски
 
REQS.labels("GET", "/runs/4217", "200").inc()
# номер запуска в метке: новый ряд на каждый

Пояснение

Номер запуска — поле журнала или атрибут трассы, а не метка метрики.

01

Ряд

каждая комбинация значений меток

02

Цена

память, диск и время каждого запроса к данным

03

Ограниченные

метод, шаблон маршрута, код, сектор

04

Неограниченные

путь с номером, адрес клиента, пользователь

Важно

Документация Prometheus: кардинальность большинства метрик — ниже 10; метрику свыше 100 рядов перепроектируют.

Демонстрация:

число рядов

01

python3 cardinality.py

Что наблюдается

20 000 запросов. Метод, шаблон маршрута и код — 9 рядов и ответ /metrics 2 КиБ; полный путь вместо шаблона — 11 945 рядов и 2051 КиБ; те же три метки и адрес клиента — 1082 ряда и 229 КиБ.

root@lab — запись со стенда

Гистограмма

и квантиль

def hq(q, bounds, counts):      # histogram_quantile
    rank = q * counts[-1]       # counts накопительные
    i = bisect.bisect_left(counts, rank)
    if i == len(bounds):        # ранг в корзине +Inf
        return bounds[-1]
    lo, below = ((0.0, 0) if i == 0
                 else (bounds[i - 1], counts[i - 1]))
    return lo + (bounds[i] - lo) * (rank - below) \
               / (counts[i] - below)
 
histogram_quantile(0.99, sum by (le) (
    rate(archive_request_duration_seconds_bucket[5m])))

01

Корзины

накопительные: le="0.3" — все не дольше 0,3 с

02

Интерполяция

линейная: ошибка — до ширины корзины

03

+Inf

ранг за последней границей — её значение

04

Граница на SLO

доля «быстрее 300 мс» без интерполяции

05

Экземпляры

гистограммы складываются, summary — нет

Демонстрация:

p99 по корзинам

01

python3 quantile.py

Что наблюдается

p99 точно 354 мс; по корзинам по умолчанию — 447 мс, с границей 300 мс — 356 мс. Доля быстрее 300 мс: 96,92 % точно и с границей, 96,23 % по умолчанию. Два экземпляра: среднее p99 229 мс, сумма гистограмм 342 мс, точно 340 мс.

root@lab — запись со стенда

Результат:

корзины и распределение

Гистограмма 20 000 задержек выдачи запуска в логарифмических осях, границы корзин по умолчанию и корзин с границей 300 мс, точный p99 354 мс и оценки 447 и 356 мс

Что наблюдается

Между 250 и 500 мс у корзин по умолчанию нет границ: интерполяция сдвигает p99 с 354 до 447 мс. Границы 300, 350 и 400 мс делят хвост, и оценка 356 мс отличается от точной на 2 мс.

Сбор и хранение

Опрос и отправка, Prometheus на стенде, язык запросов PromQL, метрики заданий

04

Зачем наблюдать

Журналы

Метрики

Сбор и хранение

Трассировки

Дашборды

Оповещения

Опрос

и отправка

Опрос, pullОтправка, push
инициаторхранилище по списку целейслужба или агент
живостьряд up после каждого опросатишину не отличить от отсутствия событий
частотазадаёт хранилищезадают источники
примерыPrometheusTelegraf и InfluxDB, OTLP, Pushgateway

Пояснение

Pushgateway — для итогов пакетных заданий уровня службы: общая точка отказа, нет ряда up, ряды хранятся до удаления.

01

Прямая запись

проще; при отказе хранилища данные теряются или копятся в памяти агента

02

Шина

Kafka: буфер и несколько читателей, но ещё одна точка отказа и задержка

03

Обогащение

метки машины и площадки, отбор лишнего на сборе

Prometheus

на стенде

global:
  scrape_interval: 5s
  evaluation_interval: 5s
rule_files:
  - rules.yml
scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ["127.0.0.1:9090"]
  - job_name: node
    static_configs:
      - targets: ["127.0.0.1:9100"]
  - job_name: vacuum
    static_configs:
      - targets: ["127.0.0.1:9101"]
  - job_name: archive
    static_configs:
      - targets: ["127.0.0.1:8403"]

01

Цели

prometheus — 9090, node — node_exporter, vacuum — exporter.py, archive — red.py serve

02

Хранение

каталог data: блоки по 2 ч, 15 суток по умолчанию, 1–2 байта на отсчёт

03

Проверка

promtool check config — до запуска, вместе с правилами

04

Правила

rules.yml: доли ошибок и оповещения — часть 7

Для физика

10 000 рядов при опросе раз в 15 с — 5,76·10⁷ отсчётов и около 86 МБ в сутки.

Демонстрация:

Prometheus и цели

01

promtool check config prometheus.yml

02

./prom.sh

03

curl -s 127.0.0.1:9090/api/v1/targets | jq -r -f targets.jq

04

promtool query instant http://127.0.0.1:9090 up

05

./q.sh rate

06

./q.sh p99

07

./q.sh vac

Что наблюдается

Четыре цели up; через минуту после старта — 39,2 и 0,8 запроса/с, p99 0,46 с, два ряда давления.

root@lab — запись со стенда

PromQL:

rate, sum by, квантиль

up == 0                       # цель не ответила
sum by (code) (rate(archive_requests_total[5m]))
sum(rate(archive_requests_total{code=~"5.."}[5m]))
  / sum(rate(archive_requests_total[5m]))
histogram_quantile(0.99, sum by (le)
  (rate(archive_request_duration_seconds_bucket[5m])))
max_over_time(vacuum_pressure_pascals[10m])
predict_linear(node_filesystem_avail_bytes[6h], 4 * 3600) < 0
time() - calib_last_success_timestamp_seconds

01

rate

скорость в секунду за окно, сброс учтён

02

Окно

несколько интервалов опроса; rate делит на длину окна

03

sum by

свёртка по экземплярам: доля ошибок службы

04

max_over_time

пики при крупном шаге графика

05

predict_linear

линейная регрессия: заполнение диска через 4 ч

Метрики

заданий

reg = CollectorRegistry()
last = Gauge("calib_last_success_timestamp_seconds",
             "Последняя успешная калибровка", registry=reg)
chans = Gauge("calib_channels",
              "Число откалиброванных каналов", registry=reg)
 
chans.set(calibrate())         # минуты работы
last.set_to_current_time()
write_to_textfile("tf/calib.prom", reg)
# внутри: временный файл и os.rename

01

Задание

итог исчезает с процессом раньше опроса

02

textfile collector

файл *.prom в каталоге node_exporter

03

Атомарность

временный файл и переименование

04

Оповещение

нет успеха дольше 26 ч

Пояснение

Время последнего успеха выявляет ошибку, незапуск и зависание; выключенную машину — оповещение по up node_exporter.

Демонстрация:

метрики задания

01

python3 calibjob.py tf

02

curl -s 127.0.0.1:9100/metrics | grep '^calib_'

03

./q.sh calib

Что наблюдается

Файл tf/calib.prom: три метрики, формат подтверждён разборщиком. node_exporter отдаёт те же ряды. С последнего успеха — 28,3 ч: больше 26 ч, оповещение CalibrationStale сработает.

root@lab — запись со стенда

Трассировки

Трасса и спан, заголовок traceparent, OpenTelemetry, сэмплирование и цена трассировки

05

Зачем наблюдать

Журналы

Метрики

Сбор и хранение

Трассировки

Дашборды

Оповещения

Трасса

и спан

traceparent:
00-bdd640fb06671ad11c80317fa3b1799d-1a3d1fa7bc8960a9-01
 
00                                 версия
bdd640fb06671ad11c80317fa3b1799d   trace_id, 32 hex
1a3d1fa7bc8960a9                   span_id, 16 hex
01                                 флаги: sampled

Пояснение

Идентификатор запроса связывает записи журналов, трасса — ещё и время участков; trace_id в журнале объединяет оба.

01

Трасса

дерево спанов одного запроса с общим trace_id

02

Спан

имя, начало и конец, атрибуты, события, статус

03

Родитель

спан той же трассы; у корня его нет

04

Span link

связь между трассами: отправка в очередь и обработка

05

traceparent

W3C Trace Context: контекст в заголовке HTTP

Open

Telemetry

provider = TracerProvider(resource=Resource.create(
    {"service.name": "gateway"}))
provider.add_span_processor(BatchSpanProcessor(exp))
tracer = provider.get_tracer("gateway")
 
with tracer.start_as_current_span("GET archive",
                                  kind=SpanKind.CLIENT):
    inject(headers)       # traceparent исходящего запроса
 
# архив: родитель — клиентский спан шлюза
with tracer.start_as_current_span("GET /runs/{id}",
        context=extract(self.headers)):
    ...

Пояснение

opentelemetry-instrument и eBPF-инструментация OBI работают без изменения кода, но контекст переносит код или библиотека.

01

API и SDK

библиотеки зависят от API, программа настраивает SDK

02

Пропагатор

по умолчанию — заголовок W3C traceparent

03

OTLP и Collector

передача, обработка и пересылка сигналов

04

Не хранилище

трассы хранят Jaeger, Tempo, Zipkin

05

SDK для Python

трассы и метрики стабильны, журналы — в разработке

Демонстрация:

трасса шлюз → архив

01

python3 tracing.py

02

python3 tracing.py sampling

Что наблюдается

Записи шлюза и архива — с одним trace=bdd640fb; traceparent несёт trace_id и span_id клиентского спана шлюза. Водопад: запрос 55 мс, из них чтение HDF5 — 35 мс. Доли 1; 0,1 и 0,01 сохраняют 10 000, 1000 и 98 трасс из 10 000.

root@lab — запись со стенда

Результат:

водопад трассы

Водопад трассы запроса «выдать запуск»: шесть спанов шлюза и архива на оси времени с длительностями

Что наблюдается

Запрос — 52,8 мс; проверка прав — 5,1 мс, вызов архива — 47,4 мс. В архиве 11,2 мс занимает запрос к базе и 34,8 мс, 66 % всего запроса, — чтение HDF5. Сеть между службами — меньше миллисекунды.

Цена

трассировки

В голове, headВ хвосте, tail
решениепри создании корневого спанав Collector по всей трассе
ценапочти нулеваябуфер всех спанов на время решения
ошибки, медленныес той же вероятностьюсохраняются все
согласованностьTraceIdRatioBased по trace_idодно решение на трассу

Важно

Наблюдение не должно останавливать службу: ограниченный буфер, отбрасывание вместо блокировки, тайм-аут отправки.

01

Очередь

BatchSpanProcessor: 2048 спанов, OTEL_BSP_MAX_QUEUE_SIZE

02

Переполнение

старые спаны вытесняются, служба работает

03

Профили

четвёртый сигнал: Pyroscope; py-spy — лекция 20

Дашборды и методы

RED, USE и золотые сигналы, правила построения панелей, ловушки восприятия

06

Зачем наблюдать

Журналы

Метрики

Сбор и хранение

Трассировки

Дашборды

Оповещения

RED, USE

и золотые сигналы

МетодОбъектСигналы
RED, Т. Уилки, 2015службы с запросамиrate, errors, duration
USE, Б. Грегг, 2012процессор, память, диск, сетьutilization, saturation, errors
золотые сигналы SREлюбая службазадержка, трафик, ошибки, насыщение

Для физика

Служба архива — RED; процессор, память и диск машины, на которой она работает, — USE.

01

Utilization

доля времени, когда ресурс занят

02

Saturation

очередь работы: PSI в /proc/pressure, procs_running

03

Errors

ошибки ресурса: диск, сеть, OOM

04

Порядок

три графика RED на службу, три вопроса USE на ресурс

Демонстрация:

RED по метрикам

01

python3 red.py

Что наблюдается

За 5,0 с — 200 запросов. R: 40,0 запроса/с. E: 4 ответа 500, 2,0 %. D: p50 18 мс, p95 25 мс, p99 460 мс. Холодное чтение длится 310 мс: p99 завышен корзиной от 0,3 до 0,5 с, а доля быстрее 0,3 с, 95,0 %, точна.

root@lab — запись со стенда

Правила

дашборда

01

Сверху вниз

от функции для пользователя к компонентам

02

Слева направо

по пути запроса: шлюз, служба, база

03

Заголовок

вопрос, единицы и окно усреднения

04

Шкала

логарифмическая для величин на порядки

05

Топ-10

topk вместо сотни рядов

06

Цвет

красный и зелёный — только для состояния

07

No data

исправить или удалить

натекание 5.0e-05 Па на 3 мин,
порог защиты 1.0e-05 Па
 
шаг     среднее   максимум  выше порога
1 мин   5.0e-05   5.0e-05   да
5 мин   3.0e-05   5.0e-05   да
1 ч     2.7e-06   5.0e-05   нет
1 сут   3.0e-07   5.0e-05   нет

Важно

Среднее за час опускает трёхминутное натекание ниже порога; max_over_time сохраняет пик.

Что не так

с панелями

Четыре панели с ошибками оформления: A — задержка p99 без данных, B — доступность залита красным, C — двенадцать линий давления без легенды и единиц, D — доля ошибок, усреднённая с начала месяца

Пояснение

Каждая из четырёх панелей модели нарушает одно из правил предыдущего слайда.

Ловушки

восприятия

01

Привязка

«в прошлый раз было то же самое»: поиск знакомой причины вместо новой

02

Доступность

яркий недавний отказ кажется частым

03

Ложная корреляция

всплески на соседних панелях: совпадение или общая причина

Пояснение

Книга Google SRE, глава «Effective Troubleshooting», называет первую и третью из этих ловушек; приёмы их обхода — лекция 20.

Результат:

панели RED со стенда

Три панели RED службы архива за 10 минут по данным Prometheus стенда: частота по кодам ответа, доля ошибок, p50 и p99 по гистограмме

Что наблюдается

В эпизоде замедления базы ошибки растут с 2 до 5 %, p50 — с 18 до 153 мс, а p99 остаётся 460 мс: хвост задают холодные чтения 310 мс, и замедления p99 не показывает.

Оповещения

SLO на практике, скорость расходования бюджета, правила Prometheus и их тесты, дежурство

07

Зачем наблюдать

Журналы

Метрики

Сбор и хранение

Трассировки

Дашборды

Оповещения

SLI, SLO и SLA

на практике

01

SLI

доля хороших событий среди всех

02

SLO

цель на скользящем окне, обычно четыре недели

03

SLA

договор, мягче SLO на доли процента

04

Бюджет

1 − SLO: при 99,9 % — 0,1 % запросов

sum(rate(archive_requests_total{code!~"5.."}[28d]))
  / sum(rate(archive_requests_total[28d]))

Для физика

Доля выдач запуска быстрее 300 мс; доля минут, когда последний отсчёт в архиве не старше 10 с, — свежесть данных.

Пояснение

Определения, девятки и бюджет ошибок — лекция 14 и глава «Системный дизайн».

Скорость расходования

бюджета

скорость = доля ошибок / (1 − SLO)
1 — бюджет закончится к концу окна SLO
2 % ошибок при SLO 99,9 % — скорость 20
 
вызов:   > 14,4 в окнах 1 ч и 5 мин
вызов:   > 6    в окнах 6 ч и 30 мин
заявка:  > 1    в окнах 3 сут и 6 ч
 
обнаружение: t > k · (1 − SLO) · W / e
e = 2 %, k = 14,4, W = 60 мин: t > 43,2 мин

01

14,4 за час

2 % месячного бюджета: вызов дежурного

02

Короткое окно

1/12 длинного: оповещение снимается вскоре после конца

03

Ниже 1,44 %

правило 1 ч не сработает: остаётся правило 6 ч

Пояснение

Пороги — SRE Workbook, таблица 5-8; их происхождение — глава «Системный дизайн».

Демонстрация:

три инцидента

01

python3 burnrate.py

Что наблюдается

Час с 2 % ошибок: вызов в 02:43, снятие в 03:01. Семь часов с 0,8 %: вызов 6 ч/30 мин в 14:30, заявка в 16:30, снятие вызова в 17:07. Пять минут с 10 % — новых оповещений нет. Расход бюджета: 2,8, 7,8 и 1,2 %.

root@lab — запись со стенда

Результат:

расход бюджета за сутки

Скорость расходования бюджета ошибок в окнах 5 мин, 1 ч и 6 ч за сутки с тремя инцидентами, пороги 14,4 и 6, интервалы вызова дежурного

Что наблюдается

Окно 1 ч пересекает порог 14,4 только в первом инциденте, окно 6 ч — порог 6 при медленном горении; всплеск в 20:00 даёт 100 в окне 5 мин, но в окне 1 ч лишь 8,3.

Правило

и его тест

- record: archive:error_ratio:rate1h
  expr: sum(rate(archive_requests_total{code=~"5.."}[1h]))
        / sum(rate(archive_requests_total[1h]))
- alert: ArchiveErrorBudgetBurnFast
  expr: archive:error_ratio:rate1h > 14.4 * 0.001
        and archive:error_ratio:rate5m > 14.4 * 0.001
  labels:
    severity: page
  annotations:
    summary: "Архив расходует бюджет ошибок быстрее 14.4 нормы"
    runbook_url: "https://wiki.example.org/runbooks/archive-errors"

01

Правило записи

ряд archive:error_ratio:rate1h — доля ошибок за час

02

Оповещение

expr, for, метка severity, summary и runbook_url

03

promtool test rules

синтетические ряды и ожидаемые оповещения

04

Ряды теста

0+600x180 — успешные; 0x60 12+12x119 — ошибки с 61-й минуты

05

Ожидание

95 мин: 1,15 % — без оповещения; 115 мин: 1,8 % — оповещение

Демонстрация:

тесты правил

01

promtool check rules rules.yml

02

promtool test rules rules_test.yml

03

curl -s 127.0.0.1:9090/api/v1/rules | jq -r -f alerts.jq

Что наблюдается

10 правил корректны, тесты — SUCCESS. На стенде сработали три оповещения: оба правила бюджета архива — модель отвечает ошибкой на 2 % запросов, в 20 раз больше бюджета 0,1 %, — и CalibrationStale: калибровке больше 28 ч.

root@lab — запись со стенда

Оповещение

и дежурство

01

Действие

каждое оповещение требует действия, иначе — панель или заявка

02

Текст

что, где, порог, значение и ссылка на инструкцию

03

Дежурство

основной и запасной, эскалация по таймеру

04

Рутина

автоматизировать: ротация журналов вместо уборки по оповещению

05

Учения

выключить экспортёр и убедиться, что сообщение дошло

Пояснение

Alertmanager группирует оповещения, подавляет зависимые, отключает их на время работ и направляет по меткам: page — на телефон, ticket — в заявки.

Важно

Нагрузка дежурного — не больше двух инцидентов за двенадцатичасовую смену; разбор инцидента — лекция 20.

Наблюдаемость

установки

Архив величинМониторинг программ
хранитзначения каналовсостояние служб
точностьисходнаядопустимо прореживание
срокгодынедели, месяцы
тревогиоператору сменыдежурному разработчику
примерEPICS Archiver AppliancePrometheus

Для физика

Главный индикатор системы сбора данных — свежесть: доля минут, когда последний отсчёт в архиве не старше 10 с.

01

Связь

vacuum_up: ответил ли контроллер

02

Цикл опроса

длительность и пропущенные циклы

03

Каналы

ошибки чтения, доля каналов без связи

04

Архив

отставание записи, заполнение дисков, predict_linear

Итоги

лекции

01

Наблюдаемость сокращает обнаружение и локализацию отказа.

02

Журнал — строка JSON на событие с идентификатором, в stdout.

03

Событие — счётчиком, состояние — gauge, задержка — гистограммой.

04

Экспортёр читает источник при каждом запросе /metrics.

05

Границы корзин — на целях, метки — с ограниченными значениями.

06

Трасса показывает, на каком участке запрос провёл время.

07

Оповещение — по симптому и скорости расходования бюджета, с тестом.

Упражнения

и задание

01

Журнал JSON

своей программы с идентификатором цикла и отбор записей jq

02

Экспортёр

для прибора или расчёта: collect(), _up, базовые единицы

03

Гистограмма

с границей на своей цели и сравнение p99 с точным

04

Правило и тест

скорость расходования бюджета и promtool test rules

05

Задание

Prometheus, node_exporter, панели USE и RED, оповещение при выключенном экспортёре

Для физика

Для установки — экспортёр контроллера или источника питания с рядом _up и временем последнего успешного опроса.

Пояснение

Конфигурация Prometheus стенда, фрагменты правил и их теста приведены в главе «Наблюдаемость».

Источники

1

Книга курса: главы «Наблюдаемость», «Системный дизайн», «От скрипта к приложению», «Работа с базами данных»

phys-dev.github.io/soft-dev-book

2

Лекции «Наблюдаемость» и «Прикладное программирование для SRE», КИТ 2024, Young&&Yandex

youtube.com/watch?v=_GpPUf9Yeg0

3

Beyer B. et al. Site Reliability Engineering. – O'Reilly, 2016; The Site Reliability Workbook. – 2018

sre.google/books

4

Документация Prometheus: типы метрик, именование, гистограммы, экспортёры, Pushgateway, тесты правил

prometheus.io/docs

5

Документация OpenTelemetry для Python и о сэмплировании; W3C Trace Context

opentelemetry.io/docs/languages/python

6

Gregg B. The USE Method; Wilkie T. The RED Method; Davidovič Š. Incident Metrics in SRE. – O'Reilly, 2021

brendangregg.com/usemethod.html

Спасибо за внимание

Вопросы

phys-dev.github.io/soft-dev-book

19