如何在同一服务器为不同客户部署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资源。
实现要点
- 复制项目目录,分别命名为
sanet_customerA和sanet_customerB,各自修改settings.py中的数据库配置。 - 启动两个独立的Gunicorn进程:
gunicorn --bind unix:/tmp/sanet_customerA.sock sanet_customerA.wsgi:application gunicorn --bind unix:/tmp/sanet_customerB.sock sanet_customerB.wsgi:application - 在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数据库路由)
- 在
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', } } - 创建数据库路由类(比如
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 - 在
settings.py中注册路由:DATABASE_ROUTERS = ['app.routers.DomainBasedRouter']注意:异步任务(如Celery)无法直接获取请求对象,需要手动传递域名信息来指定数据库,避免数据错误。
更优方案:Docker容器化部署
如果想兼顾隔离性和维护效率,Docker容器化是个不错的选择:
- 隔离性强:每个客户的应用运行在独立容器中,进程、资源完全隔离,不会互相影响。
- 维护便捷:基于同一个基础镜像构建,公共代码更新只需要重新构建镜像,再逐个重启容器即可。
- 资源可控:可以为每个容器分配独立的CPU、内存配额,避免资源抢占。
实现要点
- 编写
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"] - 构建镜像:
docker build -t sanet-app . - 启动两个容器,通过环境变量区分数据库配置:
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 - 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
相关产品推荐
相关产品推荐

