Docker Python+Django+Celeryの1コンテナで定期実行するメモ

Docker, Python, Django, Celeryで1つのコンテナで定期実行を行う。

環境

  • Docker version 19.03.8, build afacb8b
  • Python 3.7.4
  • Django==3.0.8
  • celery==4.4.6

なお、docker-composeを利用している。

コンテナを1つに

dockerは1機能1コンテナが基本だが、そうすると、コンテナ1つのサイズがかさんでしまう。 具体的にはコンテナ1つ1つにPythonランタイムがあったりするため。(多分)

そのため1つのコンテナでsupervisordを使って定期実行を行うことにした。 本当は分けた方がいいと思うけどね。容量に余裕がないので仕方なし。

Django Appの構造

諸事情でフォルダ構造は以下のようになっている:

.  # proj_rootの親フォルダで操作している。
├── # Dockerfileやsupervisord.confなどはここに置いている。
└── proj_root
    ├── proj_app
    │   └── ...
    └── app
        ├── celery  # ここのフォルダにタスクを設置する。
        └── ...

つまりDjangoアプリのルートフォルダの親フォルダでコンソール操作を行い、celeryappの下で動かす。

定期実行するタスクを用意・動作させるまで

ここでは定期実行するタスクを用意するところまで記述する。

少し長いがここがわかってないと実装は無理だと思うので書いておく。

基本的には、celeryのDocs(First Steps with Celery — Celery 4.4.6 documentation)を参考にすればいい。

Brokerを決める

redisでいいだろう。alpineなdockerイメージもある。なのでdocker-compose.ymlは、:

version: '3'

services:
  djangoapp:
    build: .
    ...
    networks:
      - ...
      - celery_network

  # For celery
  redis:
    image: redis:alpine
    networks:
      - celery_network

  # dbなど

networks:
  celery_network:

...

といった感じになる。redisはデフォルトでポート番号6379を使用する。

また、pip install redisも必要なので注意。 celeryとredisのバンドルもあるようだ。pip install -U "celery[redis]"これは未確認。

celeryをインストール

pip install celeryこれだけ。これは開発環境用になるので、Dockerで使うときは何らかの方法でインストールする。この環境では開発環境(実際はpipenv)のをコピーするようにしているので、開発環境でインストールすればいい。

ブローカーURLをDjangoの設定に追記

定義したブローカーのURLをDjangoのsettingsに追記しておく。Djangoの設定はcelery_appインスタンスから読み込むことができる。

# localのdockerでredisを動かす場合
CELERY_BROKER_URL = 'redis://localhost:6379/0'
# composeで動かす場合(サービス名がホスト名)
CELERY_BROKER_URL = 'redis://redis:6379/0'

URLの形式は、:

redis://:password@hostname:port/db_number

このようになっている。Using Redis — Celery 4.4.6 documentationより。

celery_appとタスクを定義

tasks.pyproj_root/app/celery/に配置する。内容は、:

from celery import Celery

celery_app = Celery('tasks')
# CELERY_がプレフィックスになっている設定を読み込む。
celery_app.config_from_object('django.conf:settings', namespace='CELERY')

@celery_app.task
def add(x, y):
    return x + y

とりあえずFirst steps with Django — Celery 4.4.6 documentationのサンプルと上述のFirst Steps with Celeryのブレンドで。

DJANGO_SETTINGS_MODULEなどの環境変数の設定は別で行うので不要。開発環境ではVSCode.env読み込みが使える。dockerではdocker-compose.ymlで指定できる。

@shared_taskも今回は使わない。複数appで同一のcelery_appインスタンスを利用できる模様。

Djangoはセットアップする必要がある

タスク内でmodels.pyなどを利用するときは、Djangoのロードが終わってないよと怒られることがある(タイミングの問題かもしれない):

django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.

これを回避するには、

# tasks.py
import djagno
django.setup()

# タスク定義
...

のように、django.setup()を事前に行っておけばいい。 後から考えれば、celery_app.config_from_object()models.pyのインポート前に行っていれば問題なかったかもしれない。

celeryワーカーを動作させる

$ celery -A app.celery.tasks worker --loglevel=infoをコンソールで行えばいい。

場合によっては、--workdirオプションを指定する必要がある。(proj_root/の親フォルダで操作している場合は、--workdir proj_rootが必要。)このようなエラーが出るときなど:

Error: 
Unable to load celery application.
The module proj_app.settings was not found.

タスクの呼び出しはここでは行わないが、:

>>> from app.tasks import add
>>> add.delay(4, 4)

のように.delayをコーラブルの前に挟むだけで非同期実行できる。

celery beatコマンドで定期実行を行う。

Periodic Tasks — Celery 4.4.6 documentationを参考に。

まずは、設定から。tasks.pyファイルに記述もできるようだが、そうはしないで、 CELERY_BROKER_URLと同じようにDjangoのsettingsに追記する。:

# crontab形式で指定できる
from celery.schedules import crontab

CELERY_BEAT_SCHEDULE = {
    'add-every-5-minutes': {
        'task': 'app.celery.tasks.add',
        'schedule': crontab(minute='*/5'),
        'args': (3, 4)
    },
}

あとは、ワーカーと同じように起動しておけばいい。:

$ celery -A app.celery.tasks beat

ワーカーと同じく必要なら--workdirオプションを使う。CELERY_BEAT_SCHEDULEに従って定期実行してくれる。

長かったが、ここまでがceleryを使った定期実行の定義・設定になる。 以降はここまでで設定したことをもとにdockerの1コンテナで実行する方法を検討実装する。

走らせるコマンドを考える

以上のことから準備さえ整えれば、

  • $ celery -A app.celery.tasks worker --loglevel=info
  • $ celery -A app.celery.tasks beat

この2つのコマンドで定期実行は行えることがわかる。 あとはDjangoを走らせるためにgunicornも使うので、それ用のコマンドを合わせて、同時実行するコマンドは3つとなる。

supervisordを使う

Dockerの1つのコンテナで3つのコマンドを同時に実行するなら、supervisordを使うのがいいだろう。Supervisor: A Process Control System — Supervisor 4.2.0 documentation

Django自体もPythonなので、python:3-alpineイメージを利用して、Dockerfileの中でpip install supervisorできる。

supervisord.confは、echo_supervisord_confのものに追記していく:

[supervisord]
; フォアグラウンドで動作させる
nodaemon=true                ; start in foreground if true; default false
...

[program:django_app]
command=gunicorn --bind :8000 --log-file - proj_app.wsgi:application
directory=proj_root/
...

[program:celery_worker]
command=celery -A app.celery.tasks worker --loglevel=info
directory=proj_root/
...

[program:celery_beat]
command=celery -A app.celery.tasks beat
directory=proj_root/
...

--workdirのオプションはdirectoryで指定すればOK。フォルダ構造については、当記事の最初の方で。

Dockerfileを書く

関係のありそうなところだけ抜粋して書いておく。

FROM python:3-alpine

# ...

RUN apk update  && \
    # ...
    # pipでsupervisorをインストール
    pip install --no-cache-dir supervisor  && \
    # ...

# ... supervisord.confなどをコピー

# コピーしたconfファイルでsupervisordを起動するようにする
CMD ["supervisord", "-c", "./supervisord.conf"]

起動する

docker-compose up -dなどで起動すればOK。

django-celery-beatもある

celeryのデフォルトスケジューラを、django-celery-beatモジュールのスケジューラに変更するとDjangoadminサイトでタスクの実行状況を確認できる。参考: Periodic Tasks — Celery 4.4.6 documentation

確認する頻度はそこまで多くないので、これはとりあえず導入はしないでおく。

コンテナを分ける場合

おそらく、celeryのワーカー用のコンテナでもコードなどのリソースが必要になるので、Dockerのvolumeを作成し、そこにリソースを入れて、複数のコンテナで共有するのがいいと思う。

容量に余裕がないのでこの方法は試していない。

以上です。


広告

Bitly
Bitly
タイトルとURLをコピーしました