Django с Celery на Digital Ocean
Цель
Я пытаюсь использовать Celery в сочетании с Django; Цель - настроить Celery на веб-приложении Django (развернутая тестовая среда) для отправки электронных писем по расписанию. Веб-приложение уже отправляет электронные письма. Конечная цель - добавить функциональность для отправки писем в выбранное пользователем время. Однако, прежде чем мы достигнем этой цели, первым шагом будет вызов функции delay()
, чтобы доказать, что Celery работает.
Использованные учебники и документация
Я новичок в Celery и изучаю следующие ресурсы:
- Первые шаги с документацией Celery-Django: https://docs.celeryq.dev/en/stable/django/first-steps-with-django.html#using-celery-with-django .
- Видео на YouTube об отправке электронной почты из Django через Celery посредством брокера Redis: https://www.youtube.com/watch?v=b-6mEAr1m-A .
- Дроплет Redis/Celery был настроен согласно следующему руководству https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-20-04 .
Я потратил несколько дней на просмотр существующих вопросов Stack Overflow по Django/Celery и попробовал ряд предложений. Однако я не нашел вопроса, конкретно описывающего этот эффект в контексте Django/Celery/Redis/Digital Ocean. Ниже описана текущая ситуация.
Что происходит в настоящее время?
На момент написания этого сообщения веб-приложение завершает работу, что говорит о том, что приложение Django не может успешно соединиться с Celery для отправки электронной почты. Обратите внимание, что в нижней части сообщения приведен результат успешного запуска Celery worker вручную из консоли приложения Django, включая список ожидаемых задач.
Используемый стек
- Python 3.11 и Django 4.1.6: Работает на платформе Digital Ocean App .
- Celery 5.2.7 и Redis 4.4.2 на Ubuntu 20.04: Работает на отдельном Digital Ocean Droplet .
Название проекта Django - "Whurthy".
Сниппеты кода настройки сельдерея
Следующие фрагменты взяты в основном из документации Celery-Django: https://docs.celeryq.dev/en/stable/django/first-steps-with-django.html#using-celery-with-django
Whurthy/celery.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Whurthy.settings')
app = Celery('Whurthy')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print(f'Request: {self.request!r}')
Whurthy/__init__.py
from .celery import app as celery_app
__all__ = ('celery_app',)
Сниппеты кода для конкретного приложения
Whurthy/settings.py
CELERY_BROKER_URL = 'redis://SNIP_FOR_PRIVACY:6379'
CELERY_RESULT_BACKEND = 'redis://SNIP_FOR_PRIVACY:6379'
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_TIME_LIMIT = 30 * 60
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = TIME_ZONE
Я заменил фактический IP на строку SNIP_FOR_PRIVACY
по очевидным причинам. Однако, если бы это было неверно, я бы не получил приведенного ниже результата.
Я также закомментировал параметры конфигурации redis bind
и requirepass
, чтобы облегчить поиск неисправностей во время разработки. Это максимально упрощает URL и исключает возможность того, что причиной проблемы является входящий IP или пароль.
'events/tasks.py'
from celery import shared_task
from django.core.mail import send_mail
@shared_task
def send_email_task():
send_mail(
'Celery Task Worked!',
'This is proof the task worked!',
'notifications@domain.com',
['my_email@domain.com'],
)
return
Из соображений конфиденциальности я изменил адреса электронной почты "to" и "from". Однако обратите внимание, что эта функция работает до добавления .delay()
в следующий фрагмент. Другими словами, приложение Django отправляет письмо до тех пор, пока я не добавлю .delay()
для вызова Celery.
events/views.py
(извлечение)
from .tasks import send_email_task
from django.shortcuts import render
def home(request):
send_email_task.delay()
return render(request, 'home.html', context)
Выше приведена только соответствующая выдержка из большего файла, чтобы показать конкретную строку кода, вызывающую функцию. Веб-приложение Django работает до тех пор, пока к вызову функции не будет добавлено delay()
, поэтому я не включил другие фрагменты файлов проекта Django.
Результат запуска celery -A Whurthy worker -l info
в консоли Digital Ocean Django App Console
В конечном итоге я хочу использовать эту команду в Docker, но пока что я выполняю вышеуказанную команду вручную. Ниже приведен вывод в консоли Django App, и он соответствует учебнику и другим примерам того, как должен выглядеть успешно сконфигурированный экземпляр Celery.
<SNIP>
-------------- celery@whurthy-staging-b8bb94b5-xp62x v5.2.7 (dawn-chorus)
--- ***** -----
-- ******* ---- Linux-4.4.0-x86_64-with-glibc2.31 2023-02-05 11:51:24
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app: Whurthy:0x7f92e54191b0
- ** ---------- .> transport: redis://SNIP_FOR_PRIVACY:6379//
- ** ---------- .> results: redis://SNIP_FOR_PRIVACY:6379/
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. Whurthy.celery.debug_task
. events.tasks.send_email_task
Это подтверждает, что дроплет Digital Ocean успешно запускает Celery worker (предполагая, что приведенные выше фрагменты кода верны) и что конфигурация Redis верна. Две задачи, перечисленные при запуске Celery, соответствуют ожиданиям. Однако я явно что-то упускаю и не могу исключить, что способ запуска дроплетов в Digital Ocean мешает работе.
Базовый тест заключается в том, что веб-приложение отправляет электронное письмо через вызов функции. Однако, как только я добавляю .delay()
запрос веб-страницы прерывается.
Я постарался воспроизвести все, что относится к делу. Я приветствую любые предложения по решению этой проблемы или конструктивную критику для улучшения этого вопроса.
Мне удалось выяснить причину проблемы. Я размещаю этот ответ, поскольку он действительно связан с Digital Ocean, с тем, как IP работают с D.O. Droplet, и с моей неопытностью.
Моя ошибка заключалась в использовании параметра ipv4 для Droplet в настройках Broker. Как только я использовал частный IP в CELERY_BROKER_URL
и CELERY_RESULT_BACKEND
, все сразу заработало!
Надеемся, что это поможет кому-то еще в будущем.