You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker环境运行Celery任务时出现OperationalError的排查求助

Django+Celery+Docker环境下PostgreSQL连接失败问题排查

问题背景

通过Docker容器化部署Django、Celery、Redis和PostgreSQL,重写Product模型的save方法,当对象创建时触发Celery任务向指定用户发送邮件,但任务执行时出现PostgreSQL连接失败的OperationalError,提示connection is bad: No such file or directory。


错误追踪信息

celery            | [2023-07-17 13:00:22,978: INFO/MainProcess] Task notifications.tasks.send_email_to_subs[f2b05676-2a9c-4624-af12-d62cd7f2c83e] received
django_store_api  | [17/Jul/2023 13:00:23] "POST /admin/store/product/18/change/ HTTP/1.1" 302 0
celery            | [2023-07-17 13:00:23,285: ERROR/ForkPoolWorker-2] Task notifications.tasks.send_email_to_subs[f2b05676-2a9c-4624-af12-d62cd7f2c83e] raised unexpected: OperationalError('connection is bad: No such file or directory\n\tIs the server running locally and accepting connections on that socket?')
celery            | Traceback (most recent call last):
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 289, in ensure_connection
celery            |     self.connect()
celery            |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery            |     return func(*args, **kwargs)
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 270, in connect
celery            |     self.connection = self.get_new_connection(conn_params)
celery            |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery            |     return func(*args, **kwargs)
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/postgresql/base.py", line 275, in get_new_connection
celery            |     connection = self.Database.connect(**conn_params)
celery            |   File "/usr/local/lib/python3.10/site-packages/psycopg/connection.py", line 728, in connect
celery            |     raise ex.with_traceback(None)
celery            | psycopg.OperationalError: connection is bad: No such file or directory
celery            |     Is the server running locally and accepting connections on that socket?
celery            | 
celery            | The above exception was the direct cause of the following exception:
celery            | 
celery            | Traceback (most recent call last):
celery            |   File "/usr/local/lib/python3.10/site-packages/celery/app/trace.py", line 477, in trace_task
celery            |     R = retval = fun(*args, **kwargs)
celery            |   File "/usr/local/lib/python3.10/site-packages/celery/app/trace.py", line 760, in __protected_call__
celery            |     return self.run(*args, **kwargs)
celery            |   File "/store/notifications/tasks.py", line 20, in send_email_to_subs
celery            |     for recipient in recipients:
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 398, in __iter__
celery            |     self._fetch_all()
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 1881, in _fetch_all
celery            |     self._result_cache = list(self._iterable_class(self))
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 91, in __iter__
celery            |     results = compiler.execute_sql(
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/models/sql/compiler.py", line 1560, in execute_sql
celery            |     cursor = self.connection.cursor()
celery            |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery            |     return func(*args, **kwargs)
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 330, in cursor
celery            |     return self._cursor()
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 306, in _cursor
celery            |     self.ensure_connection()
celery            |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery            |     return func(*args, **kwargs)
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 288, in ensure_connection
celery            |     with self.wrap_database_errors:
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/utils.py", line 91, in __exit__
celery            |     raise dj_exc_value.with_traceback(traceback) from exc_value
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 289, in ensure_connection
celery            |     self.connect()
celery            |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery            |     return func(*args, **kwargs)
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 270, in connect
celery            |     self.connection = self.get_new_connection(conn_params)
celery            |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery            |     return func(*args, **kwargs)
celery            |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/postgresql/base.py", line 275, in get_new_connection
celery            |     connection = self.Database.connect(**conn_params)
celery            |   File "/usr/local/lib/python3.10/site-packages/psycopg/connection.py", line 728, in connect
celery            |     raise ex.with_traceback(None)
celery            | django.db.utils.OperationalError: connection is bad: No such file or directory
celery            |     Is the server running locally and accepting connections on that socket?

docker-compose配置

services:

  # Django
  web:
    build: .
    volumes:
      - .:/store
      - static_volume:/store/static
      - media_volume:/store/media
    ports:
      - 8000:8000
    env_file:
      - web.env
    image: app:django
    container_name: django_store_api
    command: python manage.py runserver 0.0.0.0:8000

  # Redis
  redis:
    image: redis:7.0.5-alpine
    container_name: redis

  # Celery Worker
  celery-worker:
    restart: always
    build:
      context: .
    entrypoint: celery
    command: -A celery_app.app worker --loglevel=info
    volumes:
      - .:/store
    container_name: celery
    depends_on:
      - db
      - redis
      - web
    links:
      - redis


  # Flower
  flower:
    build:
      context: .
    container_name: flower
    command:  celery -A celery_app.app flower
    links:
      - redis
    depends_on:
      - redis
      - web
      - db
    ports:
      - '5555:5555'
    volumes:
      - .:/store


  # PostgreSQL
  db:
    image: postgres:14.1-alpine
    restart: always
    env_file:
      - web.env
    ports:
      - '5432:5432'
    volumes:
      - db:/var/lib/postgresql/data
volumes:
  db:
    driver: local

  static_volume:
  media_volume:

核心代码片段

celery_app.py

import os

from celery import Celery
from django.conf import settings

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')

app = Celery('core', broker_connection_retry_on_startup=True)
app.config_from_object('django.conf:settings')
app.conf.broker_url = settings.CELERY_BROKER_URL
app.autodiscover_tasks()

core/init.py

from celery_app import app as celery_store

__all__ = ('celery_store',)

settings.py中的Redis配置

CELERY_BROKER_URL = 'redis://redis:6379/0'

Product模型

class Product(models.Model):
    product_name = models.CharField(max_length=255, verbose_name='Товар')
    description = models.TextField(verbose_name='Описание')

    price = models.IntegerField(verbose_name='Цена')
    discount_percent = models.PositiveIntegerField(default=0, verbose_name='Процент скидки')

    in_stock = models.BooleanField(default=False, verbose_name='В наличии')
    amount = models.IntegerField(verbose_name='Количество')

    send_email_created = models.BooleanField(default=False)
    vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, verbose_name='Поставщик')
    category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='Категория')
    brand = models.ForeignKey(Brand, on_delete=models.CASCADE, verbose_name='Бренд')

    created = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
    updated = models.DateTimeField(auto_now=True, verbose_name='Дата обновления')

    class Meta:
        verbose_name = 'Товар'
        verbose_name_plural = 'Товары'
        ordering = ['product_name', ]

    def __str__(self):
        return f'{self.product_name} - {self.price} руб.'

    def save(self, *args, **kwargs):
        from notifications.tasks import send_email_to_subs
        self.product_name = self.product_name.capitalize()

        if self.send_email_created:
            send_email_to_subs.delay(
                discount_percent=self.discount_percent,
                price_with_discount=self.get_price_with_discount(),
                product_id=self.pk
            )

        super(Product, self).save(*args, **kwargs)

    def get_price_with_discount(self) -> int:
        return int(self.price - self.price / 100 * self.discount_percent) if self.discount_percent > 0 else self.price

Celery任务代码

@shared_task
def send_email_to_subs(discount_percent: int, product_id: int, price_with_discount: int) -> None:
    """
    Send email message to people subscribed on the listing
    """
    recipients = Customer.objects.filter(on_mail_listing=True)
    email_subject = 'New Product In Stock'
    email_body_template = 'email/new_product_notification.txt'

    email_messages = []
    for recipient in recipients:
        user_name = recipient.user_name

        email_body = render_to_string(email_body_template, {
            'user_name': user_name,
            'id': product_id,
            'discount': discount_percent,
            'price_with_discount': price_with_discount
        })

        email_message = (
            email_subject,
            email_body,
            'your-email@example.com',
            [recipient.email],
        )

        email_messages.append(email_message)
    send_mass_mail(email_messages)

问题原因及解决方案

1. Celery容器未加载数据库环境变量

Celery Worker容器未配置env_file,导致无法获取PostgreSQL的连接参数。Django和PostgreSQL都使用web.env文件存储数据库配置,Celery作为Django的异步进程,必须加载相同的环境变量才能访问数据库。

修复:在docker-compose.yml的celery-worker和flower服务中添加env_file配置:

celery-worker:
  # ... 其他配置
  env_file:
    - web.env

flower:
  # ... 其他配置
  env_file:
    - web.env

2. 数据库连接地址错误

检查web.env中的数据库主机配置,必须使用db(docker-compose中PostgreSQL服务的名称),而非localhost或127.0.0.1。Docker容器间通过服务名称通信,localhost仅指向容器自身,无法访问其他容器。

正确的数据库配置示例:

DB_HOST=db
DB_PORT=5432
DB_NAME=your_db_name
DB_USER=your_db_user
DB_PASSWORD=your_db_password
# 或者使用DATABASE_URL
DATABASE_URL=postgres://your_db_user:your_db_password@db:5432/your_db_name

3. 验证数据库连接

进入Celery容器手动测试连接,确认配置生效:

  1. 进入容器:docker exec -it celery bash
  2. 启动Python shell:python manage.py shell
  3. 执行查询:
from store.models import Customer
print(Customer.objects.count())

若报错,检查环境变量是否加载:

import os
print(os.environ.get('DB_HOST'))

4. 处理数据库启动延迟

depends_on仅保证容器启动顺序,不确保PostgreSQL服务就绪。可通过以下方式优化:

  • 保持restart: always配置,让Celery自动重试连接
  • 添加启动脚本,等待PostgreSQL就绪后再启动Celery

额外优化建议

模型save方法中延迟导入任务的方式可优化为文件顶部导入(需避免循环导入),或使用字符串路径调用任务:

# 在文件顶部导入任务
from notifications.tasks import send_email_to_subs

class Product(models.Model):
    # ... 其他字段
    def save(self, *args, **kwargs):
        self.product_name = self.product_name.capitalize()
        if self.send_email_created:
            send_email_to_subs.delay(
                discount_percent=self.discount_percent,
                price_with_discount=self.get_price_with_discount(),
                product_id=self.pk
            )
        super().save(*args, **kwargs)

内容的提问来源于stack exchange,提问作者Mihail Bury

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 01:27:01