> ## Content Index
> Fetch the complete content index at: https://www.rozhkov.me/llms.txt
> Use this file to discover other available public pages before exploring further.

# cron на fly.io
- URL: https://www.rozhkov.me/cron-jobs-on-fly-io/
- Published: 2022-11-22T07:00:00.000Z
- Updated: 2022-11-22T07:00:00.000Z
- Author: Vova Rozhkov
- Tags: інструменти

В будь-якому проекті є задача періодично запускати процеси. Наприклад, в [@Donate1024Bot](https://t.me/Donate1024Bot) це щоденна ранкова розсилка всім підписникам нового збору, щоденна розсилка звітів, оноволення статистики всіх монобанок щопівгодини та інше.

В Heroku для цього є супер-зручна штука що називається [Scheduler](https://devcenter.heroku.com/articles/scheduler). В інтерфейсі наклікуєш коли запускати задачу, команду яку потрібно виконати і все. Далі Heroku сам в потрібний час підніме інстанс та запустить джобу. Я постійно користувався цією функцією.

Коли переїхав на fly, то виявилося, що там такого немає. В кінці вересня вони додали [схожу функціональність](https://community.fly.io/t/new-feature-scheduled-machines/7398), але вона не є повноцінною заміною крону.

Тому довелося педалити милиці власноруч. В більшості базових образів `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, де є функціональність розподіленого крону. Впевнений, що читачі підкажуть.