Наш цикл очікування чекав на себе 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: він ніколи не шукає процес за командним рядком. Цикли, які нас підвели, були тими, що ми писали вручну.