先日、Pythonとceleryを使った非同期通信の勉強をしました。
今日は、Docker + Django + celeryでタスクの非同期処理について、勉強しようと思います。
<目次>
前提条件
- Mac 環境で動作確認
- Dockerおよびdocker-composeがインストールされていること
事前準備
以下のフォルダとファイルを用意してください。
1 2 3 4 5 6 7 8 |
mkdir docker-django-celery cd docker-django-celery mkdir app touch Dockerfile touch docker-compose.yml touch requirements.txt touch Makefile |
Django + Redis + Postgres環境の構築
Dockerを使って、Django + Redis + Postgresの環境を構築します。
尚、celeryを使うには、Redisが必要です。
まずは、docker-compose.ymlの設定です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# dcoker-compose.yml version: '3.7' services: app: build: context: . ports: - "8000:8000" volumes: - ./app:/app command: > sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000" environment: - DB_HOST=db - DB_NAME=app - DB_USER=postgres - DB_PASS=supersecretpassword depends_on: - db db: image: postgres:10-alpine environment: - POSTGRES_DB=app - POSTGRES_USER=postgres - POSTGRES_PASSWORD=supersecretpassword redis: image: redis:latest |
次にDockerfileの設定をしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# Dockerfile FROM python:3.7-alpine MAINTAINER kanagawa App Developer Ltd ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 COPY ./requirements.txt /requirements.txt RUN apk add --update --no-cache postgresql-client jpeg-dev RUN apk add --update --no-cache --virtual .tmp-build-deps \ gcc libc-dev linux-headers postgresql-dev musl-dev zlib zlib-dev RUN pip install -r /requirements.txt RUN apk del .tmp-build-deps RUN mkdir /app WORKDIR /app COPY ./app /app RUN mkdir -p /vol/web/media RUN mkdir -p /vol/web/static RUN adduser -D user RUN chown -R user:user /vol/ RUN chmod -R 755 /vol/web USER user |
Makefileには、Dockerを操作するコマンドを書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.PHONY: app test migrate pro admin django app: docker-compose run --rm app sh -c "python manage.py startapp ${app}" test: docker-compose run --rm app sh -c "pytest -l -v -s ${app} && flake8" migrate: docker-compose run --rm app sh -c "python manage.py makemigrations" docker-compose run --rm app sh -c "python manage.py migrate" pro: docker-compose run --rm app sh -c "django-admin startproject ${pro} ." admin: docker-compose run --rm app sh -c "python manage.py createsuperuser" django: docker-compose run --rm app sh -c "django-admin startproject app ." |
requirements.txtにインストールしたいモジュールを書きます。
1 2 3 4 5 |
Django django-redis celery django-celery-results psycopg2 |
ここまでで、一旦ビルドします。
1 |
docker-compose build |
Django のプロジェクトを作成します。
1 |
make django |
以下のコマンドで、Djangoを起動してみましょう。
1 |
docker-compose up |
ブラウザから「localhost:8000」にアクセスしてみましょう。

続いて、PostgresとredisをDjangoで使えるようにするために設定を変更します。今は、下記のフォルダ構成になっていると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
docker-django-celery ├── Dockerfile ├── Makefile ├── app │ ├── app │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── db.sqlite3 │ └── manage.py ├── docker-compose.yml └── requirements.txt |
settings.pyを変更します。
まずは、DBをデフォルトのSQLite3からPostgresに変更する設定です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } # } DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'app', 'USER': 'postgres', 'HOST': 'db', 'PORT': 5432, 'PASSWORD': 'supersecretpassword', } } |
次に、redisの設定をします。
1 2 3 4 5 6 7 8 9 |
CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://redis:6379/', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient' } } } |
LOCATIONのredisは、docker-compose.ymlに指定したサービス名(redis)です。こうしないと通信ができません。
一度、Ctrl+cでdockerを止めて、再度コンテナを立ち上げてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
docker-compose up app_1 | Running migrations: app_1 | Applying contenttypes.0001_initial... OK app_1 | Applying auth.0001_initial... OK app_1 | Applying admin.0001_initial... OK app_1 | Applying admin.0002_logentry_remove_auto_add... OK app_1 | Applying admin.0003_logentry_add_action_flag_choices... OK app_1 | Applying contenttypes.0002_remove_content_type_name... OK app_1 | Applying auth.0002_alter_permission_name_max_length... OK app_1 | Applying auth.0003_alter_user_email_max_length... OK app_1 | Applying auth.0004_alter_user_username_opts... OK app_1 | Applying auth.0005_alter_user_last_login_null... OK app_1 | Applying auth.0006_require_contenttypes_0002... OK app_1 | Applying auth.0007_alter_validators_add_error_messages... OK app_1 | Applying auth.0008_alter_user_username_max_length... OK app_1 | Applying auth.0009_alter_user_last_name_max_length... OK app_1 | Applying auth.0010_alter_group_name_max_length... OK app_1 | Applying auth.0011_update_proxy_permissions... OK app_1 | Applying sessions.0001_initial... OK app_1 | Watching for file changes with StatReloader app_1 | Performing system checks... |
Postgresでマイグレーションが走りましたね。
redisが使えるようになっているか、確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6142b971b535 redis:latest "docker-entrypoint.s…" 19 minutes ago Up 2 minutes 6379/tcp docker-django-celery_redis_1 d7148d535c8b docker-django-celery_app "sh -c 'python manag…" 19 minutes ago Up 2 minutes 0.0.0.0:8000->8000/tcp docker-django-celery_app_1 cd36663ab97a postgres:10-alpine "docker-entrypoint.s…" 20 minutes ago Up 2 minutes 5432/tcp docker-django-celery_db_1 # Docker上のマネジメントコンソール内に入る docker container exec -it d7148d535c8b sh -c "python manage.py shell" import datetime now = datetime.datetime.now() print(now.strftime('%Y-%m-%d %H:%M:%S')) >>2020-03-31 20:54:44 from django_redis import get_redis_connection redis = get_redis_connection("default") result = redis.set("time", now.strftime('%Y-%m-%d %H:%M:%S')) print(result) >> True # redis連携を確認 redis.get('time') >> b'2020-03-31 20:54:44' |
redisに入れたデータを取得することができました。OKですね。
Celeryの組み込み
前置き(環境構築)が長ったのですが、ようやく本題に入ります。
公式ドキュメントを参考にCeleryを組み込んでいきましょう。
公式ドキュメントによると、プロジェクト(ここでは、app/app)配下にcelery.pyを作れとのこと。
1 |
touch app/app/celery.py |
このファイルに以下の記述をします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# celery.py from __future__ import absolute_import, unicode_literals import os from celery import Celery # set the default Django settings module for the 'celery' program. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') # 変更1 app.settingsに変更 app = Celery('app') # 変更2 appに変更 # Using a string here means the worker doesn't have to serialize # the configuration object to child processes. # - namespace='CELERY' means all celery-related configuration keys # should have a `CELERY_` prefix. app.config_from_object('django.conf:settings', namespace='CELERY') # Load task modules from all registered Django app configs. app.autodiscover_tasks() @app.task(bind=True) def debug_task(self): print('Request: {0!r}'.format(self.request)) |
ほとんど、公式からの流用です。変更1,2の場所だけ、自分のプロジェクトに合わせて変更してます。
それから、app/app/__init__.pyに以下の追記をしましょう。
1 2 3 4 5 6 7 |
from __future__ import absolute_import, unicode_literals # This will make sure the app is always imported when # Django starts so that shared_task will use this app. from .celery import app as celery_app __all__ = ('celery_app',) |
Django起動時に、@shared_task
でデコレートされたtaskを読み込むことを保証するそうです。
は、プロジェクト全体で、tasks.pyを発見してくれる便利ツールだそうです。tasks.pyにしないといけないルールなのは、少し不便そうですね。app.autodiscover_tasks()
アプリケーションを作成しましょう。
1 2 |
make app app=demoapp touch app/demoapp/tasks.py |
settings.pyにこのdemoappを登録します。また、celeryがredisを使用する設定を入れておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # my app 'demoapp', # 3rd-party 'django_celery_results', ] # celery用設定 CELERY_BROKER_URL = 'redis://redis:6379/' # brokerをredisへ CELERY_RESULT_BACKEND = 'django-db' # ジョブ結果をdbへ |
先ほど作成したtasks.pyに以下の実装をします。
1 2 3 4 5 6 7 8 9 10 11 |
# demoapp/tasks.py # Create your tasks here from __future__ import absolute_import, unicode_literals from celery import shared_task @shared_task def hello(): time.sleep(10) print('hello') |
次にview.pyにこのタスクを使う設定を追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# demoapp/views.py from django.http import HttpResponse from django_celery_results.models import TaskResult from .tasks import hello def home(request): for _ in range(100): task_id = hello.delay() # delayでバックグランド実行 print(task_id) r = list(TaskResult.objects.all().values_list("result", flat=True)) print(r) return HttpResponse("<html><body>Hello</body></html>") |
appプロジェクト配下のurls.pyを以下のように変更しましょう。
1 2 3 4 5 6 7 8 9 |
# app/urls.py from django.contrib import admin from django.urls import path from demoapp.views import home urlpatterns = [ path('admin/', admin.site.urls), path('', home), ] |
そして、docker-compose.ymlに以下のceleryコンテナを追加してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# dcoker-compose.yml version: '3.7' services: app: build: context: . ports: - "8000:8000" volumes: - ./app:/app command: > sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000" # celery -A app worker -l info && environment: - DB_HOST=db - DB_NAME=app - DB_USER=postgres - DB_PASS=supersecretpassword depends_on: - db - redis # 忘れてたので追加 db: image: postgres:10-alpine environment: - POSTGRES_DB=app - POSTGRES_USER=postgres - POSTGRES_PASSWORD=supersecretpassword redis: image: redis:latest celery: build: context: . command: celery -A app worker -l info volumes: - ./app:/app depends_on: - db - redis |
celeryコンテナを追加しました。
は、celeryのワーカープロセスを開始します。celery -A app worker -l info
コンテナを立ち上げて、「localhost:8000」にアクセスし、コンソールを確認してみましょう。
1 2 |
app_1 | [31/Mar/2020 21:45:43] "GET / HTTP/1.1" 200 31 app_1 | a68e2e73-9322-472b-bbba-423041ec0625 |
task_idが返却されているので、celeryにタスクが渡されていることはわかります。
celeryコンテナを別途作成したのは、Djangoの「python manage.py runserver」とceleryの「celery -A app worker -l info」を別プロセスで扱いたかったからです。
コンテナを分けたのにも関わらず、taskが実行できていますよね?
まだ詳しく調べられた訳ではないのですが、以下のようになっているのだと思います。
- Django上でタスク実行(views.py)
- Redisにタスク登録
- celeryプロセスがRedisからタスクを上取り、実行
- postgresに実行後のタスクを登録
間違ってたら、ご指摘ください^^; celeryは、初めてなもので。。
管理ユーザーを作成しましょう。Makefileに作成用のコマンドを既に入れています。
1 |
make admin |
「http://localhost:8000/admin/django_celery_results/taskresult/」にアクセスすると、実行されたタスクが確認できます。
まとめ
celeryを使うと結構簡単に非同期処理が実装できて良いですね。
コメントを残す
コメントを投稿するにはログインしてください。