Celery beatをHerokuのフリープラン(1 Dyno)でデプロイする方法

#はじめに
この記事ではタスクを定期実行する機能、Celery beatをHerokuのフリープラン(1 Dyno)にデプロイする方法を記載します。githubでソースを管理している、普通のWebアプリがHeroku上で稼働できていることを前提とします。

また、筆者はDjango初心者かつ、Webアプリのデプロイを初めてやったものです。間違いなどあるかもしれませんのでご了承ください。

#環境
ローカル
mac OS 10.14
python 3.7.5
pip
Django 2.2.8
celery==4.3.0
honcho==1.0.1

#完成形はこちら
Github
[hayatek/star-chart](https://github.com/hayatek/star-chart)

#Celery beatの実装方法

ローカル環境での実装も苦戦してこちらの記事に書きました。

[Django on DockerでCelery beatを使ってジョブの定期実行を実装](https://qiita.com/hayatek/items/060c786b862344418658)

Celery beatには2つの実装方法があります。

パターン(1)（django-celery-beatを使わないやり方）
パターン(2)（django-celery-beatを使うやり方）

ローカルでやった時はDjnagoのadminでも確認できるのでdjango-celery-beatを使う方が良いと書いたのですが、Herokuでは結果としてdjango-celery-beatを使うやり方は出来ませんでした。

なのでHerokuへのデプロイはパターン(1)でやっていきます。

#プロジェクト構成
Djangoの実装ではプロジェクト構成が重要だと思うのですが、意外とここが記載されていない記事が多い気がします。最初もう１階層深い階層でやっていたのですが、うまく行かず下記の階層にしました。Procfileはmanage.pyと同階層に配置します。

```
- StarChart     #BASE_DIR
  - config
    - settings
      - settings.py
    - celery.py
  - core
    - task.py
  - manage.py
  - Procfile
  - Procfile.real
  - requirements.txt
  - runtime.txt
```

#requirements.txtの編集
必須でインストールが必要なのは以下です。

```requirements.txt
celery 
redis
gunicorn
honcho
```

#settings.pyの編集
まずsettings.pyですが、ローカルと本番環境で分けて作っています。以下は本番用でCeleryに関わる部分を解説します。

```settings.py
# Celery config for production
BROKER_URL = os.environ['REDIS_URL']
CELERY_RESULT_BACKEND = os.environ["REDIS_URL"]
BROKER_POOL_LIMIT = 1
BROKER_CONNECTION_MAX_RETRIES = None
CELERY_IMPORTS = ('core.tasks')
CELERY_BEAT_SCHEDULE = {
    'update_database': {
        'task': 'core.tasks.update_database',
        'schedule': crontab(minute=0, hour=15) #must be UTC(JST-9h)
    }
}
```
[Heroku公式のCeleryガイド](https://devcenter.heroku.com/articles/celery-heroku)

まずheroku-redisのアドオンを追加します。CLIでも出来ますがクレカ登録していない場合はWebのダッシュボードからの方がよいかもしれません。

<img width="722" alt="heroku-redis.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256640/c059376e-4e84-14ea-bb68-7bf4b7e4ce5f.jpeg">

クレカ登録済んでいれば以下で大丈夫です。

```
heroku addons:create heroku-redis:hobby-dev
```

アドオン追加が終わればHerokuのconfigにREDIS_URLが設定されているはずなのでそれを以下のように取得します。

```settings.py
BROKER_URL = os.environ['REDIS_URL']
CELERY_RESULT_BACKEND = os.environ["REDIS_URL"]
```
また以下はローカルでの実装と同じで定期実行したいスケジュールを記載しています。デフォルトのhourはUTCなのでこの例では毎日JSTの0:00にタスクを実行します。

```settings.py
CELERY_BEAT_SCHEDULE = {
    'update_database': {
        'task': 'core.tasks.update_database',
        'schedule': crontab(minute=0, hour=15)
    }
}
```
もう一つ重要なのが、以下の記載です。筆者の環境ではこれを書いてないとうまく動きませんでした。

```settings.py
CELERY_IMPORTS = ('core.tasks')
```

#Procfileの設定
gunicornでアプリを起動するだけであればProcfileに書けば良いのですが、CeleryとCelery beatの2つのプロセスを起動する必要がありますが、Herokuの1Dyno上ではどうやら2つまでしか同時にプロセスを実行出来ないようです。

作成したアプリは実験的なものなのでなんとかフリープランで動かす方法がないかを探していたところ以下の記事に出会いました。Stackoverflowはホント素晴らしいですね。

[Celery beat not starting on Heroku | Stackoverflow](https://stackoverflow.com/questions/38361040/celery-beat-not-starting-on-heroku)

この記事の2つ目の回答に[honcho](https://honcho.readthedocs.io/en/latest/#why-did-you-port-foreman)を使うやり方が書いてありました。honchoの公式ページには以下の説明があります。

>A Python port of David Dollar’s Foreman: a command-line application which helps you manage and run Procfile-based applications. It helps you simplify deployment and configuration of your applications in both development and production environments.
Secondarily, Honcho is a Python library/API for running multiple external processes and multiplexing their output.

元々はDavid Dollarさんがrubyで作ったForemanのpythonバージョンですって感じですかね。。要はProcfileベースのアプリの管理をしやすくしてくれるツールですね。

まずrequirements.txtにhonchoを追加します。

```requirements.txt
honcho==1.0.1 #追加
```

このやり方の重要なところは、Procfileでは以下のようにhonchoでProcfile.realを起動します。

```Procfile.
web: honcho start -f Procfile.real
```

そして、Procfile.real（realの部分は任意の名前でOK）に以下の設定を記載します。

```Procfile.real
web: gunicorn --bind 0.0.0.0:$PORT config.wsgi
celery_worker: celery -A config worker -l info
celery_beat: celery -A config beat -l info
```

この状態で

```
git push heroku master
```
することで1Dyno上でweb、celery、celery beatの3つのプロセスを稼働させることが出来ました!

以下のアプリで毎日JST0:00にCeleryを稼働させています。

[django-starchart.herokuapp](https://django-starchart.herokuapp.com/)

##終わりに
うまく稼働は出来たものの、実際はHerokuのフリープランでCeleryを動かすとメモリが足りなくなる事があるようです。本番稼働していますが、CeleryでDBに溜め込む処理がメモリ不足になったりしているのでその辺りは今後の課題です。また何か分かればまた共有します。
最後までお読み頂きありがとうございました！