Пара 21 / декабрь
Grafana, SLI, SLO и осмысленные алерты Создать небольшой dashboard и объяснить порог оповещения в терминах пользователя.
Создать небольшой dashboard и объяснить порог оповещения в терминах пользователя. Предоставленный dashboard показывает доступность target, скорость predict, долю 5xx и p95. Grafana запрашивает Prometheus datasource по внутреннему адресу http://prometheus:9090; браузер открывает Grafana через localhost:3000. Один график должен иметь понятное название, единицу и окно времени. Не заполняем экран CPU всех машин ради красоты. Отсутствие данных обозначаем как отсутствие данных, а не ноль. Студент должен объяснить, что бы он сделал после каждого наблюдения. Dashboard без действий становится декоративным экраном.
Контекст Сначала пользовательский путь, затем объясняющие ресурсы.
Создать небольшой dashboard и объяснить порог оповещения в терминах пользователя.
Предоставленный dashboard показывает доступность target, скорость predict, долю 5xx и p95. Grafana запрашивает Prometheus datasource по внутреннему адресу http://prometheus:9090; браузер открывает Grafana через localhost:3000. Один график должен иметь понятное название, единицу и окно времени. Не заполняем экран CPU всех машин ради красоты. Отсутствие данных обозначаем как отсутствие данных, а не ноль. Студент должен объяснить, что бы он сделал после каждого наблюдения. Dashboard без действий становится декоративным экраном.
Dashboard — ответ на вопросы Сначала пользовательский путь, затем объясняющие ресурсы.
01 Вопрос
→ 02 Источник
→ 03 Запрос
→ 04 График / решение
Аналогия: приборная панель водителя показывает нужное для управления, не всю бухгалтерию завода.
Предоставленный dashboard показывает доступность target, скорость predict, долю 5xx и p95. Grafana запрашивает Prometheus datasource по внутреннему адресу http://prometheus:9090; браузер открывает Grafana через localhost:3000. Один график должен иметь понятное название, единицу и окно времени. Не заполняем экран CPU всех машин ради красоты. Отсутствие данных обозначаем как отсутствие данных, а не ноль. Студент должен объяснить, что бы он сделал после каждого наблюдения. Dashboard без действий становится декоративным экраном.
SLI и SLO Измерение и целевое качество формулируются отдельно.
01 Пользовательский путь
→ 02 SLI: измерение
→ 03 SLO: цель + окно
→ 04 Бюджет ошибок
Аналогия: измеряем время доставки, затем договариваемся, какое время считаем приемлемым.
SLI — измеряемый показатель услуги, например доля успешных predict из валидных запросов. SLO — целевой уровень на определённом окне, например учебная гипотеза 99% за неделю; это не обещание фактического результата курса. SLA — внешнее соглашение с последствиями, его не моделируем как просто красивую цель. Определите, какие запросы входят в знаменатель, какие ошибки считаются и откуда берётся наблюдение. Внутренний scrape не доказывает доступность из браузера студента. Error budget — допустимая доля неуспеха относительно выбранного SLO, а не разрешение игнорировать сбои.
Алерт должен вести к действию Уведомление требует симптома, длительности и инструкции.
01 Симптом
→ 02 Условие + for
→ 03 Firing
→ 04 Runbook / действие
Аналогия: пожарная сигнализация должна вести к плану эвакуации, а не просто пищать.
Учебное правило StudyPulseDown срабатывает, когда up=0 держится 30 секунд. for уменьшает реакцию на одиночные краткие провалы. Это демонстрационный порог, в реальности его выбирают по последствиям и времени реакции. В annotation указываем симптом и ссылку на runbook. У нас показаны состояния inactive, pending, firing в Prometheus; внешняя отправка в мессенджер не подключена и уведомлений людям не создаёт. Для рабочих оповещений понадобятся Alertmanager, маршрутизация и дежурство. Алерт на CPU без понятного пользовательского риска часто создаёт шум.
Команды и наблюдения Ubuntu / Bash, lab; затем браузер
docker compose -f compose.yaml -f compose.monitoring.yaml up -d
# Open http://127.0.0.1:3000 and the provisioned StudyPulse dashboard
python3 scripts/load.py --requests 80 --concurrency 4
docker compose stop app
# Wait for at least 30 seconds plus scrape/rule evaluation
docker compose start appAnonymous Viewer включён только для локального учебного Grafana. Не публикуйте этот compose в интернет. Пароль администратора задаётся локально через GRAFANA_PASSWORD. Ожидаемое наблюдение: Dashboard меняется; в Prometheus /alerts видны pending и firing, после запуска — восстановление.
Разбор результата Dashboard меняется; в Prometheus /alerts видны pending и firing, после запуска — восстановление.
Anonymous Viewer включён только для локального учебного Grafana. Не публикуйте этот compose в интернет. Пароль администратора задаётся локально через GRAFANA_PASSWORD.
После остановки app target станет DOWN; rule с for=30s пройдёт pending. Реальный момент firing зависит от scrape и evaluation interval. Для восстановления docker compose start app, затем curl healthz и bash scripts/smoke.sh. Grafana password находится только в собственном lab/.env и не коммитится. Runbook не должен начинаться с удаления volume или переустановки всей системы.
На macOS Grafana, SLI/SLO и алерт одинаковы. Браузер открывает localhost:3000/9090, внутренний datasource остаётся prometheus:9090.
docker compose stop app
docker compose start app
bash scripts/smoke.shКоманды macOS необходимо проверить на конкретном устройстве. Grafana, SLI/SLO и алерт одинаковы. Браузер открывает localhost:3000/9090, внутренний datasource остаётся prometheus:9090.
Алерт с инструкцией Объясните четыре панели dashboard и единицы. Сформулируйте SLI predict с точным числителем/знаменателем. Остановите только app; наблюдайте переход алерта, затем восстановите. Напишите runbook из проверки target, процесса, журнала, исправления и smoke. Работа в парах: один оператор, второй проверяет гипотезу. Смена ролей в середине. После остановки app target станет DOWN; rule с for=30s пройдёт pending. Реальный момент firing зависит от scrape и evaluation interval. Для восстановления docker compose start app, затем curl healthz и bash scripts/smoke.sh. Grafana password находится только в собственном lab/.env и не коммитится. Runbook не должен начинаться с удаления volume или переустановки всей системы.
Что должно получиться Определение SLI/SLO и runbook с проверенным восстановлением.
Не обещаны внешние уведомления; порог назван учебным; отсутствие данных не объявлено успехом.
После остановки app target станет DOWN; rule с for=30s пройдёт pending. Реальный момент firing зависит от scrape и evaluation interval. Для восстановления docker compose start app, затем curl healthz и bash scripts/smoke.sh. Grafana password находится только в собственном lab/.env и не коммитится. Runbook не должен начинаться с удаления volume или переустановки всей системы.
Ошибка для диагностики Dashboard показывает внутреннюю доступность, а её принимают за проверку пользовательской сети.
Симптом → Гипотеза → Проверка
После остановки app target станет DOWN; rule с for=30s пройдёт pending. Реальный момент firing зависит от scrape и evaluation interval. Для восстановления docker compose start app, затем curl healthz и bash scripts/smoke.sh. Grafana password находится только в собственном lab/.env и не коммитится. Runbook не должен начинаться с удаления volume или переустановки всей системы.
Основные выводы SLI — измерение, SLO — желаемая граница на заданном окне. Чтобы условие сохранялось некоторое время перед firing. SLI — измерение, SLO — желаемая граница на заданном окне.
Чтобы условие сохранялось некоторое время перед firing.
После пары Подготовить runbook для одного пользовательского симптома собственного проекта.
Конспект, команды и разбор → Проверьте критерии сдачи и дайте возможность повторной попытки. Первоисточники: https://grafana.com/docs/grafana/latest/ https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ https://sre.google/workbook/implementing-slos/
Пара 22 / декабрь
Структурированные логи и расследование запроса Найти событие по request_id и составить временную линию без утечки входных данных.
Найти событие по request_id и составить временную линию без утечки входных данных. JSON-строки удобны для машинного фильтра. Наш журнал содержит timestamp, event, request_id, route, status, duration_ms и model_version. Тело входа не пишется. INFO означает обычное событие, WARNING — ожидаемую проблему запроса, ERROR — внутреннюю ошибку или отказ сохранения. Уровни — соглашение команды; важно определить, что действительно требует действия. В stdout идут operational logs, а в /data/events.jsonl сохраняется учебный результат; это разные роли хранения. Журнал может ротироваться или исчезнуть, поэтому доказательства важного инцидента сохраняем отдельно с минимальными данными.
Контекст Время, уровень, событие, версия и идентификатор запроса.
Найти событие по request_id и составить временную линию без утечки входных данных.
JSON-строки удобны для машинного фильтра. Наш журнал содержит timestamp, event, request_id, route, status, duration_ms и model_version. Тело входа не пишется. INFO означает обычное событие, WARNING — ожидаемую проблему запроса, ERROR — внутреннюю ошибку или отказ сохранения. Уровни — соглашение команды; важно определить, что действительно требует действия. В stdout идут operational logs, а в /data/events.jsonl сохраняется учебный результат; это разные роли хранения. Журнал может ротироваться или исчезнуть, поэтому доказательства важного инцидента сохраняем отдельно с минимальными данными.
Структура события Время, уровень, событие, версия и идентификатор запроса.
01 Событие
→ 02 Контекст
→ 03 request_id
→ 04 Версия / время
Аналогия: квитанция позволяет найти заказ без фотографии всех документов клиента.
JSON-строки удобны для машинного фильтра. Наш журнал содержит timestamp, event, request_id, route, status, duration_ms и model_version. Тело входа не пишется. INFO означает обычное событие, WARNING — ожидаемую проблему запроса, ERROR — внутреннюю ошибку или отказ сохранения. Уровни — соглашение команды; важно определить, что действительно требует действия. В stdout идут operational logs, а в /data/events.jsonl сохраняется учебный результат; это разные роли хранения. Журнал может ротироваться или исчезнуть, поэтому доказательства важного инцидента сохраняем отдельно с минимальными данными.
Корреляция вместо угадывания По одному идентификатору сопоставляем ответ и запись.
01 Ответ X-Request-ID
→ 02 Лог этого запроса
→ 03 Версия выпуска
→ 04 Временная линия
Аналогия: номер обращения связывает разговоры разных смен поддержки.
Сервис выдаёт собственный случайный X-Request-ID, не доверяя произвольному пользовательскому заголовку как безопасной метке. Клиент сохраняет заголовок, оператор ищет соответствующую строку. Для нескольких сервисов единый trace context обычно переносится стандартизированным способом; на курсе показываем идею, не пишем собственную tracing-платформу. Время фиксируется в UTC, чтобы не путать ноутбуки и серверы; при отображении учитываем локальную зону. Расхождение часов может нарушить порядок событий между узлами. По одной строке нельзя придумывать незафиксированные действия.
Конфиденциальность и сроки хранения Больше логов не всегда означает лучшую диагностику.
01 Минимальные поля
→ 02 Без секретов / payload
→ 03 Ограничение размера
→ 04 Доступ по роли
Аналогия: журнал проходной должен помогать расследованию, а не копировать содержимое всех сумок.
Пароли, токены, персональные входы и большие payload не сохраняем по умолчанию. Логи могут содержать URL с параметрами, поэтому маршрут нормализуем, query не пишем. Даже ID может быть чувствительным в контексте другой системы. Ограничение объёма и сроки хранения предотвращают заполнение диска. Docker logging driver с max-size/max-file ограничивает локальные operational logs. В нашем учебном data-файле нет персональных данных, но для настоящего проекта нужен отдельный retention. Изоляция в контейнере не скрывает логи от оператора, имеющего Docker-доступ.
Команды и наблюдения Ubuntu / Bash, lab
curl -sS -D /tmp/studypulse-headers.txt -H "Content-Type: application/json" -d '{"hours":4}' http://127.0.0.1:8000/predict
cat /tmp/studypulse-headers.txt
docker compose logs --no-log-prefix --tail 100 app > /tmp/studypulse-logs.jsonl
python3 scripts/read_logs.py /tmp/studypulse-logs.jsonlread_logs.py понимает только чистые JSON-строки и сообщает, если строка не подходит. Случайные секреты в вывод не копируем. Ожидаемое наблюдение: В заголовках виден X-Request-ID, в JSON-журнале — соответствующее событие, сырое hours не записано.
Разбор результата В заголовках виден X-Request-ID, в JSON-журнале — соответствующее событие, сырое hours не записано.
read_logs.py понимает только чистые JSON-строки и сообщает, если строка не подходит. Случайные секреты в вывод не копируем.
Возьмите ID из X-Request-ID, затем python3 scripts/read_logs.py /tmp/studypulse-logs.jsonl --request-id ID. Скрипт фильтрует parsed JSON по точному значению поля. Для неверного hours ожидается status=400 и warning; для нормального — 200 и info. Не добавлять request_id в Prometheus labels для удобства поиска.
На macOS JSON-парсер и фильтрация request_id переносимы. Не смешивайте Docker-логи приложения с macOS unified logging, это разные источники.
docker compose logs --no-log-prefix --tail 100 app > /tmp/studypulse-logs.jsonl
python3 scripts/read_logs.py /tmp/studypulse-logs.jsonlКоманды macOS необходимо проверить на конкретном устройстве. JSON-парсер и фильтрация request_id переносимы. Не смешивайте Docker-логи приложения с macOS unified logging, это разные источники.
История одного запроса Сделайте успешный и неправильный predict, сохраните два request_id. Найдите каждую запись через read_logs.py --request-id. Заполните таблицу время / версия / статус / длительность. Проверьте, не содержат ли записи тело запроса, query и лишние идентификаторы. Работа в парах: один оператор, второй проверяет гипотезу. Смена ролей в середине. Возьмите ID из X-Request-ID, затем python3 scripts/read_logs.py /tmp/studypulse-logs.jsonl --request-id ID. Скрипт фильтрует parsed JSON по точному значению поля. Для неверного hours ожидается status=400 и warning; для нормального — 200 и info. Не добавлять request_id в Prometheus labels для удобства поиска.
Что должно получиться Две минимальные временные линии без пользовательских payload.
События сопоставлены по фактам; поля и privacy проверены.
Возьмите ID из X-Request-ID, затем python3 scripts/read_logs.py /tmp/studypulse-logs.jsonl --request-id ID. Скрипт фильтрует parsed JSON по точному значению поля. Для неверного hours ожидается status=400 и warning; для нормального — 200 и info. Не добавлять request_id в Prometheus labels для удобства поиска.
Ошибка для диагностики Текстовый grep по JSON используется как доказательство точного совпадения поля.
Симптом → Гипотеза → Проверка
Возьмите ID из X-Request-ID, затем python3 scripts/read_logs.py /tmp/studypulse-logs.jsonl --request-id ID. Скрипт фильтрует parsed JSON по точному значению поля. Для неверного hours ожидается status=400 и warning; для нормального — 200 и info. Не добавлять request_id в Prometheus labels для удобства поиска.
Основные выводы Лог, потому что ID имеет высокую кардинальность. Нет; для инфраструктурной диагностики обычно достаточно минимального контекста. Лог, потому что ID имеет высокую кардинальность.
Нет; для инфраструктурной диагностики обычно достаточно минимального контекста.
После пары Добавить к runbook перечень допустимых полей лога и правило безопасного обмена доказательствами.
Конспект, команды и разбор → Проверьте критерии сдачи и дайте возможность повторной попытки. Первоисточники: https://docs.docker.com/engine/logging/configure/ https://opentelemetry.io/docs/concepts/signals/