Django с Celery на Digital Ocean

Цель

Я пытаюсь использовать Celery в сочетании с Django; Цель - настроить Celery на веб-приложении Django (развернутая тестовая среда) для отправки электронных писем по расписанию. Веб-приложение уже отправляет электронные письма. Конечная цель - добавить функциональность для отправки писем в выбранное пользователем время. Однако, прежде чем мы достигнем этой цели, первым шагом будет вызов функции delay(), чтобы доказать, что Celery работает.

Использованные учебники и документация

Я новичок в Celery и изучаю следующие ресурсы:

Я потратил несколько дней на просмотр существующих вопросов 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, все сразу заработало!

Надеемся, что это поможет кому-то еще в будущем.

Вернуться на верх