背景痛点:为什么 ChatTTS 让我“谈部署色变”
第一次把 ChatTTS 搬到服务器时,我踩了三个大坑:
- 本地 Python 3.9 跑得好好的,线上系统自带 3.6,升级后又把系统组件踩崩。
pip install chattts一口气拉了 1.2 GB 依赖,其中torch-audio还偷偷编译,CPU 打满 20 分钟,SSH 直接卡掉。- 同事想并行跑两个不同分支做 A/B,结果端口、CUDA 驱动、libc 版本连环冲突,最后只能“谁加班谁先用”。
一句话:传统裸机部署 = 环境俄罗斯方块,一着不慎满盘皆输。
技术选型:为什么不是 Kubernetes
调研时我把 Docker-compose 与 K8s 放在同一张表对比:
| 维度 | Docker-compose | Kubernetes |
|---|---|---|
| 学习曲线 | 1 小时能写能跑 | 先学 Pod/Service/Ingress,再学 Helm |
| 资源开销 | 单节点,几十 MB 额外内存 | 控制面 etcd、kubelet 就 2 GB 起步 |
| 水平扩展 | 手动 scale + LB 即可 | 原生 HPA,但 GPU 节点要装 device plugin |
| 适用场景 | 开发测试、中小规模生产 | 多租户、大流量、自动弹性 |
ChatTTS 目前主要服务内部 10 来个并发调用,GPU 卡只有两张,K8s 的复杂度明显“高射炮打蚊子”。于是拍板:先用 Docker-compose 把命续上,日活真破万再上 K8s 不迟。
核心实现:30 行 Dockerfile 与 50 行 compose
1. 多阶段构建,把 3 GB 瘦成 600 MB
# 阶段 1:编译+下载,复用构建缓存 FROM python:3.11-slim as builder WORKDIR /build RUN apt-get update && apt-get install -y --no-install-recommends \ build 工具省略 COPY requirements.txt . RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt # 阶段 2:运行时,只装运行时依赖 FROM python:3.11-slim RUN groupadd -r chat && useradd -r -g chat chat WORKDIR /app COPY --from=builder /wheels /wheels RUN pip install --no-cache /wheels/* && rm -rf /wheels COPY --chown=chat:chat . . USER chat EXPOSE 8000 CMD ["uvicorn", "chattts_api:app", "--host", "0.0.0.0", "--port", "8000"]要点:
- 用
python:3.11-slim而不是默认的latest,镜像直接小 50%。 - 非 root 用户运行,后面再讲为什么救命。
- 把模型文件
.pt在构建阶段就COPY进去,避免运行时挂掉又全量下载。
2. docker-compose.yml 完整示例
version: "3.9" services: chatts: build: . image: chatts:1.2.0 restart: unless-stopped ports: - "8000:8000" environment: # 让 torch 用 GPU 0,避免抢卡 CUDA_VISIBLE_DEVICES: 0 # 预加载模型到内存,减少首次请求延迟 CHATTTS_PRELOAD: 1 volumes: # 模型缓存外挂,更新镜像时不用重新拉 700 MB - chatts-data:/app/models deploy: resources: limits: cpus: '4' memory: 8G reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s retries: 3 start_period: 120s logging: driver: "json-file" options: max-size: "50m" max-file: "3" nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - chatts networks: - front volumes: chatts-data: networks: front: driver: bridge说明:
- 用
deploy.resources把 CPU、内存、GPU 一起限死,防止 ChatTTS 把整台卡打爆。 healthcheck给 120 秒启动期,模型大,第一次加载慢,别让 Docker 误判自杀。- 日志加
max-size,曾经一晚把 100 GB 磁盘写满,血泪教训。
3. 网络与存储卷最佳实践
- 把 nginx 与业务容器拆到不同网络,前端做灰度、限流更方便。
- 模型目录单独挂 volume,更新镜像时
docker compose up --build不会把缓存清掉。 - 如果有多卡,可以复制一份 service 叫
chatts_1,改CUDA_VISIBLE_DEVICES: 1,compose 水平扩展秒完成。
性能优化:让 4090 物尽其用
资源限制
实测 ChatTTS 并发 3 路以上就会把 24 GB 显存吃光,在deploy.resources.limits里把memory: 8G指的是宿主机内存,GPU 显存靠nvidia-docker的count: 1隔离,单卡单容器最稳。日志收集
除了json-file,可以再加一个logspoutsidecar,把日志甩给 Loki, Grafana 模板搜loki-docker就有,5 分钟搞定。健康检查
建议/health接口里不仅返回 200,再带"model_loaded": true字段,这样挂模型但端口通的情况也能被剔除。
避坑指南:我替你们踩完了
端口冲突
默认 8000 容易被其他开发占位,compose 里用"${CHATTTS_PORT:-8000}:8000",上线时.env里改一次即可。权限问题
如果容器里用 root 启动,挂载的 volume 会在宿主机写一堆 0644 文件,下次用非 root 用户就跑不起来。Dockerfile 里提前USER chat,volume 加driver_opts: o=bind,type=none,让 uid/gid 对齐。生产安全
把docker.sock挂进容器等于把宿主 root 钥匙送人,绝对禁止。需要调 Docker API 就走 TCP + TLS。模型热更新
ChatTTS 官方有时发新版模型,只改 volume 里文件不重启容器,会缓存旧权重。记得在代码里监听SIGHUP重新load_state_dict(),或者干脆滚动更新容器。
扩展讨论:一条命令上 Swarm
当单节点 GPU 不够,需要两机组成小集群时,compose 文件几乎不用改:
docker swarm init docker stack deploy -c docker-compose.yml chatts唯一变化是把volumes:换成 NFS 或 GlusterFS 共享盘,确保多节点都能读到模型。K8s 同理,把deploy.resources翻译成resources.limits.nvidia.com/gpu: 1,再用 Helm 管理版本即可。真到那一步,CI/CD 用 Argo 还是 Jenkins 就随公司 merits 了。
动手实验:给 ChatTTS 加上 Prometheus 监控
留给读者的练习:
- 在 compose 里新增
prometheus服务,官方镜像prom/prometheus。 - 把 ChatTTS 的
/metrics端点暴露(用prometheus_client库 10 行代码)。 - 浏览器打开
http://宿主机IP:9090,输入nvidia_gpu_memory_used_bytes即时看显存,再配个 Grafana 模板GPU & Docker Metrics,告警阈值 90%。
做完你会回来感谢我——凌晨两点不再被“显卡怎么又炸了”叫醒。
踩坑总结完毕,祝你也能 5 分钟起一套 ChatTTS,把环境冲突、依赖地狱统统锁进容器。如果实验顺利,下篇文章聊聊“用 GitHub Actions 自动构建多架构镜像,让 M1 Mac 也能本地调试 GPU 版 ChatTTS”,再见。