ComfyUI服务化与K8s弹性部署:从本地画图到生产级AI绘画服务

发布时间:2026/9/7 2:13:01
ComfyUI服务化与K8s弹性部署:从本地画图到生产级AI绘画服务 做AI绘画服务化我最常用的一套底座就是ComfyUI。从本地一点点调工作流到把它变成能接业务方请求的服务最后落到K8s上做弹性生产部署中间踩过的坑比想象中多得多。这套系统跑起来之后才算真正把ComfyUI从“自己画图的好用工具”变成了“别人也能稳定调用的服务”。如果你也打算做类似的事或者已经在一个团队里负责AI绘画的交付这篇应该能帮你少走不少弯路。我默认你用过ComfyUI至少看过它的节点连线知道工作流大概是怎么回事。下面不会教你怎么拖节点出图而是讲清楚“服务化”和“生产部署”这两个词背后到底意味着什么。1. 服务化改造为什么不能把ComfyUI当单机玩具用很多人第一次接触ComfyUI都会觉得这东西不就是一个本地界面嘛连好模型、跑个工作流点击Queue就能出图。但一旦需要把它开放给其他人用比如给产品同事出图、给外部用户提供绘画能力、或者接进自动化营销流程单机手动操作这套逻辑就完全不够了。1.1 单机ComfyUI和在线服务的差距先说差距在哪里。单机模式下你在UI上连节点、调参数点了Queue之后ComfyUI会按顺序执行。一旦你有多个任务ComfyUI自带一个队列只是这个队列没有优先级、没有超时控制、没有任务取消后的资源释放更谈不上并发限制。对于一个人用这无所谓但对于一个服务来说光“队列不可控”这一点就是致命的。在线服务的基本要求通常是有明确的请求接口、能支撑多个用户同时提交、每个任务有唯一标识、失败能重试、结果能追踪。ComfyUI自身其实暴露了一些HTTP接口比如/prompt提交任务、/queue查队列、/history查历史结果也有WebSocket消息推送。这些能力可以做二次开发但离“服务化”还差一层业务鉴权、参数校验、任务持久化、并发策略、模型预热等都需要在ComfyUI外面包一层自己的系统。1.2 改造前要先想清楚的核心需求开始动工之前先别急着写代码。服务化改造最怕的就是什么都想做最后做成一个四不像。我自己在初期吃过这个亏一开始想把所有节点都开放出来让调用方随便传参数结果安全问题和参数冲突把我折腾得够呛。后来收敛思路只开放业务上真正需要的几个参数比如提示词、负面提示词、图片尺寸、Seed和CFG Scale其他一律走工作流内置默认值。这里要特别说一句CFG Scale。很多人第一次接触AI绘画都问“CFG是什么意思”其实它就是提示词对生成过程的引导强度。CFG值越高图像越贴近提示词但太高容易出现色彩过饱和或者伪影CFG值太低图像跟提示词的关系又会变弱。在服务化场景里这个参数最好由业务方显式传入并且你要在服务层做范围限制比如只允许1到15之间而不是让用户随便填。否则有人传一个50出来的图大概率是废的还白白占用GPU。所以在动代码之前至少要把下面这些问题填清楚这个服务给谁用是内部设计师还是外部开发者的API开放哪些参数哪些参数是工作流内部固定绝对不让外部改的单个任务最大耗时能容忍多久超时之后是返回失败还是继续异步处理是否需要生成多张图做对比如果需要是串行还是并行高峰期并发多少路本地单张4090能撑住几路资源不够时是排队还是扩容这些问题想清楚之后服务化的技术选型才有依据。ComfyUI本身只负责“执行图”它不应该成为业务系统更像是一个执行引擎。你要做的是给它包一层适配器让外部请求能翻译成ComfyUI能理解的工作流再把它生成的结果翻译回去。2. 自定义节点把业务逻辑塞进ComfyUI服务化改造绕不开自定义节点。为什么因为ComfyUI的官方节点和社区工作流通常面向“人操作”很多信息需要手动填。但当你把它变成服务时很多输入来自上游系统比如订单号、风格标签、IP信息、用户ID这些都不是绘画参数却需要跟着任务走到最后甚至要和生成结果一起返回。2.1 自定义节点能解决什么问题自定义节点的作用是让你能把ComfyUI当成一个可编程的图执行引擎而不只是一个画图工具。你可以通过自写节点做几类事情在生成前调整提示词比如根据用户画像自动追加风格词、屏蔽敏感词。注入业务元数据比如把任务ID、回调地址写进节点输出让它们跟着工作流流传。处理中间结果比如生成后自动打水印、裁剪尺寸、计算指纹。和后端服务联动比如去Redis里查配置、调用外部模型服务做二次处理。听起来很神奇其实ComfyUI的节点就是一个Python类只要实现了INPUT_TYPES和主要处理函数再把它注册到NODE_CLASS_MAPPINGS里就行。难点不在于写一个能跑的节点而在于怎么设计节点让它在服务化体系里既灵活又不出错。2.2 我先写了个最简单的“业务参数注入”节点拿我自己最早写的节点举例。当时为了不改动每个业务工作流我写了一个叫BizMetaInject的节点它的功能特别简单接收一个业务JSON字符串解析之后分别输出若干个字符串字段。import json class BizMetaInject: classmethod def INPUT_TYPES(s): return { required: { biz_json: (STRING, {multiline: True}), } } RETURN_TYPES (STRING, STRING, STRING) RETURN_NAMES (task_id, style, biz_param) FUNCTION inject CATEGORY custom/biz def inject(self, biz_json): data json.loads(biz_json or {}) return ( str(data.get(task_id, )), str(data.get(style, )), json.dumps(data.get(biz_param, {}), ensure_asciiFalse), )这个节点本身不参与图像生成但它把外部请求的业务字段切成了标准输出后面的节点只要连线就可以读取。这样有两个好处一是工作流里所有自定义参数都能从上游统一注入二是调试的时候你在UI里填一段JSON就能模拟请求开发体验跟在服务端几乎一致。2.3 自定义节点注册和踩坑点写完类之后记得在文件底部加上注册代码NODE_CLASS_MAPPINGS { BizMetaInject: BizMetaInject, } NODE_DISPLAY_NAME_MAPPINGS { BizMetaInject: 业务元数据注入, }然后把.py文件放进ComfyUI/custom_nodes/下的某个子目录或者放在自定义节点目录里重启ComfyUI后就会在节点列表里看到。节点写好之后第一个坑就是类型匹配。ComfyUI节点之间的连线本质上是类型约束如果你自定义的输出类型和下游节点的输入类型不一致节点就接不上。所以我建议在返回类型上尽量使用原生类型比如STRING、INT、FLOAT和IMAGE。如果你要传比较复杂的数据宁可序列化成JSON字符串传也不要用List或者Dict这种自定义对象去硬接否则后续维护会很难受。第二个坑是缓存问题。ComfyUI的NodeGraph在每次执行时都会解析节点但自定义节点的加载只在启动时做一次。如果你在改节点代码必须重启ComfyUI服务光刷新页面是不够的。当时我为了图省事直接在UI上重跑工作流结果发现新代码根本没生效排查了十分钟才想起来忘了重启。后来我把自定义节点目录挂进了CI流程每次构建镜像后自动重启服务彻底不再依赖手动重启。3. 从Web界面到HTTP APIComfyUI的服务化接口设计ComfyUI自带的Web界面本质上是前端页面和它内部API的交互。想把它变成服务化系统你需要站在ComfyUI已有HTTP接口的基础上做一层自己的业务接口封装。这不是说ComfyUI有多难用而是它的定位是“本地执行工具”不是“面向外部用户的API服务”直接暴露很容易翻车。3.1 原生API能力梳理ComfyUI提供了一些实用接口服务化改造主要用这几个POST /prompt提交一个工作流并返回prompt_id。GET /queue查看当前队列状态。GET /history/{prompt_id}查询任务状态和输出结果。GET /view根据文件名获取生成的图片。WebSocket/ws实时接收任务执行进度和日志。我用得最多的是POST /prompt。它的请求体是一个JSON里面要包含工作流图数据和要覆盖的节点输入。比如你要修改某个CLIP Text Encode节点的提示词可以在请求体里通过prompt[节点id][inputs][text]的方式覆盖前提是你得知道节点ID。这个机制其实就是服务化的关键工作流不再是给人在画布上看的东西而是一个模板里面有一部分输入是固定配置另一部分由API请求动态填充。很多人刚开始会直接改工作流JSON文件其实没必要ComfyUI支持在提交时传参数覆盖更干净。3.2 工作流模板化管理最开始我直接把整个工作流JSON存在本地文件里然后用Python读取再替换字段。这种方式能用但很脆弱。只要你在ComfyUI里调整过一次画布哪怕只是挪动一个节点位置导出的JSON都会发生变化而你服务端的模板可能还是老版本。后来我养成了习惯工作流文件必须从“专门用于服务化的工作流”里导出不和日常调试的工作流混用。我建议的模板结构是这样{ workflow_name: portrait_master_v3, version: 3.2.0, nodes: { text_encode: { node_id: 6, inputs_to_override: [text] }, empty_latent: { node_id: 12, inputs_to_override: [width, height] } }, defaults: { cfg: 7.0, sampler: dpmpp_2m, scheduler: karras } }前面提到的人均可读的元信息只是配置。真正的执行是要想办法把这份模板转成ComfyUI的/prompt接口需要的格式。我自己写了一个轻量转换器读模板然后根据业务请求生成完整的节点图JSON再POST到ComfyUI。这样做的好处是模板文件和代码分离工作流的调整由绘画同学负责服务端代码不用跟着变。3.3 任务化改造和状态回传直接调用/prompt接口拿到的只是prompt_id后面你还要轮询状态。轮询方式有两种一种是定时调/history/{prompt_id}另一种是订阅WebSocket消息。轮询简单但会有延迟而且高频轮询对ComfyUI服务有压力。WebSocket更实时不过ComfyUI的WebSocket只会在任务执行事件发生时推送不会像业务系统那样给你主动确认。我的做法是中间加一层任务表把ComfyUI的prompt_id和我们自己的task_id绑定。业务方提交请求后先在服务端插入一条任务记录状态为pending然后把工作流提交给ComfyUI拿到prompt_id后更新任务表。之后由后端一个Watcher协程同时监听WebSocket和轮询/history一旦状态变为成功或者失败就更新任务表和回调。任务状态至少要有这些pending已接单未提交给ComfyUI。queued已进入ComfyUI队列等待执行。processing开始执行。succeeded生成成功结果文件已保存。failed执行失败错误信息已记录。timeout超时被服务端强制结束。有了任务状态机后续做重试、补偿、监控就都有了抓手。你甚至可以给每个任务记下“逗留在ComfyUI队列里的时间”这对后续K8s弹性伸缩非常关键因为很多性能瓶颈并不在单张GPU跑得慢而在任务排队时间太长。4. 镜像化与配置管理把ComfyUI变成可交付的产物代码层改造完之后下一件事是把ComfyUI打包成可复现、可部署的镜像。这一步很多人觉得简单不就是写个Dockerfile嘛其实坑特别多。ComfyUI本身对版本敏感Python版本、PyTorch版本、CUDA版本、自定义节点依赖任何一环不匹配就可能在启动时报failed to execute这种让人头大的错误。4.1 依赖与模型的版本锁定ComfyUI官方镜像也有但我更推荐自己在内部构建原因是可以完全控制版本。项目刚开始时我直接用了社区的一键整合包那个在本地Windows上确实好用但拿去打Docker镜像就非常痛苦因为里面自带了很多模型和插件体积动辄几十个GB构建慢不说还容易带上一些根本不需要的依赖。后来我改为“最小化基础镜像 挂载模型”的方案。基础镜像只装Python、PyTorch、ComfyUI核心代码外加工作流运行所需的少量自定义节点。模型权重不打包进镜像而是放到外部对象存储或共享存储里运行时通过挂载的方式放到容器内。这个思路跟K8s里的ConfigMap、PVC分离是同一个逻辑把稳定不变的代码和经常变的数据分开。4.2 Dockerfile关键点下面是一个简化过的Dockerfile片段你可以参考FROM nvidia/cuda:12.1.1-cudnn8-devel-ubuntu22.04 ENV PYTHONUNBUFFERED1 \ DEBIAN_FRONTENDnoninteractive RUN apt-get update apt-get install -y --no-install-recommends \ python3.10 python3-pip git curl ffmpeg libgl1 libglib2.0-0 \ rm -rf /var/lib/apt/lists/* WORKDIR /app # 先复制ComfyUI代码 RUN git clone --depth 1 --branch v0.2.3 https://github.com/comfyanonymous/ComfyUI.git /app/ComfyUI WORKDIR /app/ComfyUI # 安装Python依赖 COPY requirements.txt /app/ComfyUI/requirements.txt RUN pip install --no-cache-dir -r requirements.txt # 复制自定义节点 COPY custom_nodes/ /app/ComfyUI/custom_nodes/ # 默认启动入口 COPY docker/entrypoint.sh /entrypoint.sh RUN chmod x /entrypoint.sh CMD [/entrypoint.sh]构建镜像时有几个细节需要注意。第一基础镜像里的cudnn8和ComfyUI要求要匹配否则跑CUDNN相关算子时经常报错。第二依赖安装不要直接塞在启动脚本里否则每次启动都要等很久而且不同实例装出来的依赖可能不一致这在K8s多副本场景下非常致命。第三自定义节点目录会经常变动建议单独打一个Layer不要和ComfyUI核心代码混在一起这样改节点时不用整体重新构建。4.3 模型挂载与分层设计模型文件怎么处理我见过的方案有三种直接打包进镜像。优点是启动最简单缺点是镜像巨大、构建极慢、更新模型成本高。启动时从对象存储拉取。优点是镜像小缺点是启动慢而且如果服务重启频繁容易把带宽打满。共享存储挂载比如NFS、CephFS、JuiceFS。优点是被多个Pod共享、启动快缺点是存储的IO可能成为瓶颈要观察加载模型时的耗时。生产环境我推荐第三种但要做缓存层。ComfyUI加载模型会把权重读进内存模型文件很大时首次加载可能占用几GB内存如果每个Pod都从共享存储拉存储压力会很大。后来我在节点池上加了本地PVC容器启动前把常用模型从对象存储同步到本地盘再挂载进容器第一次冷启动慢一点之后都走本地缓存。这个操作在K8s里可以用initContainer来做一个用于拉模型一个负责启动ComfyUI主进程分工清晰。5. K8s弹性生产部署从Docker到集群调度ComfyUI镜像搞定后就该上K8s了。你可能已经在本地用Docker跑过ComfyUIDocker和K8s最大的区别在于K8s解决的是“多台机器、多个容器、如何编排和调度”的问题。单机Docker可以让你一条命令启动ComfyUI但做不到“GPU不足时自动加节点、任务少时自动缩容”这类操作。K8s正是为这个场景准备的。5.1 为什么用K8s而不是裸机跑Docker在单机上你可以用Docker Compose来管理ComfyUI和它的辅助服务。但业务一旦起来你会发现几个问题GPU资源如何在不同任务间分配如何做到多个ComfyUI容器共用同一批模型文件高峰期如何快速扩容机器故障时服务怎么恢复这些问题在K8s里都有现成的抽象。GPU可以借助设备插件直接调度HPA可以基于队列长度自动扩缩容Pod不健康时自动重启和迁移多个副本可以统一挂载同一份配置文件。当然K8s不是一个银弹它的复杂度也摆在那里但如果你真要面向生产提供AI绘画服务这一步绕不开。5.2 Deployment配置要点先看一个最精简的Deployment。假设你的ComfyUI镜像已经推到仓库模型挂在共享存储上组件的核心内容大致是这样apiVersion: apps/v1 kind: Deployment metadata: name: comfyui-worker namespace: ai-service spec: replicas: 2 selector: matchLabels: app: comfyui-worker template: metadata: labels: app: comfyui-worker spec: containers: - name: comfyui image: registry.internal/ai/comfyui:3.2.0 ports: - containerPort: 8188 resources: requests: cpu: 4 memory: 16Gi nvidia.com/gpu: 1 limits: cpu: 8 memory: 24Gi nvidia.com/gpu: 1 volumeMounts: - name: models mountPath: /app/ComfyUI/models env: - name: HF_HOME value: /app/models/huggingface volumes: - name: models persistentVolumeClaim: claimName: comfyui-models-pvc这里有个关键点resources.limits里的nvidia.com/gpu: 1必须写在limits里K8s才能识别GPU资源。GPU不支持超卖所以一个容器申请了1张GPU节点上就得真有1张空闲GPU。CPU和内存可以设request和limit不一样但GPU是严格绑定的。5.3 GPU调度与节点池生产环境里你的集群一般会有多种类型的节点。便宜的CPU节点、中配的T4节点、高配的A100节点。如果ComfyUI的任务只是普通文生图用T4或L4就够了如果要做大分辨率高清修复或者训练LoRA可能需要A100。为了不让调度器把一个A100容器调度到只有T4的节点上最稳妥的方式是给节点打标签然后用nodeSelector或者nodeAffinity限制。打个比方kubectl label node gpu-node-01 acceleratornvidia-t4然后在Deployment的PodSpec里加spec: nodeSelector: accelerator: nvidia-t4如果资源紧张还可以用污点和容忍度来做更严格的隔离。比如给T4节点加一个taintdedicated-gpu:NoSchedule只有带有对应容忍度的Pod才能调度过去防止那些不需要GPU的Pod占住节点。5.4 水平自动扩缩容HPA按队列长度伸缩K8s自带HPA默认基于CPU和内存扩缩容。但ComfyUI这类任务的瓶颈通常是GPU而且CPU负载不一定能准确反映“任务积压”。如果基于CPU扩可能出现一种情况ComfyUI进程把工作流模型加载到显存后CPU使用率并不高但任务已经排队排到天上去了。更好的策略是自定义指标比如把ComfyUI的队列长度暴露成Prometheus指标然后HPA基于这个指标扩缩容。实现上可以在你的服务化适配器里开放一个/metrics端点输出当前队列深度from prometheus_client import Gauge queue_depth Gauge(comfyui_queue_depth, Current task queue depth) queue_depth.set(current_queue_depth)然后在K8s里使用Prometheus Adapter对这个指标做自动伸缩策略。也可以简单一点HPA同时参考CPU请求量和内存并设置最小副本数和最大副本数。实际效果没有自定义指标精准但对于中小规模系统已经足够。不过要特别提醒HPA扩缩容只能解决“任务多起来后加实例”解决不了“队列已经塞了几百个任务但扩出来还需要拉模型”的冷启动延迟问题。所以我的做法是保留一个最小常驻副本比如2个专门应对突发同时在流量高峰到来之前提前扩容比如用定时HPA或者事件驱动扩容。5.5 任务队列在K8s下的分工ComfyUI本身有队列但那是单实例的队列。一个ComfyUI进程只能单机跑扩到20个Pod并不是一个队列而是20个独立的队列。所以你要在应用层自己建一个任务分发中心比如基于Redis的队列。具体分工是业务请求先打到API网关网关把任务写入Redis队列后面跑着多个ComfyUI Worker每个Worker启动时循环从Redis里取任务取到就调用ComfyUI API生成图片。这个模式下ComfyUI的/prompt只接受Worker交给它的任务而Worker到ComfyUI之间不需要再排队ComfyUI队列里最多只有一个任务。这样多个Worker可以并行跑任务队列的调度也完全可控。这一步做完你会发现K8s里的副本数其实对应“同时能跑的Worker数”而不是“ComfyUI进程数”。Worker和ComfyUI可以放在同一个Pod里也可以分开。我建议放在同一个Pod用Sidecar模式启动ComfyUI和Worker两个容器这样网络开销小任务流转最简单。6. 生产环境的稳定性与成本控制上线之后真正的麻烦才开始。AI绘画服务不像普通Web服务一个请求下来GPU要跑几十秒甚至几分钟中间任何一个环节抖动都可能导致任务失败。稳定性和成本控制是生产环境里最容易被低估的两件事。6.1 超时、重试与幂等ComfyUI一个任务跑很久网络或者客户端可能先断连。你的服务不能因为调用方断连就把生成任务取消。我的做法是API接口收到请求后立即返回task_id真正的生成结果通过回调或者结果查询来获得。调用方需要轮询或者等待回调而不是一直保持长连接。超时设置也要区分层级。第一层是请求接收超时比如5秒内必须写入任务表并返回第二层是任务执行超时比如文生图任务超过120秒就杀掉第三层是结果查询超时比如轮询超过10分钟还没结果就标记异常。重试方面幂等要做在业务层任务表里用请求唯一ID去重。同一个请求重试时不新建任务而是返回原任务ID和状态避免客户端重试一次就多扣一次预算。6.2 监控、日志与告警K8s部署下监控指标最重要的是这几个GPU利用率看显存和算力是否用满如果长期在20%以下说明任务太轻或者调度有问题。队列深度如果队列持续增加说明Worker数量不够。任务成功率看失败的占比区分是超时、显存溢出还是用户参数问题。模型加载耗时这部分最容易飘尤其是从共享存储加载大模型的时候。日志方面ComfyUI本身会输出大量执行日志但需要做好分级。默认全部打印会让日志系统很贵因此我只保留Warning以上日志并且在自定义节点里用标准logger做带任务ID的跟踪。每个任务的日志都带上task_id排查的时候只需要按task_id搜索就非常快。6.3 成本控制缩容策略与实例复用GPU是成本大头K8s下的成本控制核心就是“能缩就缩”。非工作时间可以把常驻副本缩到1个早上高峰期再提前扩容。另外一个实用技巧是给每个Worker Pod设置一个空闲退出定时器比如Worker持续空闲10分钟就自动退出然后交给K8s把Pod缩掉。这样可以在业务低谷时自然缩容不用依赖复杂指标。模型缓存也很重要。如果多个任务共用同一组基础模型最好让一个Pod连续处理多个任务而不是每来一个任务就创建一个新Pod。每次冷启动加载模型可能就要40秒任务本身才20秒冷启动成本比任务成本还高。所以生产环境我一般不让HPA缩到0至少保留一个热Pod保证请求进来不用等模型加载。7. 问题排查实录那些让我通宵的坑最后分享几个我实际踩过的坑也都是网上问得比较多的问题。尤其那句ComfyUIfailed to execute很多人一看到就慌。其实这只表示“某个节点执行失败了”真正的原因要看具体日志。7.1 最常见的ComfyUI failed to execute一旦你看到failed to execute先去日志里找更详细的异常栈。最常见的原因有三个自定义节点里import不到某个依赖此时会出现ModuleNotFoundError。模型路径不对ComfyUI找不到你指定的.safetensors文件。显存不足通常会伴随CUDA out of memory字样。有一次排查了很久最后发现是自定义节点代码里把torch.no_grad()用在了torch.inference_mode()装饰器外面导致上下文管理冲突节点在启动时正常真正执行到生成步骤才炸。所以自定义节点的函数入口最好统一加上torch.inference_mode()并且不要在装饰器外再套一层no_grad。7.2 GPU调度不上去Pod一直Pending在K8s里提交Deployment后如果Pod一直Pending不要乱猜先看事件kubectl describe pod pod-name如果事件里提示没有满足nvidia.com/gpu条件基本就是没有可用GPU节点。检查节点标签、污点和NVIDIA设备插件是否正常。还有一个容易忽略的问题如果Deployment设置了nodeSelector但节点标签写错了Pod也会Pending。另外请求的GPU数量超过节点剩余数量也会Pending。当时我犯过一个低级错误Deployment里写了resources.limits.nvidia.com/gpu: 1但Pod里同时跑了Worker和ComfyUI两个容器两个容器都申请了GPU。结果一个Pod要占2张卡节点只有2张卡只起了一个Pod就满了其他全Pending。后来把GPU申请只放在ComfyUI容器上才恢复正常。7.3 并发一高就报CUDNN错误ComfyUI并发增大后经常会出现类似CUDNN_STATUS_EXECUTION_FAILED的错误。这通常不是ComfyUI本身的问题而是多个进程同时初始化cuDNN或者显存碎片化严重。我的解决方法是控制单实例并发不让一个ComfyUI进程同时处理多个任务。ComfyUI虽然支持单实例多任务排队但每个任务都会把模型加载到显存容易爆显存。生产环境里我反而限制ComfyUI的请求队列不让外部直接向同一个ComfyUI实例提交多个任务。每个Worker每次只提交一个任务等上一个任务完成后才提交下一个。这样单实例并发为1完全避免了cuDNN层面的竞争。虽然看起来浪费了一些并行能力但稳定性大大提高。7.4 任务卡在队列里不动有一次我观察到队列里有任务但一直不执行ComfyUI的日志也没有反应。后来发现是前一个任务进程僵死把GPU显存占满了后面的任务无法执行。为什么僵死因为某个模型加载到一半外部依赖超时节点代码又没写异常捕获导致ComfyUI主循环被卡住。处理办法有两个一是给ComfyUI启动进程加看门狗脚本定期检查GPU利用率和任务执行时间超过阈值强制重启容器二是给每个任务加一个“执行超时”记录Worker判断任务执行超过预设时间后直接结束ComfyUI子进程并重建Pod。不要指望ComfyUI自己能清理异常状态它是一个执行工具不是高可用系统。最后说一个我在实战里反复验证过的小技巧写自定义节点的时候尽量让节点保持“无副作用”也就是说节点不直接改全局变量、不直接读写外部文件所有输入输出都通过参数和返回值传递。这样ComfyUI工作流在服务化后不仅好调试还更容易在K8s里做多副本并发因为多个Pod之间不会因为文件系统状态不同而产生奇怪差异。ComfyUI这套体系单机玩和做成服务完全两码事但只要把执行引擎、任务队列和资源调度三层分开它就能撑起挺大规模的业务。