Наш цикл ожидания ждал сам себя 35 часов: pgrep -f и pkill -f по SSH
Операции
31 октября мы запустили небольшой цикл по SSH: ждать, пока на другой машине завершится задача перевода, а затем запустить последующую задачу. Задача перевода завершилась той же ночью. Когда мы посмотрели снова два дня спустя, цикл ждал уже 35 часов, а последующая задача так и не запустилась.
Что представлял собой цикл
ssh build-host 'while pgrep -f "i18n_job.py example.com" >/dev/null; do sleep 30; done; ./follow-up.sh'
pgrep -f ищет совпадение с шаблоном в полной командной строке каждого процесса. SSH выполняет заключенную в кавычки команду как bash -c 'while pgrep -f "i18n_job.py example.com" …', и эта командная строка содержит шаблон. pgrep исключает из результатов собственный процесс, но не оболочку, которая его запустила. Поэтому, пока цикл был жив, он находил совпадение: с самим собой.
Его близнец: pkill -f убивает оболочку, в которой запущен
То же самое происходит с pkill -f, но с худшими последствиями:
ssh build-host 'pkill -f "worker.py --queue a"; nohup python3 worker.py --queue a &'
Командная строка самой оболочки содержит шаблон, поэтому pkill убивает оболочку. Воркер останавливается и больше не запускается, а SSH завершается с кодом 255 без сообщения об ошибке. Это выглядит как проблема с кавычками, чем мы и посчитали это в первый раз.
С сентября по конец октября мы попадали в одну из этих ситуаций как минимум с дюжину раз: перезапуски, которые так и не произошли, цикл, который удерживал проверку три с половиной часа, другой, который удерживал очередь переводов 50 минут, и этот случай.
Что мы делаем теперь
- Скобка в шаблоне:
pgrep -f "[i]18n_job.py example.com". Как регулярное выражение[i]соответствует букве i, поэтому задача по-прежнему находится; командная строка оболочки содержит символы[i], которым выражение не соответствует. Это помогает, только если обычный шаблон нигде больше не встречается в этой строке: команда перезапуска позже в той же строке снова найдет совпадение. - Искать, останавливать и запускать в отдельных вызовах SSH. Один вызов выводит список процессов с помощью
pgrep -af, следующий убивает выведенные PID, третий запускает новый процесс. - Ждать PID, а не шаблон:
tail --pid=1234 -f /dev/nullилиwhile kill -0 1234; do sleep 30; done. - Еще лучше — доверить задачу systemd. Запустите ее как юнит и проверьте через
systemctl is-activeили запустите с помощьюsystemd-run --wait. Здесь не ошибешься в выборе шаблона. - У каждого ожидания должен быть дедлайн (
timeout 3h) с указанием, когда оно прекращается. Цикл без дедлайна завершается молча.
Factory Floor, набор, который управляет нашими машинами, работает через юниты systemd: он никогда не ищет процесс по командной строке. Именно те циклы, которые мы писали вручную, и подвели нас.