抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Technology is not work, it is life.

近期在阿里云 ACK (Kubernetes) 环境中将 Dify 升级至 1.13.0 版本。表面上看只是一次常规的镜像 Tag 更新,但实际上 1.13.0 在底层架构上做了一次极其剧烈的“大换血”:工作流调度、知识库检索等重度计算任务从 api 容器剥离,全面移交给了 worker (Celery) 容器;同时引入了 SSRF Proxy 和更严格的 Sandbox 隔离机制。

这次底层重构直接导致了原来稳定运行的系统暴露出诸多“并发症”。本文完整复盘了本次升级中遇到的 5 个典型生产级问题及其终极 SRE 问题修复方案。

问题修复一:沙箱代码执行节点 DNS 解析失败

现象:
执行包含代码节点的工作流时,直接报错:
Failed to execute code... (Error: [Errno -2] Name or service not known)

根因分析:
在 1.13.0 之前,请求 Sandbox 的动作是由 api 容器发起的;升级后,真正的执行者变成了 worker。而在 K8s 内部,如果 worker 的环境变量中没有正确配置沙箱服务的路由,或者使用了容易被 CoreDNS 丢弃的短域名,就会导致寻址失败。

修复方案:
修改 worker 的 Deployment,注入 FQDN(全限定域名)级别的端点环境变量。

# 在 dify-worker Deployment 的 env 中添加/修改:
- name: CODE_EXECUTION_ENDPOINT
  value: "http://dify-ack-dify-sandbox.<your-namespace>.svc.cluster.local:8194"
- name: SSRF_SANDBOX_HOST
  value: "dify-ack-dify-sandbox.<your-namespace>.svc.cluster.local"

问题修复二:调用外部知识库抛出 Metadata 校验错误

现象:
调用自建 RAG 接口或外部知识库时报错,应用完全瘫痪:
1 validation error for SourceMetadata document_id Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]

根因分析:
这是 1.13.0 的一个已知 Regression(Issue #32585)。底层 Pydantic 升级后,SourceMetadata 模型里的 document_id 字段从 Optional[str] 变成了必填的字符串。如果底层检索没返回这个字段,Python 端拿到 None 就会直接抛错。

修复方案(K8s ConfigMap 热修复):
为了在不降级、不改镜像的前提下解决,通过 ConfigMap 挂载强行覆盖有缺陷的 Python 脚本。

  1. 提取源码文件并修改:将 document_id=item.metadata.get("document_id") 修改为带有容错机制的 document_id=item.metadata.get("document_id") or item.metadata.get("title") or ""
  2. 创建 ConfigMap:
    kubectl create configmap dify-rag-hotfix --from-file=dataset_retrieval.py -n <namespace>
    
  3. 同时挂载到 API 和 Worker 的 Deployment(切记,现在真正干活的是 Worker):

    # volumeMounts 配置
    volumeMounts:
      - name: hotfix-script
        mountPath: /app/api/core/rag/retrieval/dataset_retrieval.py
        subPath: dataset_retrieval.py
    
    # volumes 配置
    volumes:
      - name: hotfix-script
        configMap:
          name: dify-rag-hotfix
    

问题修复三:流式输出频繁断连 (InvalidChunkLength)

现象:
即使是纯内网调用大模型,客户端也经常报出流式断开错误:
("Connection broken: InvalidChunkLength(got length b'', 0 bytes read)")

根因分析:
1.13.0 之后,流式推送的链路变成了:Worker -> (Redis) -> API -> 客户端。由于 Worker 处理变慢(下文会提到),api 容器内部的 Web 服务器(Gunicorn)如果在默认的 120 秒内没有等到 Worker 传回来的数据,就会判定死锁并强杀 Worker 线程,导致客户端拿到残缺的字节流。

修复方案:
大幅拉长 api 容器的容忍度。修改 dify-api 的 Deployment:

- env:
  - name: GUNICORN_TIMEOUT
    value: "360"  # 延长至 6 分钟
  - name: SERVER_WORKER_CONNECTIONS
    value: "2000" # 拉高并发保持能力

问题修复四:工作流执行性能断崖式下降

现象:
同样的工作流,老版本 1 分钟跑完,升级 1.13.0 后暴涨到 3-4 分钟。

根因分析:
如前所述,所有的重计算逻辑都被塞给了 worker。如果升级时没有对 worker 进行重新资源分配,它会陷入严重的 CPU 节流(Throttling)和协程排队。

修复方案(垂直+横向扩容):
dify-worker 实施针对性调优。

  1. 解除 CPU 封印:Limits 至少拉高到 2000m (2核) 或更高,保证单线程运算不卡顿。
  2. 拉高协程并发数:让单 Pod 也能同时处理更多子任务。
    - name: CELERY_WORKER_CLASS
      value: "gevent"
    - name: CELERY_WORKER_CONCURRENCY
      value: "4" # 视 CPU 资源而定
    
  3. 横向扩容:将 Worker 的 Replicas 扩容至 3,分摊 Redis 队列压力。
  4. 扩容数据库连接池:状态机落盘频次增加,需在 API 和 Worker 中补充:
    - name: SQLALCHEMY_POOL_SIZE
      value: "60"
    

问题修复五:脚本节点执行成功但最终抛出 Socket 错误

现象:
工作流日志显示代码节点里的逻辑已经成功跑完,但在最后节点结束时报错:
请求失败: ('Connection broken: IncompleteRead(0 bytes read)')

根因分析:
1.13.0 引入了极严的安全机制。如果你的代码节点里去请求了内网的其他 API,底层新加入的 SSRF Proxy 可能会因为安全策略直接发送 TCP RST 强杀连接。另外,如果脚本最后 return 的对象过大,也会导致 Sandbox 在向 Worker 序列化传回数据时崩溃。

修复方案:
配置内部网络白名单,绕过 SSRF 代理的“多管闲事”;同时调大容量限制。在 apiworker 中配置:

- env:
  # 将内网网段加入白名单
  - name: SSRF_PROXY_EXCLUDE
    value: "10.0.0.0/8,192.168.0.0/16,*.cluster.local"
  - name: NO_PROXY
    value: "localhost,127.0.0.1,*.cluster.local,10.0.0.0/8"
  # 调大沙箱返回字符串上限
  - name: CODE_MAX_STRING_LENGTH
    value: "200000"

总结

Dify 1.13.0 的架构向着微服务化和深层隔离迈出了一大步,代价是短期内极具挑战的运维阵痛。核心解题思路在于:认清当前干重活的已经变成了 Worker,所有的算力倾斜、网络白名单、域名解析、热更脚本,都必须在 Worker 节点上落实。 希望这份踩坑指南能帮你少走弯路。

评论