В будь-якому проекті є задача періодично запускати процеси. Наприклад, в @Donate1024Bot це щоденна ранкова розсилка всім підписникам нового збору, щоденна розсилка звітів, оноволення статистики всіх монобанок щопівгодини та інше.
В Heroku для цього є супер-зручна штука що називається Scheduler. В інтерфейсі наклікуєш коли запускати задачу, команду яку потрібно виконати і все. Далі Heroku сам в потрібний час підніме інстанс та запустить джобу. Я постійно користувався цією функцією.
Коли переїхав на fly, то виявилося, що там такого немає. В кінці вересня вони додали схожу функціональність, але вона не є повноцінною заміною крону.
Тому довелося педалити милиці власноруч. В більшості базових образів cron
вже встановлено, у мене це ruby:3.1.2-alpine
. Далі робимо текстовий файл з нашими джобами:
# Send new post MON to SUN at 9:00
0 7 * * * cd /app && ./bin/bundle exec rake donate:daily
# update mono jars every half of a hour
0,30 * * * * cd /app && ./bin/bundle exec rake donate:update_mono_jars
В Dockerfile
ініціалізуємо цей крон:
COPY crontab.txt /app/crontab.txt
RUN crontab /app/crontab.txt
Потім потрібно зробити на fly.toml процес власне крону:
[processes]
web = "bin/rails server -p 8080"
worker = "bundle exec sidekiq -c 3"
cron = "crond -f -d 0"
Все. Після деплою з'явиться новий інстанс всередині якого буде бігти крон.
З таким підходом у нас постійно буде крутитись окрема машина для цього, це звичайно недолік. Щоб оптимізувати, можна запускати крон разом з, наприклад, вебом через стартовий скрипт або systemd
, але я не хотів цим запарюватися.
Також потрібно зауважити що всі джоби мають коректно відпрацьовувати повторні або паралельні запуски. Конкретно я для цього використовую локи в базі даних: Lock.find_by(key: "daily_post").with_lock do
, щоб гарантувати єдиноразовий запуск, та не трапилося такого, що користувачі два рази отримують одне й те саме повідомлення.
Також сама крон джоба не містить коду який працює, а просто кладе в чергу Sidekiq джобу яка вже буде оброблятися на іншому інстансі. Таким чином ми можемо виділити крон-процесу мінімум пам'яті, бо все що він робить—це ставить завдання у чергу.
В проектах з мікросервісами крон це проблема, тому що там теж треба якимось чином гарантувати єдиноразовий запуск та коректно обробляти повторні запуски однієї й тієї ж самої джоби. На попередньому проекті де був AWS для розподіленого крону ми використовували CloudWatch Event які тригерили лямбду, яка клала джобу в чергу або тригерила якийсь ендпоїнт. Чесно кажучи це цирк на дроті, а не нормальне рішення. Можливо зараз амазон дає щось краще. Також в кубернетісі та номаді є крон джоби, тому можливо ця задача вирішується простіше, аніж встановлення спеціального рішення типу Quartz або Sidikiq Enterprise, де є функціональність розподіленого крону. Впевнений, що читачі підкажуть.
Сподобалось? Долучайтеся до мого телеграм каналу: https://t.me/full_of_hatred