Практикум: Скрипты базового мониторинга

Курс по созданию инструментов оценки здоровья системы. Вы научитесь объединять команды управления процессами и сетевые утилиты в автоматизированные конвейеры для оперативного мониторинга ресурсов.

1. Конвейеры и фильтрация: сборка диагностических данных из стандартных потоков

Конвейеры и фильтрация: сборка диагностических данных из стандартных потоков

Вы вводите команду free -m, чтобы узнать объем свободной оперативной памяти. На экране появляется аккуратная таблица с заголовками, строками для физической памяти и раздела подкачки. Для человека это удобно. Но если ваша цель — написать скрипт, который отправит алерт при превышении порога использования памяти в 90%, эта таблица бесполезна. Скрипту не нужны заголовки и форматирование. Ему нужно ровно одно числовое значение, очищенное от любого текстового мусора, чтобы выполнить математическое сравнение. Превращение человекочитаемого вывода системных утилит в строгие машинные метрики — это главная задача конвейеров в скриптах мониторинга.

Анатомия конвейера и файловые дескрипторы

В основе философии UNIX лежит принцип: программы должны делать что-то одно, делать это хорошо и уметь работать вместе. Связующим звеном выступают стандартные потоки ввода-вывода. Каждый запускаемый процесс автоматически получает три открытых файловых дескриптора:

  • 0 (stdin) — стандартный поток ввода (по умолчанию клавиатура).
  • 1 (stdout) — стандартный поток вывода (по умолчанию терминал).
  • 2 (stderr) — стандартный поток ошибок (по умолчанию терминал).
  • Символ вертикальной черты | (pipe) создает анонимный канал в оперативной памяти, который связывает файловый дескриптор 1 левого процесса с файловым дескриптором 0 правого процесса.

    !Схема передачи данных между процессами через конвейер

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

    Это порождает интересное взаимодействие на уровне сигналов ОС. Если правый процесс завершает работу раньше, чем левый закончил писать данные (например, в конструкции cat large_log.txt | head -n 5), ядро Linux отправляет левому процессу сигнал SIGPIPE. Получив SIGPIPE, процесс немедленно завершается. Именно благодаря этому механизму команда чтения гигабайтного файла мгновенно останавливается, как только утилита head получает нужные пять строк, экономя ресурсы процессора и диска.

    Извлечение метрик: от текста к числу

    Рассмотрим процесс создания метрики на примере мониторинга свободного места на корневом диске. Стандартный инструмент для этой задачи — утилита df.

    Вызов df -h / выдает следующий результат:

    Нам нужно извлечь значение 89 (без знака процента). Построим конвейер поэтапно.

    Во-первых, нужно избавиться от строки заголовков. Хотя можно использовать grep для фильтрации, утилита awk предоставляет более элегантный способ работы со структурированным текстом. В awk есть встроенная переменная NR (Number of Record), хранящая номер текущей обрабатываемой строки.

    Мы можем приказать awk обрабатывать только вторую строку и выводить пятое поле (колонку): df -h / | awk 'NR==2 {print 5}' | tr -d '%'

    Теперь конвейер возвращает строго 89. Это значение уже можно использовать в логических операциях.

    Математика внутри конвейера

    Иногда нужной метрики просто нет в готовом виде, и ее нужно вычислить на лету. Вернемся к задаче мониторинга оперативной памяти. Утилита free -m показывает использованную и доступную память в мегабайтах, но не показывает процент использования.

    Для мониторинга абсолютные значения часто бесполезны (1000 МБ свободной памяти — это критически мало для сервера с 128 ГБ RAM, но вполне нормально для виртуальной машины с 2 ГБ). Нам нужен процент. Формула расчета процента занятой памяти:

    где — искомый процент, — использованная память (Used), — общая память (Total).

    Мы можем поручить эти вычисления утилите awk, которая прекрасно справляется с математикой с плавающей точкой:

    free -m | awk 'NR==2 {printf "%.0f", 2 * 100}'

    Здесь мы обращаемся ко второй строке (NR==2), берем третье поле (2, Total), умножаем на 100. Вместо обычного print используется функция форматирования printf "%.0f", которая округляет результат до целого числа (ноль знаков после запятой). Конвейер стал не просто фильтром, но и вычислительным модулем.

    Проблема невидимых ошибок (stderr)

    При построении длинных конвейеров администраторы сталкиваются с критической уязвимостью. Конвейер | перенаправляет только стандартный вывод (stdout, дескриптор 1). Поток ошибок (stderr, дескриптор 2) игнорируется конвейером и отправляется напрямую в терминал.

    Представьте, что скрипт проверяет состояние службы: systemctl status nginx | grep "Active:" | awk '{print 5}'

    Захват результата: Command Substitution

    Чтобы использовать результат работы конвейера внутри скрипта, его нужно поместить в переменную. Для этого используется механизм подстановки команд (Command Substitution) — конструкция (df -h / 2>/dev/null | awk 'NR==2 {print (...), она создает дочерний процесс (subshell), выполняет внутри него весь конвейер, перехватывает его итоговый stdout и подставляет его в исходную строку вместо самой конструкции.

    Исторически для этой цели использовались обратные кавычки (` команда ), однако синтаксис (команда1 ?, вы увидите код grep, а не фатальную ошибку curl. Хуже того, если последней командой был awk '{print $1}', он успешно обработает пустой ввод и вернет код 0 (успех). Скрипт продолжит работу, считая, что метрика успешно собрана, хотя на самом деле сеть лежит.

    Для решения этой проблемы в скриптах мониторинга обязательно используется директива изменения поведения оболочки: set -o pipefail

    При включенной опции pipefail конвейер возвращает код ошибки, если любая команда в цепочке завершилась с ошибкой. Возвращается код возврата самой правой провалившейся команды. Если все команды отработали успешно, возвращается 0.

    Включение set -o pipefail (часто в комбинации с set -e`, которая прерывает скрипт при любой ошибке) превращает хрупкие цепочки фильтрации в надежные инструменты диагностики, которые немедленно сигнализируют о сбоях в получении данных, а не продолжают работу с пустыми переменными.