MAATRIX / Блог / Чужие cron-задачи: разбираем расписание, которое никто не помнит

Чужие cron-задачи: разбираем расписание, которое никто не помнит

MAATRIX

Вы приняли сервер — купили проект, заменили ушедшего подрядчика, стали новым админом в компании — и на второй-третий день замечаете: что-то запускается по ночам. В логах мелькают процессы, о которых никто в команде не может внятно рассказать. crontab -l показывает пять строк без единого комментария, а под капотом наверняка есть ещё столько же в системных файлах и таймерах. Одна из этих строк, возможно, снимает единственную рабочую копию базы. Другая — шлёт клиентам письма, о существовании которых вы не подозревали. Третья не делает вообще ничего полезного уже два года. Разница между ними на первый взгляд незаметна: все выполняются молча, все выглядят одинаково безобидно в списке. Разберём, как за разумное время получить полную картину чужого расписания, отличить то, что держит бизнес, от того, что можно смело выключить, и как проверить это на практике — не сломав по дороге ничего рабочего.

Полная инвентаризация: cron всех пользователей плюс systemd timers

Первая ошибка при приёмке чужого сервера — посмотреть crontab -l под своим логином и решить, что расписание видно целиком. На сервере с несколькими сервисными аккаунтами (www-data, postgres, backup, приложением на своём пользователе) задачи разбросаны минимум по пяти-шести разным местам, и часть из них не покажет ни одна команда без явного указания пользователя.

Обойти все личные crontab одной командой:

for u in $(cut -f1 -d: /etc/passwd); do
  out=$(crontab -l -u "$u" 2>/dev/null)
  if [ -n "$out" ]; then
    echo "=== $u ==="
    echo "$out"
  fi
done

Системные файлы, которые эта команда не покажет вообще, потому что это отдельный механизм:

cat /etc/crontab
ls -la /etc/cron.d/ && cat /etc/cron.d/*
ls -la /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/

И слой, который на унаследованном сервере особенно легко пропустить — systemd timers. Если прежний админ настраивал сервер в последние пару лет, часть периодических задач вполне может жить именно там, а не в cron:

systemctl list-timers --all

Флаг --all обязателен — без него команда покажет только таймеры с ближайшим запуском в будущем, а отключенные или ещё не наступившие останутся невидимыми. Полную карту мест, где может прятаться периодическая задача — включая /etc/anacrontab, at-задачи и сырые файлы в /var/spool/cron/ для уже удалённых пользователей — подробно разбирали в статье где ещё смотреть, кроме crontab. Здесь не будем повторять весь список — сразу перейдём к тому, что делать с находками на только что принятом сервере.

Результат инвентаризации имеет смысл сразу выгрузить в один текстовый файл — построчно, с указанием, из какого источника взята каждая запись. На унаследованном сервере это не разовая справка для галочки, а основа, к которой вы будете возвращаться неделями, пока не разберётесь с каждой строкой.

Как читать чужую строку: пользователь, скрипт, куда он смотрит

Строка расписания сама по себе почти ничего не говорит о критичности задачи — критичность определяется тем, что делает скрипт и с чем он работает. Прежде чем классифицировать, стоит для каждой найденной задачи быстро выяснить три вещи.

От чьего имени она выполняется. Задача от root может трогать что угодно в системе; задача от прикладного пользователя ограничена его правами. Это первый и самый грубый фильтр риска — если строка в /etc/cron.d/ запускает неизвестный скрипт от root, разбираться с ней стоит раньше остальных.

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

test -f /opt/scripts/имя_скрипта.sh && echo "есть" || echo "НЕТ ФАЙЛА"

Если файла нет — задача гарантированно не делает ничего полезного, кроме письма об ошибке на локальную почту. Такие случаи закрываются быстрее всего: доказывать критичность нечего.

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

grep -oE '(mysqldump|pg_dump|rsync|curl|wget|s3cmd|aws s3|sendmail|mailx|msmtp|borg|restic)[^ ]*' /opt/scripts/имя_скрипта.sh

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

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

На унаследованном сервере, где нет документации, разумно исходить из того, что любая задача критична, пока не доказано обратное. Но три категории заслуживают особого внимания сразу, потому что именно их отключение чаще всего превращается в инцидент, а не в мелкую неприятность.

Бэкапы. Ищите в командах pg_dump, mysqldump, mongodump, rsync с флагом на удалённый хост, borg, restic, duplicity, вызовы aws s3/s3cmd/rclone. Задача-бэкап может быть замаскирована под что-то другое — например, называться nightly.sh без единого намёка в имени. Проверяйте не только факт запуска, но и куда складывается результат:

grep -rE "pg_dump|mysqldump|rsync|borg|restic|s3cmd|rclone" /etc/cron.d/* /var/spool/cron/crontabs/* 2>/dev/null

Если нашли — сразу выясните, где физически лежат копии, за сколько дней хранятся и снимался ли когда-нибудь тестовый restore. Незнакомый бэкап, который никто не тестировал, — не повод его выключать, это повод его наконец проверить.

Рассылки и уведомления. Признаки — вызовы sendmail, mailx, msmtp, обращения к SMTP-библиотекам в самописных скриптах, команды, дёргающие Postfix-очередь, задачи, которые звонят во внешние API рассылок. На унаследованном сервере это часто транзакционные письма (чеки, подтверждения заказов) или отчёты клиентам — то, что не сломает сайт при отключении, но сломает доверие клиента, если он неделю не получал ожидаемое письмо.

grep -rlE "sendmail|mailx|msmtp|smtplib|nodemailer|PHPMailer" /opt/scripts/*.sh /opt/scripts/*.py 2>/dev/null
mailq   # что реально стоит в очереди Postfix прямо сейчас

Синхронизации. Самая коварная категория, потому что последствия отключения проявляются не сразу, а спустя дни или недели — когда данные в двух системах молча разъедутся. Сюда относятся: обмен с 1С/CRM, сверка заказов с маркетплейсом, подтягивание курсов валют, репликация каталога товаров между сайтом и складской системой. Ищите вызовы к внутренним и внешним API, обращения к очередям сообщений, скрипты с именами вроде sync_, import_, export_, feed_.

КатегорияКлючевые слова в скриптеТипичный риск отключения
Бэкапыpg_dump, mysqldump, rsync, borg, restic, s3потеря единственной актуальной копии данных
Рассылкиsendmail, msmtp, SMTP-библиотеки, mailqклиент не получает чек, подтверждение, отчёт
Синхронизацииsync_, import_, export_, обращения к API/очередямданные в двух системах расходятся молча

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

Что обычно можно отключать почти сразу

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

  • Скрипт физически отсутствует — уже разобрали выше, самый безопасный случай.
  • Задача обслуживает домен или API, который больше не отвечает. Проверяется одной командой: curl -sSf -o /dev/null -w "%{http_code}\n" https://адрес — если это стабильно 000 или 404 не первую неделю, объект задачи, скорее всего, мёртв.
  • Самописная замена штатного механизма. Ручной скрипт очистки /tmp, который дублирует systemd-tmpfiles, или ротация логов вручную там, где давно можно доверить это logrotate. Такие задачи не вредны сами по себе, но обходят системные механизмы и добавляют риск без выгоды.
  • Тестовые и отладочные скрипты прежнего администратора. Имена вроде test.sh, check_new.sh, tmp_fix.sh, особенно с датой в имени файла старше нескольких месяцев — почти всегда временное решение, которое забыли убрать.

Даже для этих случаев правило одно: не удалять в день обнаружения. На чужом сервере вы не можете быть уверены на сто процентов, даже когда все признаки указывают на мусор — у прежнего админа мог быть контекст, которого вы не видите. Здесь и нужен способ проверить гипотезу без риска.

Временное логирование вместо немедленного отключения

Самая частая ошибка при работе с чужим расписанием — сразу закомментировать сомнительную строку и ждать жалоб. Проблема в том, что жалоба может прийти через месяц, когда никто уже не свяжет её с вашим действием, а часть последствий (расхождение данных, непришедший отчёт) вообще не порождает явной жалобы — просто тихо портит бизнес-процесс.

Более надёжный первый шаг — не выключать задачу, а обернуть её в логирующий враппер, который фиксирует каждый запуск, но не меняет поведение самого скрипта. Задача продолжает выполняться как выполнялась, ничего не ломается, а вы за одну-две недели накапливаете факты вместо догадок.

Простой враппер для crontab-задачи:

#!/bin/bash
# /opt/cron-wrappers/watch-имя_скрипта.sh
LOG=/var/log/cron-watch/имя_скрипта.log
START=$(date +%s)

echo "$(date '+%F %T') START pid=$$ user=$(whoami)" >> "$LOG"

/opt/scripts/имя_скрипта.sh
CODE=$?

END=$(date +%s)
echo "$(date '+%F %T') END code=$CODE duration=$((END-START))s" >> "$LOG"

exit $CODE

В crontab исходная строка на время наблюдения просто заменяется вызовом враппера вместо самого скрипта — расписание и поведение не меняются:

mkdir -p /var/log/cron-watch
chmod +x /opt/cron-wrappers/watch-имя_скрипта.sh
# было:  0 3 * * * /opt/scripts/имя_скрипта.sh
# стало: 0 3 * * * /opt/cron-wrappers/watch-имя_скрипта.sh

За одну-две недели такой лог отвечает на вопросы, которые до этого приходилось угадывать: действительно ли задача запускается по расписанию (иногда выясняется, что crontab-строка вообще не срабатывает из-за ошибки окружения), сколько времени занимает выполнение, стабилен ли код возврата, не растёт ли аномально длительность со временем.

Для systemd timers логирование получить ещё проще — journal уже пишет каждый запуск и код завершения без дополнительных врапперов, нужно только смотреть за достаточный период:

journalctl -u имя.service --since "-14 days" | grep -E "Started|Finished|Failed|status="

Отдельно стоит прогнать связанные системы через грубую внешнюю проверку — не факт запуска задачи, а факт того, что зависящий от неё процесс продолжает получать свежие данные. Если задача синхронизации формально отработала без ошибок, но данные в приёмнике не менялись две недели — это сигнал, что либо задача сломана содержательно, либо приёмник давно не смотрит на её результат. Подход через внешний пинг после выполнения задачи, который удобно приспособить и для такого обратного контроля, разобран в статье про Healthchecks.io и мониторинг cron-задач.

От логов к решению: отключать, оставить или переписать

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

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

Логи показывают, что задача годами падает или не запускается. Если за две недели наблюдения набрался код возврата отличный от нуля на каждом прогоне, а в рабочих чатах никто ни разу не упомянул связанную с ней проблему — это сильный аргумент, что результат никому не был нужен настолько, чтобы заметить его отсутствие. Такую задачу безопасно отключать: она и так не делала полезной работы всё то время, что вы её наблюдали.

Логи показывают, что задача исправно работает, но объекта её работы больше нет (мёртвый API, закрытый проект) — можно отключать сразу после подтверждения, без дополнительной недели ожидания: это не то же самое, что живая, но подозрительная задача.

Финальное отключение — то же самое правило «гасить, а не удалять», о котором подробно с примерами для crontab-строк, /etc/cron.d/ и systemd timers написано в статье про ревизию cron-задач: комментарий с датой и причиной вместо стирания строки, systemctl disable вместо mask, период дополнительного наблюдения после самого отключения — уже без враппера, просто тишина в логах.

Итоговая находка по каждой задаче заносится в общий реестр сервера — простую таблицу рядом с документацией по инфраструктуре. На унаследованном сервере такой реестр особенно ценен: он не восстанавливает то, что делал прежний администратор, а строит новое знание, которое в следующий раз не придётся собирать с нуля. Общий подход к тому, как вообще принимать сервер от предыдущего владельца или подрядчика, помимо разбора cron, собран в чек-листе приёмки нового сервера.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Сколько времени в среднем занимает разбор чужого cron на только что принятом сервере?

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

Можно ли сразу отключить задачу, если она выглядит подозрительно, но её объект работы существует?

Лучше не отключать, а сначала обернуть во враппер и понаблюдать. «Выглядит подозрительно» — не доказательство: задача может обслуживать процесс, о котором вы просто не знаете, а именно так на унаследованных серверах чаще всего и ломают чужие бизнес-процессы.

Что делать, если враппер сам может повлиять на работу задачи — например, изменить рабочую директорию?

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

Как быть, если задача критична, но никто не знает пароля или доступа к системе, с которой она работает?

Это отдельная и частая находка на унаследованном сервере — сама задача жива и нужна, но её нельзя ни проверить полноценно, ни перенести без утраченного доступа. В таком случае временное логирование ещё важнее: пока доступ не восстановлен, вы хотя бы фиксируете, что задача выполняется и с каким результатом, вместо того чтобы трогать её вслепую.

Нужно ли предупреждать команду перед тем, как начинать логирование задач?

Обёртывание во враппер ничего не меняет для внешних наблюдателей — можно делать без предупреждения. А вот перед финальным отключением любой задачи, чей объект работы не доказанно мёртв, короткое сообщение в общий чат стоит меньше, чем разбор инцидента с процессом, о котором вы не знали.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →