Kubernetes编排实战:同时调度多个DDColor工作流实例
在数字档案修复与历史影像再生的实践中,一个常见的挑战是:如何在有限时间内批量处理成千上万张黑白老照片?传统人工着色方式效率低下,而单台服务器运行AI模型又很快遭遇性能瓶颈。当某省级档案馆提出“三个月内完成十万张图像修复”的需求时,我们意识到——必须构建一套高并发、可扩展、自动化的智能修复系统。
这正是容器编排技术大显身手的场景。通过将DDColor + ComfyUI封装为容器镜像,并借助 Kubernetes 实现多实例并行调度,我们成功打造了一个既能保证修复质量又能线性扩展吞吐能力的生产级解决方案。
从模型到服务:DDColor 工作流的技术底座
DDColor 并非简单的滤镜工具,它是一种基于扩散机制的深度学习着色模型,擅长在保留原始结构细节的前提下生成自然色彩分布。尤其在人脸肤色过渡、衣物纹理还原和建筑材质表现方面,其效果远超传统算法。
但在实际部署中,我们发现直接调用原始模型存在两大障碍:一是推理流程复杂,需手动编写预处理与后处理逻辑;二是参数配置门槛高,非技术人员难以驾驭。为此,我们将 DDColor 集成进ComfyUI——这个节点式可视化 AI 工作流平台成了关键转折点。
ComfyUI 的核心价值在于“所见即所得”的图形化操作模式。我们将整个修复过程拆解为标准节点链:
Load Image → Preprocess → DDColor-ddcolorize → Post-process → Save Image并通过 JSON 文件固化两条专用工作流:
-DDColor人物黑白修复.json:针对人像优化,启用更高频细节增强模块
-DDColor建筑黑白修复.json:侧重大面积平滑区域的颜色一致性控制
这些 JSON 文件不仅记录了节点连接关系,还嵌入了默认参数(如model_size=640),使得同一工作流可在不同环境中稳定复现。更重要的是,它们可以通过 RESTful API 被远程触发,为自动化铺平道路。
这里有个经验教训:早期版本未固定模型路径,导致容器启动时报错找不到
ddcolorize模块。后来我们在 Dockerfile 中明确挂载/comfyui/models/ddcolor/目录,并设置环境变量指向该路径,才彻底解决加载失败问题。
容器化封装:让工作流真正“跑起来”
要实现规模化部署,第一步就是把 ComfyUI + DDColor 打包成可移植的容器镜像。我们的构建策略遵循以下原则:
- 基础镜像轻量化:选用
nvidia/cuda:12.1-base-ubuntu20.04作为基底,避免 Alpine 因缺少 CUDA 兼容库引发运行时错误。 - 模型预置化:在构建阶段下载 DDColor 权重文件并存入镜像,减少 Pod 启动延迟(否则每次拉取模型可能耗时数分钟)。
- 入口脚本可控:自定义
entrypoint.sh,支持传入参数动态选择工作流模板或调整日志级别。
最终生成的镜像大小约 8.7GB,其中模型占 6.2GB,其余为 Python 依赖与前端资源。推送到私有 registry 后,即可供集群随时拉取。
FROM nvidia/cuda:12.1-base-ubuntu20.04 WORKDIR /comfyui COPY . . RUN pip install -r requirements.txt RUN wget -O models/ddcolor/model.safetensors https://example.com/ddcolor_v2.safetensors EXPOSE 8188 CMD ["python", "main.py", "--listen", "0.0.0.0", "--port", "8188"]值得注意的是,虽然模型可以外挂 PV 加载以节省镜像体积,但考虑到冷启动频率和网络稳定性,我们仍选择将其打入镜像——毕竟 SSD 存储成本远低于因延迟带来的用户体验下降。
Kubernetes 编排设计:不只是“多开几个副本”那么简单
很多人以为,在 K8s 上跑多个 AI 推理服务不过是写个 Deployment 设replicas: 5就完事了。实际上,真正的难点在于资源隔离、数据共享与任务分发的协同设计。
GPU 资源独占是底线
每个 DDColor 推理实例都必须独占一块 GPU,否则会出现显存争抢甚至 OOM Killer 终止进程的情况。我们在 Pod spec 中严格声明资源限制:
resources: limits: cpu: "2" memory: "8Gi" nvidia.com/gpu: 1同时配合nodeSelector: gpu: "true",确保所有 Pod 只调度到配备 NVIDIA 显卡的节点上。测试表明,A100 80GB 单卡可稳定支持model_size=960的建筑类图像推理,而 T4 则建议上限设为 640。
多Pod共享存储的陷阱与对策
输入输出目录需要被所有 Pod 共同访问,这就要求 PVC 必须支持ReadWriteMany (RWX)模式。我们采用 NFS 或 MinIO 搭建的对象存储网关来满足这一需求。
但新问题随之而来:多个 Pod 同时写入同一输出目录可能导致文件覆盖。为此,我们引入“唯一任务ID”机制,在保存结果时附加时间戳与 Pod 名称前缀:
output_path = f"/comfyui/output/{pod_name}_{int(time.time())}_{filename}"此外,通过 StatefulSet 替代 Deployment,还能让每个 Pod 拥有稳定的主机名(如ddcolor-0,ddcolor-1),便于追踪日志来源。
自动扩缩容的边界在哪里?
理论上,HPA 可根据 GPU 利用率自动增减副本数。但在实践中,我们发现单纯依赖nvidia_gpu_duty_cycle指标容易误判——因为 DDColor 属于短时高负载型任务,峰值利用率可达90%,但平均值往往低于30%。
因此,我们改用Prometheus 自定义指标 + Queue Length作为扩缩依据:
behavior: scaleDown: stabilizationWindowSeconds: 300 scaleUp: policies: - type: Pods value: 2 periodSeconds: 60 metrics: - type: External external: metric: name: ddcolor_pending_tasks target: type: Value value: 10即当待处理任务超过10个时,立即扩容2个 Pod,避免积压。这种基于业务语义的弹性策略比纯资源指标更可靠。
批量任务调度:API驱动的无人值守流水线
有了稳定的多实例服务层,下一步就是实现全自动任务提交。这里的关键角色是Kubernetes Job。
设想这样一个场景:每天凌晨,系统需处理前一天上传的所有新照片。我们编写了一个轻量级控制器脚本,其工作流程如下:
- 扫描
pvc-input中未处理的.png文件列表 - 根据文件名前缀判断类型(
person_*.png→ 人物流,building_*.png→ 建筑流) - 动态注入图像路径至对应 JSON 模板
- 调用 Service 名称
ddcolor-comfyui-service:8188/prompt提交任务
import requests import os def submit_task(image_path, workflow_json): # 注入图像路径 workflow_json["1"]["inputs"]["image"] = image_path # 提交至任意可用实例(由Service负载均衡) resp = requests.post("http://ddcolor-comfyui-service:8188/prompt", json={"prompt": workflow_json}) return resp.status_code == 200该脚本本身也容器化,并通过 CronJob 每天定时启动:
apiVersion: batch/v1 kind: CronJob metadata: name: daily-ddcolor-batch spec: schedule: "0 2 * * *" jobTemplate: spec: template: spec: containers: - name: scheduler image: registry.example.com/ddcolor-scheduler:v1 restartPolicy: OnFailure得益于 Service 的轮询负载均衡机制,请求会自动分发到最空闲的 Pod 上,无需额外的调度器介入。
生产实践中的关键考量
如何避免“小图撑爆显存”?
尽管设置了model_size推荐值,但仍有人上传 4K 分辨率的人物照,导致显存溢出。我们在入口处增加了图像尺寸检查中间件:
from PIL import Image def validate_image(path): with Image.open(path) as img: w, h = img.size if max(w, h) > 1280: raise ValueError(f"Image too large: {w}x{h}, max allowed 1280px")并在 Init Container 中执行此校验,若失败则 Pod 不进入 Running 状态,从根本上杜绝无效任务消耗资源。
日常运维怎么监控?
我们搭建了三层数字看板:
- 基础设施层:Prometheus 抓取 kube-state-metrics 和 GPU-exporter,监控节点 GPU 使用率、显存占用趋势;
- 应用层:Fluentd 收集各 Pod 的 stdout 日志,通过正则提取“任务开始/结束”事件,计算平均处理时长;
- 业务层:在任务完成后向 MySQL 写入记录(原图路径、耗时、目标工作流、是否成功),供后续统计分析。
一旦发现连续三个任务失败,Alertmanager 便会触发告警,通知值班工程师介入排查。
总结:从技术整合到工程落地
这套系统的真正价值,不在于用了多少前沿技术,而在于将分散的能力组件有机融合,形成可持续运营的生产力工具。
某媒体公司在接入该平台后,图像处理产能从每日不足百张跃升至三千以上,人力成本下降70%。更关键的是,整个流程实现了全链路可追溯——每张输出图像都能回溯到具体的模型版本、参数配置与执行节点,这对于内容审核至关重要。
未来,我们计划引入 Redis 作为任务队列缓冲层,进一步解耦生产者与消费者;同时也探索使用 Kueue 实现细粒度的任务优先级管理,让紧急任务能够插队执行。
这样的架构思路不仅适用于老照片修复,也可推广至医学影像增强、卫星图渲染、动漫上色等多个领域。其本质是一种“AI as a Service”的工程范式:把复杂的模型能力封装成标准化、可调度、易维护的服务单元,这才是 Kubernetes 在 AIGC 时代的核心使命。