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

如何在同一服务器为不同客户部署Django项目SANET的专用版本?

Hey there! Let's walk through your two deployment options and weigh their pros and cons, plus throw in a couple of optimized approaches that might fit your scenario better.

方案分析与选择建议

方案1:复制项目独立部署

优点

  • 强隔离性:两个项目完全独立,customerB的代码修改、版本升级不会对customerA产生任何影响;调试问题时只需要关注对应项目的日志和进程,排查更简单。
  • 灵活扩展:后续如果某个客户需要更多服务器资源(比如增加Gunicorn worker数),可以单独调整,不会互相抢占资源。

缺点

  • 维护成本高:代码冗余,公共功能的bug修复或迭代需要在两个项目中重复操作,容易出现遗漏。
  • 资源占用大:两个独立的Gunicorn进程、重复的依赖包,会消耗更多服务器内存和CPU资源。

实现要点

  1. 复制项目目录,分别命名为sanet_customerA和sanet_customerB,各自修改settings.py中的数据库配置。
  2. 启动两个独立的Gunicorn进程:
    gunicorn --bind unix:/tmp/sanet_customerA.sock sanet_customerA.wsgi:application
    gunicorn --bind unix:/tmp/sanet_customerB.sock sanet_customerB.wsgi:application
    
  3. 在Nginx中配置两个server块,分别反向代理到对应的socket文件:
    server {
        listen 80;
        server_name customerA.mydomain.com;
    
        location / {
            include proxy_params;
            proxy_pass http://unix:/tmp/sanet_customerA.sock;
        }
    }
    
    server {
        listen 80;
        server_name customerB.mydomain.com;
    
        location / {
            include proxy_params;
            proxy_pass http://unix:/tmp/sanet_customerB.sock;
        }
    }
    

方案2:根据域名切换数据库配置

优点

  • 维护简单:代码唯一,公共功能的修改只需要做一次,无需重复操作。
  • 资源高效:单个Gunicorn实例(或一组worker)即可处理两个客户的请求,节省服务器资源。

缺点

  • 隔离性弱:代码层面的bug可能同时影响两个客户;调试时需要区分请求来源,增加排查难度。
  • 扩展受限:如果后续customerB需要定制化功能(比如新增模块、修改业务逻辑),该方案会导致代码耦合度变高,难以维护。
  • 潜在风险:数据库切换逻辑如果处理不当(比如线程安全问题),可能出现串库的情况,导致数据混乱。

实现要点(Django数据库路由)

  1. 在settings.py中配置多个数据库:
    DATABASES = {
        'customerA': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'sanet_customerA',
            'USER': 'db_user',
            'PASSWORD': 'db_pass',
            'HOST': 'localhost',
            'PORT': '5432',
        },
        'customerB': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'sanet_customerB',
            'USER': 'db_user',
            'PASSWORD': 'db_pass',
            'HOST': 'localhost',
            'PORT': '5432',
        }
    }
    
  2. 创建数据库路由类(比如app/routers.py):
    class DomainBasedRouter:
        def db_for_read(self, model, **hints):
            # 从请求对象中获取域名,选择对应数据库
            from django.http import HttpRequest
            request = hints.get('request') or HttpRequest()
            host = request.get_host()
            if host == 'customerA.mydomain.com':
                return 'customerA'
            elif host == 'customerB.mydomain.com':
                return 'customerB'
            return None
    
        def db_for_write(self, model, **hints):
            return self.db_for_read(model, **hints)
    
        def allow_relation(self, obj1, obj2, **hints):
            # 仅允许同一数据库内的对象建立关联
            db_list = ('customerA', 'customerB')
            if obj1._state.db in db_list and obj2._state.db in db_list:
                return obj1._state.db == obj2._state.db
            return None
    
        def allow_migrate(self, db, app_label, model_name=None, **hints):
            # 每个数据库仅迁移自身的模型
            if db in ('customerA', 'customerB'):
                return app_label == 'sanet'  # 替换为你的应用名称
            return False
    
  3. 在settings.py中注册路由:
    DATABASE_ROUTERS = ['app.routers.DomainBasedRouter']
    

    注意:异步任务(如Celery)无法直接获取请求对象,需要手动传递域名信息来指定数据库,避免数据错误。

更优方案:Docker容器化部署

如果想兼顾隔离性和维护效率,Docker容器化是个不错的选择:

  • 隔离性强:每个客户的应用运行在独立容器中,进程、资源完全隔离,不会互相影响。
  • 维护便捷:基于同一个基础镜像构建,公共代码更新只需要重新构建镜像,再逐个重启容器即可。
  • 资源可控:可以为每个容器分配独立的CPU、内存配额,避免资源抢占。

实现要点

  1. 编写Dockerfile:
    FROM python:3.10-slim
    
    WORKDIR /app
    
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    
    COPY . .
    
    CMD ["gunicorn", "--bind", "0.0.0.0:8000", "sanet.wsgi:application"]
    
  2. 构建镜像:
    docker build -t sanet-app .
    
  3. 启动两个容器,通过环境变量区分数据库配置:
    docker run -d --name sanet-customerA \
      -e DB_NAME=sanet_customerA \
      -e DB_USER=db_user \
      -e DB_PASS=db_pass \
      -p 8001:8000 \
      sanet-app
    
    docker run -d --name sanet-customerB \
      -e DB_NAME=sanet_customerB \
      -e DB_USER=db_user \
      -e DB_PASS=db_pass \
      -p 8002:8000 \
      sanet-app
    
  4. Nginx反向代理配置:
    server {
        listen 80;
        server_name customerA.mydomain.com;
    
        location / {
            include proxy_params;
            proxy_pass http://localhost:8001;
        }
    }
    
    server {
        listen 80;
        server_name customerB.mydomain.com;
    
        location / {
            include proxy_params;
            proxy_pass http://localhost:8002;
        }
    }
    

最终选择建议

  • 选方案2:如果两个客户的应用完全一致,仅数据库不同,且短期内没有定制化需求,同时你能妥善处理异步任务的数据库选择问题。
  • 选方案1或Docker化:如果customerB未来可能需要定制功能,或者你需要严格的隔离避免风险,Docker化比单纯复制项目更便于长期维护。
  • 平衡方案:用Git分支管理代码,公共代码放在主分支,客户定制功能放在各自的分支,部署时拉取对应分支。这样既减少代码冗余,又能满足定制化需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:55:12