DockerでDjango + Celeryを使った非同期メール送信&受信の環境構築

はじめまして。 株式会社アドグローブ ソリューション事業部エンジニアの藪内です。
今回は、DockerでDjango + Celeryを使った非同期メールを送信&受信する際の環境構築をご紹介します。

1. はじめに

Django と Celery を使用した非同期処理の導入は、時間のかかるタスク(例: メール送信など)を非同期で処理できるため非常に便利です。このブログでは、Docker 上で非同期メール送信を実現し、MailHog を使ってローカルでメールを受信する環境を構築します。さらに、Django のカスタムコマンドを使用してタスクをトリガーする方法も解説します。

2. Django プロジェクトの準備

Pythonのインストール

まず Python がインストールされているか確認します。インストールされていない場合、以下の手順でインストールしてください。

  • Windows

    1. 公式サイトから最新のPythonをダウンロードしてインストールします。
    2. 「Add Python to PATH」にチェックを入れることを忘れずに。
  • Mac/Linux

    • MacではHomebrewを使ってインストールできます: bash brew install python
    • Linuxでは次のコマンドを使います: bash sudo apt-get install python3

仮想環境の作成

なぜ仮想環境を使うのか?

Python の仮想環境(venv)は、プロジェクトごとに依存パッケージを分離して管理するためのツールです。これにより、以下のようなメリットがあります。

  1. パッケージの競合を防ぐ
    複数のプロジェクトが異なるバージョンのライブラリやパッケージを必要とする場合があります。仮想環境を使うことで、各プロジェクトごとに異なるバージョンのパッケージをインストールし、システム全体に影響を与えずに使用できます。

  2. プロジェクトごとの依存関係の管理が容易
    仮想環境では、プロジェクトに必要なライブラリだけをインストールするため、不要なパッケージがプロジェクトに含まれる心配がありません。また、プロジェクトを他の開発者と共有する際に、requirements.txt に書かれたライブラリを簡単にインストールでき、環境を統一できます。

  3. システムの安全性向上
    グローバル環境に依存パッケージを直接インストールするのではなく、仮想環境内で管理することで、システムの設定や他のアプリケーションに影響を与えることなくプロジェクトを運用できます。

仮想環境の作成方法

  1. 仮想環境の作成: 仮想環境はプロジェクトのルートディレクトリ内に作成します。以下のコマンドを実行して仮想環境を作成します。
   python -m venv myenv

myenv は仮想環境の名前です。任意の名前に変更しても問題ありません。

  1. 仮想環境の有効化: 仮想環境を作成した後、以下のコマンドを実行して有効化します。

    • Windows myenv\Scripts\activate
    • Mac/Linux source myenv/bin/activate

    仮想環境が有効化された状態では、ターミナルのプロンプトが (myenv) のように変化します。 仮想環境を終了するには、以下のコマンドを使用します。

   deactivate

Djangoのインストール

仮想環境を有効化した状態で、次のコマンドを実行して Django、Celery、Redis をインストールします。

## 必要なパッケージをinstall
pip install django celery redis

##requirements.txt ファイルを生成
pip freeze > requirements.txt

その後、Django プロジェクトを新規作成します。

django-admin startproject myproject
cd myproject

次に、settings.py に以下の Celery 設定を追加します。 また今回はコマンドからメール送信をテストするため、myprojectを INSTALLED_APPS に追加しておきます。

# settings.py
INSTALLED_APPS = [
・
・
・
    'django.contrib.staticfiles',
    'myproject',
]

CELERY_BROKER_URL = 'redis://redis:6379/0'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'

3. 作成されるファイルのツリー

以下は、このプロジェクトで作成されるファイルとディレクトリ構造です。

myproject/
│
├── Dockerfile
├── compose.yml
├── manage.py
├── myproject/
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── tasks.py
└── myapp/
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations/
    │   └── __init__.py
    └── management/
        └── commands/
            └── send_test_email.py
  • Dockerfile: Django アプリケーションを Docker コンテナで動作させるための設定。
  • compose.yml: Docker Compose の設定ファイル。Django、Celery、Redis、MailHogのサービスを定義。
  • tasks.py: Celery タスク(非同期メール送信)を定義。
  • send_test_email.py: Django のカスタムコマンドで、Celery タスクを実行するためのスクリプト。

4. Docker Composeで環境を構築

compose.yml ファイルを作成し、Django アプリケーション、Redis、Celery、MailHogを含むDocker環境を定義します。

services:
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/app
    ports:
      - "8000:8000"
    depends_on:
      - redis
  
  redis:
    image: "redis:alpine"
  
  celery:
    build: .
    command: celery -A myproject worker --loglevel=info
    volumes:
      - .:/app
    depends_on:
      - redis

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"

Dockerfile も作成します。

FROM python:3.9

WORKDIR /app

COPY . /app

RUN pip install -r requirements.txt

5. Celeryタスクの実装

Django プロジェクトにおける Celery の設定を行い、非同期でメールを送信するタスクを定義します。

celery.py の作成

# myproject/celery.py
import os
from celery import Celery

# DJANGO_SETTINGS_MODULE を明示的に設定
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

app = Celery('myproject')

# Djangoの設定を読み込む
app.config_from_object('django.conf:settings', namespace='CELERY')

# Djangoアプリからタスクを自動で発見する
app.autodiscover_tasks()

__init__.py の設定

# myproject/__init__.py
from .celery import app as celery_app

__all__ = ('celery_app',)

メール送信タスクの定義

非同期でメールを送信するタスクを定義します。

# tasks.py

from celery import shared_task
from django.core.mail import send_mail

@shared_task
def send_email_task(to_email):
    send_mail(
        'Test Subject',
        'Here is the message.',
        'from@example.com',
        [to_email],
        fail_silently=False,
    )

6. Djangoのカスタムコマンドでタスクをトリガー

次に、Django のカスタムコマンドを作成し、メール送信タスクをトリガーします。

カスタムコマンドの作成

management/commands/send_test_email.py というファイルを作成します。

# management/commands/send_test_email.py
from django.core.management.base import BaseCommand
from myproject.tasks import send_email_task

class Command(BaseCommand):
    help = 'Send a test email using Celery'

    def handle(self, *args, **kwargs):
        to_email = 'test@example.com'
        send_email_task.delay(to_email)
        self.stdout.write(self.style.SUCCESS(f'Email sent to {to_email}'))

このコマンドにより、次のコマンドでメール送信タスクを実行できます。 現段階ではDockerが起動していないためメール配送はされません。

python manage.py send_test_email

7. MailHogでのメール受信

MailHog は、ローカル開発環境でメールを受信し簡単に確認できるツールです。compose.yml ですでに設定されていますが、Django のメール設定を以下のように変更します。

# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'mailhog'
EMAIL_PORT = 1025

8. コンテナ起動

ワーカーが起動すると添付画像のようにCeleryがready状態となります。

docker-compose up --build

先ほど作成したカスタムコマンドを実行してメール送信タスクをトリガーします。

## 別のターミナルからwebコンテナにbashで入りdangoのコマンドを実行する。
docker-compose exec web bash
python manage.py send_test_email

9. ローカルでのメール受信テスト

MailHog の Web インターフェースにアクセスし、受信したメールを確認します。 無事MailHogのUI上にメールが送信されました。

http://localhost:8025

これで送信されたメールが受診していることを確認できました。

10. トラブルシューティング

よくある問題と対処法

  • Redis が接続できない場合: compose.ymldepends_on を正しく設定し、Redis サービスが起動しているか確認します。
  • メールが届かない場合: MailHog のポート設定が正しいか、EMAIL_HOSTEMAIL_PORT の設定を再確認してください。
  • コンテナが起動しない: 利用しているポートがすでに利用されている状態なを再確認してください。

11. まとめ

Docker 環境で Django + Celery を使った非同期メール送信の環境を構築し、カスタムコマンドでタスクをトリガーしました。MailHog を利用することで、ローカルでのメール送信と受信を簡単に確認でき、このセットアップでコマンドベースでメールが送信されていることや、受診状況を確認することができます。
こちらの環境を応用することで一括のメール送信することや、Celeryにはリトライのオプションを設定することもでき運用環境でも利用することができます。

この記事をご覧になったみなさんも、活かせそうな場面があればぜひ非同期処理を活用してみてください。 最後までお読みいただきありがとうございました。


アドグローブでは、さまざまなポジションで一緒に働く仲間を募集しています!
詳細については下記からご確認ください。みなさまからのご応募お待ちしております。

採用情報