Расписание наблюдений — cron или systemd timer
Предыдущая глава дала сборщик наблюдений с ограниченными командами и сроками ожидания. Теперь рассмотрим его периодический вызов. Автоматизация полезна, когда сохраняет тот же договор, что и ручная процедура: известно, от какого пользователя выполняется работа, где находятся входы, что случается при занятой блокировке и как найти результат. Одной строки «каждые пятнадцать минут» для такого договора недостаточно.
В stages/15 учебного архива сохранены полный сборщик, сценарий сводки, TSV, новый scheduled-collect.sh и два варианта конфигурации. Скопируйте содержимое этого снимка в отдельную собственную лабораторию ~/ops-lab-schedule, если будете воспроизводить упражнение. Путь в примере cron нужно привести к настоящему абсолютному имени каталога читателя. При подготовке курса расписания не устанавливались, службы не активировались и примеры не запускались.
Окружение определяем явно
В интерактивном терминале часто уже настроены переменные, сокращения и текущий каталог. Периодическая задача получает другое окружение. Поэтому обёртка сама задаёт PATH, локаль и рабочий каталог. Это не означает, что утилиты автоматически появятся на машине: наличие нужных программ по-прежнему проверяется сборщиком. Настройка лишь определяет, где искать подготовленные инструменты и из какого каталога выполнять работу.
В этом упражнении приложению не нужны новые секреты. Сборщик читает синтетическую таблицу и доступные системные сведения. Он не загружает deskqueue.env, не получает пароль PostgreSQL и не выполняет команды через source. Режим service мог бы добавить проверку существующей учебной службы, но в конфигурациях расписания выбран независимый режим basic. Такое разделение делает первый шаг воспроизводимым без одновременно работающего API.
Одна операция удерживает одну блокировку
Два параллельных сборщика могут создать разные каталоги, однако всё равно лишний раз нагружают машину и затрудняют понимание интервала наблюдений. Для периодического вызова введём блокировку state/collector.lock. Она принадлежит лаборатории и не связана с внутренними блокировками приложения или PostgreSQL. Все запуски, которые должны соблюдать это правило, проходят через одну обёртку.
#!/usr/bin/env bash
set -u
set -o pipefail
umask 077
PATH=/usr/sbin:/usr/bin:/sbin:/bin
LC_ALL=C
export PATH LC_ALL
if (( $# < 1 || $# > 2 )) || [[ $1 != /* || ! -d $1 ]]; then
printf 'Usage: bash scheduled-collect.sh ABSOLUTE_LAB_ROOT [basic|service]\n' >&2
exit 2
fi
if root=$(cd -- "$1" && pwd -P); then :; else exit 1; fi
mode=${2:-basic}
case "$mode" in
basic|service) ;;
*) printf 'Unknown mode\n' >&2; exit 2 ;;
esac
if ! command -v flock >/dev/null 2>&1 || [[ ! -r "$root/scripts/collect.sh" ]]; then
printf 'flock and the collector are required\n' >&2
exit 2
fi
if ! mkdir -p -- "$root/state"; then exit 1; fi
cd -- "$root" || exit 1
exec 9> "$root/state/collector.lock" || exit 1
if flock -n -E 75 9; then
exec /bin/bash "$root/scripts/collect.sh" "$root" "$mode"
else
result=$?
printf 'Collection skipped: lock could not be acquired (status %s)\n' "$result" >&2
exit "$result"
fi
Число девять обозначает дополнительный файловый дескриптор. Сначала Bash открывает файл, затем flock получает исключительную блокировку на этот дескриптор. При -n ожидания нет: занятый ресурс приводит к немедленному завершению. Параметр -E 75 даёт отдельный код для конфликта. Поэтому отсутствие нового каталога может означать намеренно пропущенный запуск, а не поломку сборщика.
exec заменяет процесс обёртки процессом Bash, выполняющим сборщик. Открытый дескриптор остаётся у новой программы, поэтому блокировка удерживается на время её работы и освобождается после закрытия. Удалять файл в конце не нужно. Само наличие пустого файла не говорит о занятой блокировке: существенное состояние хранит ядро. Детали описаны в справке flock.
Это согласованная блокировка участников, а не запрет любых чтений лаборатории. Человек может обойти обёртку и вызвать collect.sh напрямую. На некоторых сетевых файловых системах правила блокировок отличаются; пример предполагает обычный локальный каталог. Если процедура должна работать на другом хранилище, сначала проверяют его свойства и выбирают подходящий механизм, а не объявляют файл универсальным средством координации.
Вариант с пользовательским cron
Первый вариант хранится в config/crontab.example. Это запись пользовательской таблицы: отдельного поля имени пользователя после времени здесь нет. Пять временных полей обозначают минуту, час, день месяца, месяц и день недели. Выражение */15 в первом поле задаёт минуты 0, 15, 30 и 45 каждого подходящего часа. Остальные звёздочки оставляют остальные поля свободными.
# Choose this OR the user systemd timer, never both.
# Adjust /home/reader/ops-lab-schedule to your own laboratory path.
SHELL=/bin/bash
PATH=/usr/sbin:/usr/bin:/sbin:/bin
LC_ALL=C
MAILTO=""
*/15 * * * * /bin/bash /home/reader/ops-lab-schedule/scripts/scheduled-collect.sh /home/reader/ops-lab-schedule basic >> /home/reader/ops-lab-schedule/scheduled.log 2>&1
Сценарий вызывается абсолютным путём, а рабочий каталог меняет сама обёртка. Вывод добавляется в лабораторный scheduled.log; перенаправление 2>&1 здесь удобно для краткого сообщения обёртки, поскольку подробные потоки наблюдений сборщик уже сохраняет раздельно. Значение MAILTO отключает почтовую отправку вывода. Оно не создаёт систему уведомлений: читатель должен отдельно решить, как будет замечать отсутствие свежих материалов.
Комментарий расположен отдельной строкой. Внутри записи cron знак комментария после команды может стать частью самой команды. Также нельзя рассчитывать на произвольные интерактивные переменные в конфигурации. Условия окружения, шаги временных полей и особенности календарного времени описаны в справке crontab. Для наших наблюдений пропущенный период означает пробел в данных; будущая сводка должна учитывать этот пробел.
Вариант с пользовательским systemd timer
Второй вариант состоит из пары файлов: deskqueue-observe.service описывает одну конечную процедуру, а deskqueue-observe.timer определяет календарь её вызова. Это пользовательские units для собственной лаборатории; основная служба приложения deskqueue.service остаётся отдельной системной сущностью. Подстановка %h указывает домашний каталог пользователя соответствующего менеджера, а не домашний каталог произвольного процесса.
[Unit]
Description=Collect observations for the DeskQueue laboratory
[Service]
Type=oneshot
TimeoutStartSec=70s
WorkingDirectory=%h/ops-lab-schedule
Environment=PATH=/usr/sbin:/usr/bin:/sbin:/bin
Environment=LC_ALL=C
ExecStart=/bin/bash "%h/ops-lab-schedule/scripts/scheduled-collect.sh" "%h/ops-lab-schedule" basic
SuccessExitStatus=75
Тип oneshot подходит работе, которая завершается после сбора материалов. В конфигурации явно задан общий срок семьдесят секунд; он дополняет сроки отдельных наблюдений. Код 75 признан допустимым итогом, потому что пропуск при занятой блокировке предусмотрен договором. Код один от сборщика продолжит означать наличие неуспешного наблюдения, которое нужно прочитать в созданном каталоге. Семантика такого конечного задания приведена в справке systemd.service.
[Unit]
Description=Observe the DeskQueue laboratory every fifteen minutes
[Timer]
OnCalendar=*:0/15
Persistent=true
AccuracySec=1min
RandomizedDelaySec=30s
Unit=deskqueue-observe.service
[Install]
WantedBy=timers.target
OnCalendar задаёт минуты через пятнадцать. Точность одна минута и случайная задержка до тридцати секунд разрешают небольшое смещение запуска. Поэтому сравниваем фактическое время каталогов, а не требуем появления результата ровно в нулевую секунду. Для задачи наблюдений такое смещение приемлемо; требование другого продукта может потребовать иной конфигурации.
Persistent=true сохраняет момент предыдущего вызова и позволяет выполнить одну догоняющую процедуру после периода неактивности. Таймер не воспроизводит каждый пропущенный снимок прошлого и не создаёт данные за время, когда машина была выключена. Эта настройка относится к календарным таймерам. Пользовательский менеджер также должен быть активен в выбранной среде; файл конфигурации сам по себе не обеспечивает работу пользователя во время отсутствия сеанса. Эти границы описаны в справке systemd.timer.
Выбор и наблюдение за самим расписанием
Выберите один из двух планировщиков. Две одновременно установленные записи создают лишние попытки: блокировка может предотвратить часть повторов, но не делает двойную конфигурацию понятной. Сохраните в рабочей заметке имя выбранного механизма, пользователя, период и путь лаборатории. Следом укажите, где смотреть статус последней процедуры и её материалы. Для пользовательского таймера после его отдельной подготовки доступны чтения состояния:
systemctl --user status deskqueue-observe.timer
systemctl --user list-timers --all
Ожидаемый результат собственной настройки — последовательность отдельных каталогов evidence, а при конфликте блокировки понятное сообщение о пропуске. Уже сохранённые материалы и scheduled.log постепенно занимают место; срок их хранения нужно определить до постоянной эксплуатации. В этом упражнении автоматического удаления нет. Итог главы — воспроизводимый периодический вызов с известным окружением и правилами конкуренции. В заключительном уроке применим похожую дисциплину к копированию файлов и проверке восстановления.