<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
            <title type="text">Deng Yongjie's blog</title>
    <updated>2026-07-05T22:26:50+08:00</updated>
        <id>https://blog.yongjie.top</id>
        <link rel="alternate" type="text/html" href="https://blog.yongjie.top" />
        <link rel="self" type="application/atom+xml" href="https://blog.yongjie.top/atom.xml" />
    <rights>Copyright © 2026, Deng Yongjie's blog</rights>
    <generator uri="https://halo.run/" version="1.5.3">Halo</generator>
            <entry>
                <title><![CDATA[第二章-从文档到可信答案：RAG 知识点化入库、证据门与反馈闭环]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-er-zhang---cong-wen-dang-dao-ke-xin-da-an-rag-zhi-shi-di-hua-ru-ku--zheng-ju-men-yu-fan-kui-bi-huan" />
                <id>tag:https://blog.yongjie.top,2026-07-05:di-er-zhang---cong-wen-dang-dao-ke-xin-da-an-rag-zhi-shi-di-hua-ru-ku--zheng-ju-men-yu-fan-kui-bi-huan</id>
                <published>2026-07-05T17:27:12+08:00</published>
                <updated>2026-07-05T22:26:50+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%E4%BB%8E%E6%96%87%E6%A1%A3%E5%88%B0%E5%8F%AF%E4%BF%A1%E7%AD%94%E6%A1%88%EF%BC%9Arag-%E7%9F%A5%E8%AF%86%E7%82%B9%E5%8C%96%E5%85%A5%E5%BA%93%E3%80%81%E8%AF%81%E6%8D%AE%E9%97%A8%E4%B8%8E%E5%8F%8D%E9%A6%88%E9%97%AD%E7%8E%AF" tabindex="-1">从文档到可信答案：RAG 知识点化入库、证据门与反馈闭环</h1><p>上一篇讲的是企业 RAG 智能体的生产分层：NewAPI 做入口，Adapter 做协议和策略，RAGFlow 做知识库，Model Router 和 vLLM 做推理。</p><p>但架构分清以后，真正难的问题才出现：把文档上传到知识库，并不等于系统拥有了可信知识。</p><p>我对 RAG 的一个基本判断是：模型幻觉往往不是生成阶段才发生的，它更早发生在文档治理、知识边界、召回和证据筛选阶段。前面没有把证据做干净，后面再靠 prompt 要求“不要胡说”，只能算心理安慰。</p><p><img src="/upload/2026/07/rag-knowledge-agent-lifecycle.svg" alt="rag-knowledge-agent-lifecycle" /></p><h2 id="1.-%E6%96%87%E6%A1%A3%E4%B8%8D%E6%98%AF%E7%9F%A5%E8%AF%86%EF%BC%8Cchunk-%E4%B9%9F%E4%B8%8D%E6%98%AF%E7%9F%A5%E8%AF%86" tabindex="-1">1. 文档不是知识，chunk 也不是知识</h2><p>很多 RAG 项目会把“文档上传成功”和“知识库可用”画等号。这个等号很危险。</p><p>文档是资料容器。chunk 是检索载体。真正能支撑回答的，是有边界、有来源、有问法映射、有负例约束的知识点。</p><p>可以这样理解：</p><table><thead><tr><th>对象</th><th>定位</th><th>风险</th></tr></thead><tbody><tr><td>Document</td><td>原始资料容器，保留文件、章节、表格和附件</td><td>原文很长，里面可能混杂多个主题</td></tr><tr><td>Knowledge Point</td><td>最小业务语义单元，能独立回答一组相近问题</td><td>如果没有来源和边界，容易变成二次创作</td></tr><tr><td>Chunk</td><td>embedding 和检索使用的文本片段</td><td>固定长度切分可能切断语义</td></tr></tbody></table><p>一个制度文档可能包含适用范围、申请流程、审批规则、例外情况和处罚条款。如果直接按固定长度切 chunk，模型可能只拿到“申请前 30 日应…”这种半句话。</p><p>真正可靠的做法，是先按结构和语义拆知识点，再让 chunk 服务检索性能。</p><h2 id="2.-%E5%85%A5%E5%BA%93%E9%98%B6%E6%AE%B5%E8%A6%81%E6%AF%94%E9%97%AE%E7%AD%94%E9%98%B6%E6%AE%B5%E6%9B%B4%E4%B8%A5%E6%A0%BC" tabindex="-1">2. 入库阶段要比问答阶段更严格</h2><p>RAG 的文档 embedding 发生在入库阶段，不是用户每次提问后才把文档重新向量化。</p><p>入库主链路可以交给 RAGFlow 托管：</p><pre><code class="language-text">上传文档  -&gt; 文档解析  -&gt; chunk  -&gt; embedding  -&gt; 写入 doc engine</code></pre><p>但这不代表外部治理层可以消失。企业级场景至少要在入库前后补齐这些控制：</p><table><thead><tr><th>环节</th><th>目的</th></tr></thead><tbody><tr><td>文档准入</td><td>判断文件是否可读、是否扫描件、是否需要 OCR、是否有敏感信息</td></tr><tr><td>文档类型识别</td><td>合同、手册、FAQ、表格、API 文档不能用同一种切分策略</td></tr><tr><td>tenant / KB / ACL</td><td>入库时就要确定谁能检索到这些内容</td></tr><tr><td>source span</td><td>每个 chunk 必须能回到原文页码、标题路径、段落或表格位置</td></tr><tr><td>embedding 版本</td><td>模型、维度、chunk profile 一旦变化，就要重建索引</td></tr><tr><td>入库质量门</td><td>检查召回、引用、负例和权限过滤是否通过</td></tr></tbody></table><p>我不建议首版把解析、chunk、embedding、写库、检索全部拆成自研微服务。更稳的方式是“RAGFlow 托管主链路，外部治理层做准入、策略、质量门和回归评测”。</p><h2 id="3.-knowledge-point-%E6%98%AF%E7%9F%A5%E8%AF%86%E5%BA%93%E7%9A%84%E9%80%BB%E8%BE%91%E9%AA%A8%E6%9E%B6" tabindex="-1">3. Knowledge Point 是知识库的逻辑骨架</h2><p>Knowledge Point 可以理解为：</p><pre><code class="language-text">可独立回答一个或一组相近问题的最小业务语义单元。</code></pre><p>它不是替代 Document，也不是替代 chunk。更准确地说，Document 保留原文真实性，chunk 服务检索效率，Knowledge Point 负责把业务语义、问法边界和评测样本组织起来。</p><p><img src="/upload/2026/07/rag-knowledge-point-model.svg" alt="rag-knowledge-point-model" /></p><p>它至少应该包含这些信息：</p><table><thead><tr><th>字段</th><th>作用</th></tr></thead><tbody><tr><td>canonical_question</td><td>这个知识点最标准的问题表达</td></tr><tr><td>answer_fact</td><td>可被引用的事实答案</td></tr><tr><td>source_span</td><td>原文位置，支撑引用和回溯</td></tr><tr><td>aliases</td><td>缩写、别名、同义表达</td></tr><tr><td>query_variants</td><td>用户可能使用的不同问法</td></tr><tr><td>negative_queries</td><td>看起来像但不该命中的问题</td></tr><tr><td>evidence_policy</td><td>最小证据数量、冲突处理和拒答条件</td></tr><tr><td>eval_samples</td><td>后续回归测试使用的问题样本</td></tr></tbody></table><p>这套结构的价值，不是为了让知识库“更复杂”，而是为了让它可测试。</p><p>如果一个知识点没有标准问法、没有负例、没有引用位置，那么召回结果好坏只能靠体感。体感一旦进入生产，就很难治理。</p><h2 id="4.-%E7%94%A8%E6%88%B7%E4%B8%8D%E4%BC%9A%E6%8C%89%E6%96%87%E6%A1%A3%E5%8E%9F%E5%8F%A5%E6%8F%90%E9%97%AE" tabindex="-1">4. 用户不会按文档原句提问</h2><p>企业知识库里最常见的错位，是文档写得很正式，用户问得很口语。</p><p>文档里可能写：</p><pre><code class="language-text">合同有效期届满前 30 日，由经办部门发起续签评估。</code></pre><p>用户会问：</p><pre><code class="language-text">合同过期了咋办？协议到期还能不能继续用？到期合同是不是自动终止？续签是谁来提？</code></pre><p>如果只用用户原句做一次向量检索，很容易召回不稳定。</p><p>更好的检索入口是“原始问题 + 归一化问题 + canonical question + 高置信问法变体”。但扩展必须克制，不能无限改写。</p><p>建议默认最多 4 条查询：</p><pre><code class="language-text">1. raw query2. normalized query3. matched canonical question4. highest-confidence variant</code></pre><p>RAG 的难点不是把问题改写得越多越好，而是只增加能提高召回、不会引入噪声的表达。</p><h2 id="5.-hard-negatives-%E6%98%AF%E9%98%B2%E8%AF%AF%E5%8F%AC%E5%9B%9E%E7%9A%84%E5%85%B3%E9%94%AE" tabindex="-1">5. hard negatives 是防误召回的关键</h2><p>召回只看“命中正确答案”是不够的，还要看“相似但错误的问题不会命中”。</p><p>比如“合同怎么续签”和“合同怎么签署”很接近，但业务含义不同。前者问生命周期续约，后者问签署流程。如果系统总是把它们混在一起，答案看起来很像，实际却可能误导用户。</p><p>这就是 hard negative 的价值。</p><table><thead><tr><th>正向问题</th><th>hard negative</th></tr></thead><tbody><tr><td>合同到期后如何处理</td><td>合同首次签署流程是什么</td></tr><tr><td>API Key 如何轮换</td><td>API Key 如何申请</td></tr><tr><td>PVE 虚机如何重启</td><td>PVE 集群如何新建节点</td></tr><tr><td>知识库如何发布</td><td>知识库如何删除</td></tr></tbody></table><p>hard negatives 不只是评测数据，它会反过来塑造知识点边界。一个知识点越能说清“我不回答什么”，它越稳定。</p><h2 id="6.-evidence-gate-%E6%AF%94-prompt-%E6%9B%B4%E5%8F%AF%E9%9D%A0" tabindex="-1">6. Evidence Gate 比 prompt 更可靠</h2><p>生产级 RAG 不应该把所有候选 chunk 都塞给 LLM，然后祈祷它自己判断。</p><p>Evidence Gate 要在生成前做决策：</p><p><img src="/upload/2026/07/rag-evidence-gate-decision.svg" alt="rag-evidence-gate-decision" /></p><table><thead><tr><th>判断项</th><th>可能动作</th></tr></thead><tbody><tr><td>没有命中内部证据</td><td>拒答、澄清或在授权后联网</td></tr><tr><td>top1 分数低</td><td>降级回答或要求补充问题</td></tr><tr><td>证据冲突</td><td>标出冲突，要求用户确认</td></tr><tr><td>用户无权限</td><td>拒绝返回相关内容</td></tr><tr><td>问题需要最新信息</td><td>进入显式联网授权流程</td></tr><tr><td>敏感查询</td><td>禁止外发，要求脱敏</td></tr></tbody></table><p>这个环节决定系统是否有“不会答”的能力。</p><p>很多人喜欢追求“什么都能回答”的智能体，但企业场景里，敢于拒答比自信胡答更值钱。</p><h2 id="7.-llm-%E6%8E%A5%E6%94%B6%E7%9A%84%E6%98%AF%E6%96%87%E6%9C%AC%E8%AF%81%E6%8D%AE%EF%BC%8C%E4%B8%8D%E6%98%AF%E5%90%91%E9%87%8F" tabindex="-1">7. LLM 接收的是文本证据，不是向量</h2><p>一个容易混淆的问题是：RAG 召回重排后，发给 vLLM 的到底是什么？</p><p>答案是 final messages，也就是文本化后的证据上下文、用户问题、系统规则和引用编号。</p><p>流程是：</p><pre><code class="language-text">RAGFlow / Adapter 找到证据  -&gt; 组织 source id、chunk text、metadata、source span  -&gt; 打包进 final messages  -&gt; vLLM tokenizer 编码成 input_ids  -&gt; 模型生成 token  -&gt; Adapter 汇总答案、sources、usage、audit</code></pre><p>vLLM 不知道 Elasticsearch 里有哪些 chunk，也不知道哪个 evidence score 更高。它只看到 prompt。</p><p>因此，答案可信度不能只看模型文本。可信来源应该来自结构化的 <code>rag.sources</code>、retrieval trace 和 citation check。</p><h2 id="8.-%E5%8F%8D%E9%A6%88%E9%97%AD%E7%8E%AF%E4%B8%8D%E6%98%AF%E7%82%B9%E8%B5%9E%E6%8C%89%E9%92%AE" tabindex="-1">8. 反馈闭环不是点赞按钮</h2><p>“有用 / 无用”的反馈按钮只是入口，不是闭环。</p><p>真正的闭环应该是：</p><pre><code class="language-text">bad answer  -&gt; 保存问题、命中证据、答案、引用和用户反馈  -&gt; 判断是缺知识、召回错、重排错、引用错还是 prompt 错  -&gt; 修正文档、Knowledge Point、query variants 或 hard negatives  -&gt; 重新入库或重建索引  -&gt; 用 golden questions 回归测试  -&gt; 通过质量门后发布</code></pre><p>这也是为什么我认为 RAG 项目必须保留“知识库工程”的角色。RAG 不是一次性导入文档后就自动变聪明，它需要持续整理、评测和修复。</p><p>好的知识库像一套可维护的产品文档，不像一个无人管理的文件夹。</p><h2 id="9.-%E9%A6%96%E7%89%88-poc-%E5%BA%94%E8%AF%A5%E9%AA%8C%E6%94%B6%E4%BB%80%E4%B9%88" tabindex="-1">9. 首版 PoC 应该验收什么</h2><p>这个项目没有把首版目标放在高并发压测上，而是放在生命周期闭环上。我认为这个选择是正确的。</p><p>PoC 最应该验收这些能力：</p><table><thead><tr><th>验收项</th><th>标准</th></tr></thead><tbody><tr><td>文档入库</td><td>Markdown、PDF、表格能解析，chunk 能回到原文</td></tr><tr><td>检索质量</td><td>正确文档能召回，误召回可量化</td></tr><tr><td>回答质量</td><td>答案基于证据，不用常识补企业制度</td></tr><tr><td>引用能力</td><td>返回 sources、document、chunk、source span</td></tr><tr><td>拒答能力</td><td>证据不足时拒答或澄清</td></tr><tr><td>权限边界</td><td>tenant、KB、user、ACL 在检索前生效</td></tr><tr><td>联网边界</td><td>Internal 禁网，Web 显式授权后才联网</td></tr><tr><td>工具调用</td><td>Codex tools / tool_calls 不能被字符串化</td></tr><tr><td>反馈修复</td><td>坏答案可转成 golden question 并回归验证</td></tr></tbody></table><p>这些验收项比单纯 QPS 更早决定项目能不能上线。</p><p>性能可以后置优化，可信度如果一开始没有框架，后面补会很痛。</p><h2 id="10.-%E5%B0%8F%E7%BB%93" tabindex="-1">10. 小结</h2><p>RAG 的质量不是模型单点能力，而是知识生命周期能力。</p><p>文档要能准入，知识点要有边界，chunk 要能追溯，问法要能覆盖，负例要能约束，证据要能过门，答案要能引用，坏答案要能回到知识库修复。</p><p>我喜欢把这件事概括成一句话：RAG 不是让模型替文档管理还债，而是把文档管理变成可检索、可验证、可修复的工程系统。</p><p>下一篇会继续讨论产品化边界：企业 RAG 智能体卖的到底是什么，为什么 Internal/Web 要分开，以及为什么 Token 工厂应该作为并行产品线，而不是塞进 RAGFlow。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第一章-深度拆解企业级RAG智能体架构]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-yi-zhang---shen-du-chai-jie-qi-ye-ji-rag-zhi-neng-ti-jia-gou" />
                <id>tag:https://blog.yongjie.top,2026-07-05:di-yi-zhang---shen-du-chai-jie-qi-ye-ji-rag-zhi-neng-ti-jia-gou</id>
                <published>2026-07-05T17:12:14+08:00</published>
                <updated>2026-07-05T22:26:42+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%E4%BC%81%E4%B8%9A%E7%BA%A7-rag-%E6%99%BA%E8%83%BD%E4%BD%93%E6%9E%B6%E6%9E%84%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%8A%8A-newapi%E3%80%81adapter%E3%80%81ragflow-%E5%92%8C-vllm-%E5%88%86%E5%BC%80" tabindex="-1">企业级 RAG 智能体架构：为什么要把 NewAPI、Adapter、RAGFlow 和 vLLM 分开</h1><p>很多 RAG 项目失败，不是因为向量库选错了，也不是因为模型不够强，而是从第一天就把边界混在了一起：入口想做知识库，RAG 层想做计费，模型服务想决定是否联网，最后每个组件都变成半个控制面。</p><p>我越来越相信一件事：复杂系统的第一步不是加功能，而是让责任边界可解释。边界清楚以后，质量、性能、计费和商业化才有生长空间。</p><p>这篇文章结合一个 Model Agnostic RAG 知识库智能体项目，整理我认为更适合生产落地的架构：<code>NewAPI -&gt; Adapter / RAG LB -&gt; RAGFlow -&gt; Model Router -&gt; vLLM</code>。</p><p><img src="/upload/2026/07/rag-production-layered-architecture.svg" alt="rag-production-layered-architecture" /></p><h2 id="1.-%E5%85%88%E8%AF%B4%E7%BB%93%E8%AE%BA%EF%BC%9A%E4%B8%8D%E8%A6%81%E9%A6%96%E7%89%88%E8%87%AA%E7%A0%94%E5%AE%8C%E6%95%B4-rag-%E5%B9%B3%E5%8F%B0" tabindex="-1">1. 先说结论：不要首版自研完整 RAG 平台</h2><p>这个项目的核心判断很克制：首版不自研完整 RAG 平台，而是采用 RAGFlow 承担文档解析、知识库、chunk、检索、rerank、引用和 Agent/workflow。</p><p>自研保留在一个很薄的 Adapter / RAG LB 上。它负责协议、策略、授权、审计、usage、sources 和 OpenAI-compatible 兼容，而不是重新造一套解析、入库、检索和编排系统。</p><p>这不是“技术上做不了自研”，而是生产节奏上的选择。首版最重要的不是证明我们能写一个向量检索服务，而是证明企业知识库智能体能完整闭环：入库、检索、回答、引用、拒答、联网授权、工具调用、日志审计和反馈修复。</p><h2 id="2.-newapi-%E5%8F%AA%E5%81%9A%E5%85%A5%E5%8F%A3%EF%BC%8C%E4%B8%8D%E5%81%9A%E7%9F%A5%E8%AF%86%E5%BA%93" tabindex="-1">2. NewAPI 只做入口，不做知识库</h2><p>NewAPI 在这套架构里是唯一外部入口。它负责 API Key、渠道、模型名、额度、计费、日志和 Web 管理。</p><p>但它不应该直接连接 RAGFlow，也不应该理解 chunk、rerank、source span 或联网检索策略。NewAPI 是网关和商业入口，不是 RAG 控制面。</p><p>这层边界很重要。企业客户最终关心的是谁调用了、用了多少 token、走了哪个模型、是否扣费、能不能按租户和模型分账。这些是入口层的职责。</p><p>如果把知识库逻辑塞进 NewAPI，后续模型渠道、普通模型 API、RAG 模型 API、Token 工厂和计费报表会纠缠在一起。短期看省了一层，长期看会失去演进弹性。</p><h2 id="3.-adapter-%E5%BE%88%E8%96%84%EF%BC%8C%E4%BD%86%E4%B8%8D%E8%83%BD%E7%9C%81" tabindex="-1">3. Adapter 很薄，但不能省</h2><p>Adapter / RAG LB 是 NewAPI 和 RAGFlow 之间的薄协议层。它看起来不起眼，但生产上非常关键。</p><p>如果只用一句话描述它：Adapter 是“外部 API 契约”和“内部 RAG 策略”之间的缓冲垫。外部继续使用稳定的 OpenAI-compatible 语义，内部则可以按租户、知识库、模型别名和授权策略去选择不同的 RAGFlow app。</p><p><img src="/upload/2026/07/rag-adapter-contract-flow.svg" alt="rag-adapter-contract-flow" /></p><p>它至少要稳定处理这些事情：</p><table><thead><tr><th>能力</th><th>为什么必须在 Adapter</th></tr></thead><tbody><tr><td>OpenAI-compatible</td><td>NewAPI、SDK、业务系统和 Codex CLI 需要稳定 <code>/v1/*</code> 入口</td></tr><tr><td>Responses API</td><td>Codex 等工具调用场景不能只支持普通 chat</td></tr><tr><td>tool calls 保真</td><td><code>tools/tool_choice/tool_calls</code> 不能被 RAGFlow Agent 字符串化</td></tr><tr><td>模型别名</td><td><code>*-RAG-Internal</code>、<code>*-RAG-Web</code> 要映射到不同 app/dataset/profile</td></tr><tr><td>usage 归一</td><td>NewAPI 需要统一记录 prompt、completion、total tokens</td></tr><tr><td>sources 归一</td><td>内部来源、网页来源、retrieval trace 需要统一响应格式</td></tr><tr><td>显式联网授权</td><td>是否联网不应由模型自由决定</td></tr><tr><td>审计字段</td><td>tenant、user、KB、web authorization、tool call 都要可追踪</td></tr></tbody></table><p>我的理解是，Adapter 不是为了“增加一层架构感”，而是为了隔离变化。RAGFlow 版本会变，Codex CLI 协议会变，NewAPI 上游格式也可能变。Adapter 是把这些变化挡在生产边界外的缓冲层。</p><h2 id="4.-ragflow-%E8%B4%9F%E8%B4%A3%E6%89%BE%E8%AF%81%E6%8D%AE%EF%BC%8C%E4%B8%8D%E8%B4%9F%E8%B4%A3%E7%94%9F%E6%88%90-token" tabindex="-1">4. RAGFlow 负责找证据，不负责生成 token</h2><p>RAGFlow 的价值在于 RAG 本身：文档上传、解析、知识库管理、chunk、embedding、检索、rerank、citation 和 Agent/workflow。</p><p>它不负责 API Key，不负责统一计费，不负责普通模型售卖，也不负责 GPU 调度。</p><p>企业 RAG 智能体卖的是“基于企业知识的可信回答”。可信来自三件事：证据、引用、边界。</p><p>所以 RAGFlow 应该把精力放在证据质量上：文档解析是否稳定，chunk 是否可追溯，检索是否能召回正确片段，rerank 是否能把真正相关的证据排上来，引用能不能回到原文。</p><h2 id="5.-vllm-%E5%8F%AA%E5%81%9A%E6%8E%A8%E7%90%86%EF%BC%8C%E4%B8%8D%E4%BF%9D%E5%AD%98%E7%9F%A5%E8%AF%86" tabindex="-1">5. vLLM 只做推理，不保存知识</h2><p>vLLM 在这里的角色也很清楚：加载模型权重，接收 final messages，生成 token。</p><p>它不会访问 Elasticsearch，不会查知识库，不会决定是否联网，也不会把聊天内容写入长期记忆。RAGFlow / Adapter 把证据组织进 prompt，vLLM 只基于上下文生成答案。</p><p>这句话听起来简单，但很多架构讨论会在这里混淆：模型“知道”某个答案，不等于企业知识库允许它回答；模型“可以”调用工具，不等于它应该自己决定是否把内部问题发到公网搜索。</p><p>在企业场景里，权限和证据优先于模型自由发挥。</p><h2 id="6.-%E5%AD%98%E5%82%A8%E9%80%89%E5%9E%8B%EF%BC%9A%E9%A6%96%E7%89%88%E5%85%88%E7%94%A8-ragflow-%E9%BB%98%E8%AE%A4-doc-engine" tabindex="-1">6. 存储选型：首版先用 RAGFlow 默认 doc engine</h2><p>这个项目的存储路线也很克制：首版使用 RAGFlow 默认的 Elasticsearch doc engine，Infinity 作为后续备选，Milvus 不作为首版前置条件。</p><p>这不代表 Milvus 不适合生产。Milvus 在千万级、亿级向量、高并发 ANN 和统一企业向量基础设施场景里很有价值。</p><p>但首版 PoC 的主要问题不是“哪种向量库性能最高”，而是“这个智能体生命周期是否可信”。如果一开始就把精力投入到 RAGFlow backend 二次开发，项目很容易偏离文档解析、引用、拒答和权限这些真正影响用户信任的环节。</p><p>更合理的顺序是：</p><table><thead><tr><th>阶段</th><th>重点</th></tr></thead><tbody><tr><td>Phase 1</td><td>RAGFlow + Elasticsearch 跑通生命周期 PoC</td></tr><tr><td>Phase 2</td><td>补齐权限、审计、备份、监控和部署固化</td></tr><tr><td>Phase 3</td><td>做知识点化、评测集、hard negatives 和 groundedness</td></tr><tr><td>Phase 4</td><td>如果 ES 不满足，再验收 Infinity 或 Milvus backend</td></tr></tbody></table><p>生产架构不是把所有好东西第一天堆上去，而是让每一层复杂度都有明确收益。</p><h2 id="7.-internal-rag-%E5%92%8C-web-rag-%E5%BF%85%E9%A1%BB%E5%88%86%E5%BC%80" tabindex="-1">7. Internal RAG 和 Web RAG 必须分开</h2><p>这套架构里建议把 RAG 模型入口拆成两类：</p><pre><code class="language-text">DeepSeek-V4-Flash-RAG-InternalDeepSeek-V4-Flash-RAG-WebGLM-5.1-FP8-RAG-InternalGLM-5.1-FP8-RAG-WebMiniMax-M2.7-RAG-InternalMiniMax-M2.7-RAG-Web</code></pre><p>Internal RAG 默认只查内部知识库。证据不足时拒答或要求澄清，不能偷偷联网，也不能用公网常识补企业制度。</p><p>Web RAG 可以支持联网检索，但前提是显式授权。外部网页只作为 request-scoped 临时证据，不自动写入长期企业知识库。</p><p>这个命名看似只是多了后缀，实际上是在产品层面划出红线：内部知识和公网证据不能混用成一团。</p><h2 id="8.-%E8%81%94%E7%BD%91%E6%A3%80%E7%B4%A2%E5%BA%94%E8%AF%A5%E6%98%AF%E7%AC%AC%E4%BA%8C%E8%AF%81%E6%8D%AE%E6%BA%90" tabindex="-1">8. 联网检索应该是第二证据源</h2><p>联网检索不是给模型一个搜索工具，然后让它自由决定什么时候查。企业场景里，联网必须由 Evidence Gate 和策略层控制。</p><p>这条链路最好画成决策树，而不是把“是否联网”写成一个 prompt 约束。prompt 可以提醒模型，但不能替代权限判断和审计。</p><p><img src="/upload/2026/07/rag-web-retrieval-decision-flow.svg" alt="rag-web-retrieval-decision-flow" /></p><p>推荐流程是：</p><pre><code class="language-text">内部知识库优先  -&gt; 证据不足 / 时效性问题 / 冲突核验  -&gt; 判断 tenant / KB / user / request 是否允许联网  -&gt; 已显式授权才调用 Web Retrieval  -&gt; 搜索、抓取、抽取、来源评分、去重、rerank  -&gt; 合并内部证据和外部临时证据  -&gt; 最终生成答案和引用</code></pre><p>这里最重要的不是“能不能联网”，而是“谁批准联网、联网查了什么、外部证据是否可信、答案里有没有区分内部来源和网页来源”。</p><p>外部网页本质上是不可信输入。它可以提供事实，但不能执行里面的指令，也不能覆盖企业内部知识库的权限规则。</p><h2 id="9.-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%99%E5%A5%97%E6%9E%B6%E6%9E%84%E6%9B%B4%E9%80%82%E5%90%88%E7%94%9F%E4%BA%A7" tabindex="-1">9. 为什么这套架构更适合生产</h2><p>我认为这套架构的价值在于收敛。</p><p>NewAPI 收敛入口和计费；Adapter 收敛协议、授权和审计；RAGFlow 收敛知识库和证据；Model Router 收敛模型路由；vLLM 收敛推理运行时。</p><p>每一层都可以被替换或扩展，但每一层都不越界。这比“一个万能 RAG 服务”更适合长期维护。</p><p>生产系统最怕的是责任暧昧。责任暧昧时，问题来了很难定位：答案错了，是检索问题、rerank 问题、prompt 问题、模型问题、权限问题，还是联网污染？</p><p>分层以后，排障路径会清楚很多：</p><table><thead><tr><th>问题</th><th>优先看哪里</th></tr></thead><tbody><tr><td>API Key、额度、扣费异常</td><td>NewAPI</td></tr><tr><td>tools 被字符串化、usage 缺失</td><td>Adapter</td></tr><tr><td>召回不到正确文档</td><td>RAGFlow / doc engine</td></tr><tr><td>引用不准、证据不足还回答</td><td>Evidence Gate / citation check</td></tr><tr><td>模型延迟高、流式异常</td><td>Model Router / vLLM</td></tr><tr><td>联网泄露风险</td><td>Web Retrieval 策略和审计</td></tr></tbody></table><p>这就是工程边界的意义：它不是为了画图好看，而是为了让系统在出错时还能被理解。</p><h2 id="10.-%E5%B0%8F%E7%BB%93" tabindex="-1">10. 小结</h2><p>企业级 RAG 智能体不是“向量库 + 大模型”的简单组合。它是一个由入口、协议、知识库、证据治理、模型推理、联网策略和审计计费共同组成的系统。</p><p>首版不自研完整 RAG，是为了把精力集中在生命周期闭环；保留 Adapter，是为了守住 OpenAI-compatible、工具调用、usage、sources 和显式授权；使用 RAGFlow，是为了优先获得稳定的文档解析和引用能力；让 vLLM 专注推理，是为了避免模型服务承担不该承担的知识库职责。</p><p>一句话总结：RAG 的核心不是让模型“多知道一点”，而是让系统能说明“答案依据是什么、谁有权看到、什么时候该拒答、哪些证据可以被追溯”。</p><p>下一篇会继续沿着这套架构往下走，重点讨论知识库如何从“上传一堆文档”变成“可评测、可修复、可拒答的知识生命周期”。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[用Claude+Codex开发的AIOps边缘自动化运维闭环实践：PVE、Ceph、网络设备、裸金属与平台审计]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/yong-claudecodex-kai-fa-de-aiops-bian-yuan-zi-dong-hua-yun-wei-bi-huan-shi-jian-pveceph-wang-luo-she-bao--luo-jin-shu-yu-ping-tai-shen-ji" />
                <id>tag:https://blog.yongjie.top,2026-07-04:yong-claudecodex-kai-fa-de-aiops-bian-yuan-zi-dong-hua-yun-wei-bi-huan-shi-jian-pveceph-wang-luo-she-bao--luo-jin-shu-yu-ping-tai-shen-ji</id>
                <published>2026-07-04T11:35:11+08:00</published>
                <updated>2026-07-05T22:26:31+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="aiops-%E8%BE%B9%E7%BC%98%E8%BF%90%E7%BB%B4%E9%97%AD%E7%8E%AF%E5%AE%9E%E8%B7%B5%EF%BC%9Apve%E3%80%81ceph%E3%80%81%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87%E3%80%81%E8%A3%B8%E9%87%91%E5%B1%9E%E4%B8%8E%E5%B9%B3%E5%8F%B0%E5%AE%A1%E8%AE%A1" tabindex="-1">AIOps 边缘运维闭环实践：PVE、Ceph、网络设备、裸金属与平台审计</h1><p>边缘机房的运维平台，真正困难的不是“能不能连上设备”，而是“连上以后如何安全地执行、如何记录、如何失败隔离、如何给用户一个可信的结果”。</p><p>这个 AIOps 项目已经把 PVE、Ceph、网络设备、裸金属、公网 IP、SNAT 映射和平台审计放进了同一个闭环。但闭环设计来自真实运维知识：哪些命令只能只读、哪些操作必须二次确认、哪些凭据不能落盘、哪些产物不能直接给 AI。</p><p>本文主要讲功能和实现方法，不展开源码。重点不是证明“AI 能写代码”，而是说明：没有足够的运维和安全知识储备，很难写出让 AI 稳定落地这类平台的提示词。</p><p><img src="/upload/2026/07/aiops-edge-ops-closed-loop.svg" alt="aiops-edge-ops-closed-loop" /></p><h2 id="1.-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BE%B9%E7%BC%98%E8%BF%90%E7%BB%B4%E8%A6%81%E5%81%9A%E6%88%90%E9%97%AD%E7%8E%AF" tabindex="-1">1. 为什么边缘运维要做成闭环</h2><p>边缘机房通常具备几个特点：机房多、网络边界复杂、内网地址可能重复、资源类型不统一、现场凭据敏感、故障排查窗口短。</p><p>如果用脚本方式处理，短期很快，长期会遇到几个问题：谁执行的、执行了什么、失败在哪一步、产物在哪里、能不能重跑、有没有越权、是否泄露凭据。</p><p>AIOps 的设计目标，是把这些问题变成平台能力：</p><pre><code class="language-text">发起任务 -&gt; 权限校验 -&gt; 中心编排 -&gt; edge-agent 执行 -&gt; 结果回传 -&gt; 产物归档 -&gt; AI 分析 -&gt; 平台审计</code></pre><p>闭环的价值不只是自动化，而是可追溯、可治理、可交付。</p><p>这也是 AI 开发的第一道门槛。你必须先知道闭环里有哪些环节，才能要求 AI 去实现它们。只说“做一个自动化巡检”，AI 不会天然知道哪些场景需要失败隔离、哪些产物需要脱敏、哪些操作不能开放。</p><h2 id="2.-%E8%BF%99%E5%A5%97%E9%97%AD%E7%8E%AF%E8%83%8C%E5%90%8E%E7%9A%84%E5%B7%A5%E7%A8%8B%E9%87%8F" tabindex="-1">2. 这套闭环背后的工程量</h2><p>边缘运维闭环不是几个脚本页面的组合，而是一个跨中心平台和边缘执行器的工程体系。按非空行粗略统计，整个 AIOps 项目约 2,957 个文件、30.7 万行工程内容，仓库文件清单约 3,100+ 个文件。</p><p>和边缘闭环强相关的部分主要集中在三块：后端约 2,067 个文件，负责模型、权限、审计、任务、产物和编排；前端控制台约 702 个文件，负责多资源域页面、表格、弹窗、导入导出和操作台；edge-agent 约 153 个文件，Python 非空行约 3.5 万行，负责真正进入边缘网络做受控执行。</p><p>从结构上看，后端有 69 个 Controller、112 个 Service 实现、143 个 DAO 接口；前端 <code>src/pages</code> 下约 258 个页面文件；edge-agent <code>src</code> 下约 82 个 Python 文件。这些数字背后，是大量“看不见但必须有”的生产细节：参数校验、状态机、异常分支、权限拒绝、审计脱敏、产物归档、版本兼容和边缘失败隔离。</p><p>所以，用  Claude+Codex  开发这套平台，并不是把一段脚本交给 AI 改成网页。它更像是让 AI 在明确架构和提示词约束下，持续完成一个多模块、多边界、多发布物的大工程。</p><h2 id="3.-pve-%E8%99%9A%E6%9C%BA%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F" tabindex="-1">3. PVE 虚机生命周期</h2><p>PVE 虚机操作是高风险能力。平台没有把它设计成“输入 IP 后直接重启”，而是拆成解析、核对、确认、执行、审计几个步骤。</p><p>典型流程是：</p><table><thead><tr><th>步骤</th><th>说明</th></tr></thead><tbody><tr><td>选择机房</td><td>确定边缘网络和 Agent 范围</td></tr><tr><td>输入虚机 IP</td><td>支持单 IP 或多 IP 查询</td></tr><tr><td>解析候选虚机</td><td>从 PVE API、Guest Agent、配置和缓存中定位</td></tr><tr><td>用户核对</td><td>展示 VMID、节点、虚机名、IP、当前状态</td></tr><tr><td>二次确认</td><td>避免误操作高风险资源</td></tr><tr><td>执行动作</td><td>启动、停止、重置等受控操作</td></tr><tr><td>写入审计</td><td>保存操作者、目标、结果、失败原因和耗时</td></tr></tbody></table><p>这里的“重置”被定义为“先停止再启动”，而不是直接执行 PVE 硬 reset。定义清楚动作语义，可以减少生产误解。</p><p>这类定义不是 AI 自动想出来的，而是运维经验转成提示词后的结果。越是高风险动作，越不能让 AI 自由解释业务语义。</p><h2 id="4.-ceph-%E5%B7%A1%E6%A3%80%E4%B8%8E-ai-%E5%88%86%E6%9E%90" tabindex="-1">4. Ceph 巡检与 AI 分析</h2><p>Ceph 巡检不是复用 PVE 巡检接口，也不是把 Ceph 当成 PVE 的一个字段。它被设计成独立资源模型和独立巡检类型。</p><p>平台支持两类 Ceph：</p><table><thead><tr><th>类型</th><th>场景</th></tr></thead><tbody><tr><td>PVE 超融合 Ceph</td><td>Ceph 运行在 PVE 节点上，和 PVE 集群有关联</td></tr><tr><td>独立 Ceph 集群</td><td>独立存储集群，按机房和 Agent 单独登记</td></tr></tbody></table><p>中心平台负责选择范围、解密凭据、调用 edge-agent、保存结果、生成 Excel/图表、触发 AI 分析。edge-agent 只执行白名单只读命令，并返回结构化 JSON。</p><p>这条边界能避免两个风险。</p><p>第一，中心平台不直接 SSH 到边缘节点，减少网络和凭据暴露面。</p><p>第二，edge-agent 不保存长期凭据和最终产物，不会变成边缘侧小数据库。</p><h2 id="5.-%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87%E5%B7%A1%E6%A3%80" tabindex="-1">5. 网络设备巡检</h2><p>网络设备巡检的关键不是“能跑命令”，而是命令必须白名单化、产物必须受控下载、私钥必须脱敏。</p><p>平台支持网络设备台账、测试连接、自测巡检、批量测试、批量禁用、导入导出和 SSH Key 批量更新。</p><p>SSH 认证分为两种模式：</p><table><thead><tr><th>模式</th><th>说明</th></tr></thead><tbody><tr><td>path</td><td>保存 edge-agent 容器内只读私钥路径</td></tr><tr><td>content</td><td>前端提交 PEM 私钥，后端加密保存，接口和审计不回显</td></tr></tbody></table><p>巡检产物下载也有边界。edge-agent 只允许下载网络巡检输出目录下的合法产物，拒绝 <code>_work</code> 目录、临时私钥、路径穿越和输出根目录。</p><p>这类安全细节决定了网络巡检能不能用于生产。如果 artifact 里混入私钥或原始敏感输出，自动化越强，风险越大。</p><p>因此，AI 生成网络巡检能力前，提示词必须先锁死边界：只读命令、白名单 runtime、私钥脱敏、artifact 下载范围、失败设备隔离、ZIP 结构、AI 输入截断。少一个约束，都可能变成生产隐患。</p><h2 id="6.-%E8%A3%B8%E9%87%91%E5%B1%9E%E4%B8%8E%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E5%90%8C%E6%AD%A5" tabindex="-1">6. 裸金属与配置中心同步</h2><p>裸金属管理不是简单维护服务器列表，它还涉及节点状态、BMC、业务 IP、客户归属、批量分配、批量回收、电源操作和外部配置源同步。</p><p>平台里裸金属能力采用几个原则：</p><table><thead><tr><th>原则</th><th>作用</th></tr></thead><tbody><tr><td>托管源启用后阻断手工入口</td><td>避免 Gitea、钉钉在线文档和人工导入互相覆盖</td></tr><tr><td>批量操作先整批预校验</td><td>避免部分成功造成状态不一致</td></tr><tr><td>电源操作写入审计</td><td>保留高风险操作追溯链</td></tr><tr><td>长任务后端异步执行</td><td>避免大 Excel 或批量同步卡死前端请求</td></tr><tr><td>空业务 IP 不视为错误</td><td>支持故障、未交付或未安装系统节点</td></tr></tbody></table><p>这些设计都来自生产场景。AI 开发时，如果只按“增删改查”实现，会漏掉大量状态机和运维边界。</p><h2 id="7.-%E5%85%AC%E7%BD%91-ip-%E4%B8%8E-snat-%E6%98%A0%E5%B0%84" tabindex="-1">7. 公网 IP 与 SNAT 映射</h2><p>公网 IP 管理被放在边缘运维下，但它不是 edge-agent runtime 能力。公网 IP 是中心平台的权威数据，不需要 Agent 到网络设备上执行变更。</p><p>平台支持公网 IPv4 台账、CIDR 拆分、父子段关系、单 IP 分配、回收、删除、批量导入、运营商识别和审计。</p><p>SNAT 映射自动生成则是面向网络侧交付的能力：根据容器 IP 清单和公网 SNAT IP/CIDR 清单，轮询生成分组映射，保存不可变历史版本，并支持 Excel、TXT 下载和历史版本对比。</p><p>这类功能的重点是“生成可信交付物”，不是自动改防火墙。第一阶段不自动下发网络配置，是一个很重要的边界。</p><h2 id="8.-%E5%B9%B3%E5%8F%B0%E6%93%8D%E4%BD%9C%E5%AE%A1%E8%AE%A1" tabindex="-1">8. 平台操作审计</h2><p>AIOps 把操作审计从“系统用户审计”升级成“平台操作审计”。审计域不只覆盖系统管理，也覆盖边缘运维等高风险模块。</p><p><img src="/upload/2026/07/aiops-operation-audit-sanitized.png" alt="aiops-operation-audit-sanitized" /></p><p>审计页支持按审计域、模块、动作、状态、关键字和时间范围查询。表格保留操作者、目标用户、资源、状态、开始时间、耗时和详情入口。</p><p>生产审计要注意两件事。</p><p>第一，审计不是简单保存 HTTP 请求体。请求体里可能有密码、Token、私钥、OIDC 信息或验证码。平台只记录语义化快照和脱敏摘要。</p><p>第二，审计写入要覆盖成功和失败。失败记录同样重要，因为它能证明越权、参数错误、状态不允许等情况确实被平台拦截。</p><h2 id="9.-ai-%E5%88%86%E6%9E%90%E5%A6%82%E4%BD%95%E6%8E%A5%E5%85%A5" tabindex="-1">9. AI 分析如何接入</h2><p>巡检的 AI 分析不应该把所有原始输出无脑塞进 prompt。这样既容易超长，也容易泄露敏感信息。</p><p>更合理的做法是使用任务摘要、设备状态、错误摘要、结构化 metrics 和经过截断的 resultJson。对网络巡检，默认不读取完整 raw 原始命令输出，也不直接下载 ZIP 给 AI。</p><p>这让 AI 分析更像“结构化诊断助手”，而不是“原始日志吞吐机”。它可以给出健康风险、异常项解释和排障建议，但不能替代白名单边界和人工确认。</p><p>换句话说，AI 在这里不是被放到生产系统里“自由判断”，而是被放到一个受控输入、受控输出、受控权限的诊断位置。这个边界设计比调用模型本身更重要。</p><h2 id="10.-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%99%E5%A5%97%E6%96%B9%E6%B3%95%E9%80%82%E5%90%88-claude%2Bcodex" tabindex="-1">10. 为什么这套方法适合  Claude+Codex</h2><p>边缘运维闭环涉及很多跨层改动：前端页面、后端编排、权限 SQL、审计模型、Agent runtime、产物下载、AI prompt、发布文档和浏览器验证。</p><p>Claude+Codex 适合这种工作，因为它可以在同一个上下文里连续推进多个文件和多个模块。但要让它稳定产出，必须给出强约束：</p><pre><code class="language-text">先读设计文档。先确认目标和非目标。危险操作必须后端校验。凭据不得回显。SQL 必须幂等。edge-agent 变更必须同步版本和 capabilities。每轮变更必须写回开发流和发布说明。</code></pre><p>这些约束把 AI 的产出从“能运行”推向“能上线”。</p><p>这里的含金量不在于“AI 帮我写了代码”，而在于你能把边缘运维的生产经验拆成清晰任务：哪些由中心平台负责，哪些由 edge-agent 负责，哪些必须进入审计，哪些必须拒绝，哪些必须在发布文档里提醒。</p><p>如果没有这些知识储备，即使模型能力很强，也只能生成一个看起来能跑、但很难上线的工具。</p><h2 id="11.-%E5%B0%8F%E7%BB%93" tabindex="-1">11. 小结</h2><p>AIOps 边缘运维闭环的核心，不是把所有设备都接进来，而是把每一次查询、巡检、操作和分析都放进受控链路。</p><p>PVE 负责虚机生命周期，Ceph 负责存储巡检，网络设备负责只读网络巡检，裸金属负责节点和电源状态，公网 IP/SNAT 负责网络资源交付，平台审计负责把所有高风险动作串起来。</p><p>中心平台保存权威状态，edge-agent 做无状态受控执行，AI 分析基于脱敏结构化数据工作。这个组合，才是边缘运维从脚本走向平台化的关键。</p><p>这也是我认为 AI 开发真正有价值的地方：它让有经验的人把复杂系统更快落地，而不是让没有经验的人绕过系统设计、风险判断和生产验收。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[用Claude+Codex开发生产级 AIOps：提示词约束、开发流与验收方法]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/yong-claudecodex-kai-fa-sheng-chan-ji-aiops-ti-shi-ci-yue-shu--kai-fa-liu-yu-yan-shou-fang-fa" />
                <id>tag:https://blog.yongjie.top,2026-06-28:yong-claudecodex-kai-fa-sheng-chan-ji-aiops-ti-shi-ci-yue-shu--kai-fa-liu-yu-yan-shou-fang-fa</id>
                <published>2026-06-28T18:23:35+08:00</published>
                <updated>2026-07-03T13:39:10+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%E7%94%A8-claude%2Bcodex-%E5%BC%80%E5%8F%91%E7%94%9F%E4%BA%A7%E7%BA%A7-aiops%EF%BC%9A%E6%8F%90%E7%A4%BA%E8%AF%8D%E7%BA%A6%E6%9D%9F%E3%80%81%E5%BC%80%E5%8F%91%E6%B5%81%E4%B8%8E%E9%AA%8C%E6%94%B6%E6%96%B9%E6%B3%95" tabindex="-1">用  Claude+Codex  开发生产级 AIOps：提示词约束、开发流与验收方法</h1><p>很多人用 AI 写代码时，最容易遇到的问题不是“AI 不会写”，而是“AI 写得太快，边界没有跟上”。一个生产级 AIOps 平台，不能只看功能是否跑通，还要看权限、审计、SQL、版本、构建、部署、回滚和敏感信息是否都被管住。</p><p>这个 AIOps 项目已经完成上线，代码实现由  Claude+Codex  完成。这里说的“AI 开发”，不是把一句话丢给模型，也不是小白复制提示词就能交付生产平台。它需要先具备足够的运维、架构、权限、安全、发布和验收经验，再把这些经验转成 AI 能执行的提示词和工程规则。</p><p>本文重点讲开发方法，不贴源码，讨论如何用脚手架、提示词和文档约束，让 AI 从需求一路推进到可交付版本。</p><p><img src="/upload/2026/07/aiops-codex55-development-workflow.svg" alt="aiops-codex55-development-workflow" /></p><h2 id="1.-%E4%B8%80%E8%A1%8C%E4%BB%A3%E7%A0%81%E4%B8%8D%E5%86%99%EF%BC%8C%E4%B8%8D%E7%AD%89%E4%BA%8E%E6%B2%A1%E6%9C%89%E5%B7%A5%E7%A8%8B%E9%97%A8%E6%A7%9B" tabindex="-1">1. 一行代码不写，不等于没有工程门槛</h2><p>如果只对 AI 说“帮我做一个公网 IP 管理功能”，它很可能先生成页面，再补接口，最后留下权限、审计、导入模板、SQL 幂等和发布文档的缺口。</p><p>所以第一道门槛不是写代码，而是知道一个生产功能应该包含哪些部分。没有这层知识储备，AI 生成得越快，缺口越隐蔽。</p><p>更稳的方式，是把 Codex 当成一个工程团队来管理。每个需求进入开发前，都要先回答这些问题：</p><table><thead><tr><th>问题</th><th>目的</th></tr></thead><tbody><tr><td>这个功能属于哪个菜单和能力域</td><td>避免功能入口混乱</td></tr><tr><td>哪些操作是高风险写操作</td><td>决定权限、二次确认和审计</td></tr><tr><td>数据模型是否需要新表或字段</td><td>决定 SQL 和兼容升级</td></tr><tr><td>是否影响 edge-agent</td><td>决定是否升级边缘组件</td></tr><tr><td>如何验收</td><td>决定测试、截图和浏览器冒烟范围</td></tr><tr><td>如何发布和回滚</td><td>决定版本、镜像、SQL 和升级文档</td></tr></tbody></table><p>这些问题回答清楚以后，AI 写代码才不会散。</p><p>这也是为什么“一行代码不写”并不等于“任何人都能做”。真正难的是把隐性的生产经验显性化，再让 AI 按这个结构执行。</p><h2 id="2.-%E5%B7%A5%E7%A8%8B%E8%A7%84%E6%A8%A1%E8%B6%8A%E5%A4%A7%EF%BC%8C%E8%B6%8A%E9%9C%80%E8%A6%81%E6%8F%90%E7%A4%BA%E8%AF%8D%E7%BA%AA%E5%BE%8B" tabindex="-1">2. 工程规模越大，越需要提示词纪律</h2><p>这个项目最终不是一次性生成的小工具，而是一个 30 万行级别的生产工程。按非空行粗略统计，排除常见构建产物、依赖目录和图片资产后，仓库仍约有 2,957 个文件、30.7 万行工程内容。</p><p>其中后端 Java 约 10.6 万行，前端 TS/TSX 约 7 万行，edge-agent Python 约 3.5 万行；后端包含 42 个 Maven 模块/聚合、69 个 Controller、112 个 Service 实现、143 个 DAO 接口；前端 <code>src/pages</code> 下约 258 个页面文件；边缘 Agent <code>src</code> 下约 82 个 Python 文件。</p><p>前端目录还包含部分静态运行时资产，所以不能把所有行数都简单理解为业务代码。但这个体量足以说明一件事：AI 不是在替人写几个函数，而是在一个跨前端、后端、数据库、边缘执行器、文档和发布流程的大工程里持续协作。</p><p>规模一上来，提示词就不能只写“帮我实现某功能”。它必须变成工程约束，明确菜单、权限、SQL、审计、版本、敏感信息、测试、截图和发布文档，否则每一轮自动生成都可能扩大债务。</p><h2 id="3.-%E5%85%88%E6%90%AD%E8%84%9A%E6%89%8B%E6%9E%B6%EF%BC%8C%E5%86%8D%E8%B0%88-ai-%E9%80%9F%E5%BA%A6" tabindex="-1">3. 先搭脚手架，再谈 AI 速度</h2><p>AI 适合在清晰边界内高速推进，不适合在一片空地上替人决定所有工程秩序。</p><p>这个项目能用  Claude+Codex  持续开发，前提是先搭好了基础脚手架：</p><table><thead><tr><th>脚手架</th><th>作用</th></tr></thead><tbody><tr><td>前端路由和菜单体系</td><td>让新功能知道放在哪个能力域</td></tr><tr><td>后端分层和模块边界</td><td>让接口、Service、DAO、模型不混乱</td></tr><tr><td>权限和菜单 SQL 规则</td><td>让页面入口、接口鉴权、角色授权同步</td></tr><tr><td>edge-agent runtime 规范</td><td>让边缘能力保持受控 API，而不是任意脚本</td></tr><tr><td>文档和发布目录</td><td>让设计、进度、升级和回滚有固定位置</td></tr><tr><td>版本和镜像规则</td><td>让生产交付不会被临时 tag 污染</td></tr></tbody></table><p>有了这些地基，AI 才能像工程团队一样扩展功能。没有这些地基，AI 只能不断生成局部补丁。</p><h2 id="4.-%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%A6%81%E5%85%88%E7%BA%A6%E6%9D%9F%E8%BE%B9%E7%95%8C" tabindex="-1">4. 提示词要先约束边界</h2><p>这个项目里最重要的提示词约束，不是“代码写得优雅”，而是“哪些事情不能做”。</p><p>提示词不是模板魔法。高质量提示词背后，是对业务、架构和生产风险的理解。你必须知道哪些字段敏感、哪些操作危险、哪些状态不能跳过、哪些发布动作会影响线上，才能写出有效约束。</p><p>典型约束包括：</p><pre><code class="language-text">不要泄露真实 IP、客户名、主机名、密码、Token、私钥。不要把构建产物、node_modules、target、dist 长期留在项目目录。不要只做前端隐藏，危险操作必须后端强校验。不要使用 latest、local 或长后缀 tag 作为生产版本。不要把 edge-agent 当成有状态数据库或任务中心。不要把任意 shell 执行能力暴露给边缘 Agent。</code></pre><p>这些“不要”比“要实现什么”同样重要。它们决定了平台最终是不是生产系统，而不是演示项目。</p><h2 id="5.-%E5%BC%80%E5%8F%91%E6%B5%81%E6%96%87%E6%A1%A3%E6%98%AF-ai-%E9%A1%B9%E7%9B%AE%E7%9A%84%E8%AE%B0%E5%BF%86" tabindex="-1">5. 开发流文档是 AI 项目的记忆</h2><p>AIOps 项目里，每个重要功能都有设计文档和开发进度文档。它们不是形式主义，而是长周期 AI 开发的记忆系统。</p><p>例如边缘运维能力会记录：设计结论、目标、非目标、菜单结构、总体架构、责任边界、数据模型、安全策略、接口草案和验收清单。</p><p>开发流则记录每轮变更：</p><pre><code class="language-text">阶段：本轮目标：修改范围：测试命令：测试结果：风险：下一步：</code></pre><p>这样做有两个好处。</p><p>第一，Codex 在下一轮开发时可以从文档恢复上下文，不需要靠聊天记录猜测历史决策。</p><p>第二，人工验收时能快速判断这轮改动是否越界，是否漏掉 SQL、权限、版本或发布说明。</p><h2 id="6.-%E9%9C%80%E6%B1%82%E6%8B%86%E5%88%86%E5%BF%85%E9%A1%BB%E5%8C%85%E5%90%AB%E9%9D%9E%E7%9B%AE%E6%A0%87" tabindex="-1">6. 需求拆分必须包含非目标</h2><p>生产项目里，非目标经常比目标更重要。</p><p>以 Ceph 巡检为例，目标是通过 edge-agent 做只读采集，中心平台生成 Excel、统计图和 AI 分析。非目标则包括：不让中心平台直连边缘 SSH、不开放任意 shell、不执行 Ceph 变更命令、不复用 PVE runtime 接口、不把历史脚本目录打进生产镜像。</p><p>这些非目标能防止 AI 为了“快速实现”做出危险捷径。</p><p>再以公网 IP 管理为例，第一阶段只做 IPv4 公网 IP 台账、CIDR 拆分、客户分配、回收、批量导入和运营商识别，不做 IPv6 地址池展开，不自动下发防火墙，不做带宽计费。</p><p>功能越复杂，越要把第一阶段范围压清楚。</p><h2 id="7.-%E5%89%8D%E7%AB%AF%E3%80%81%E5%90%8E%E7%AB%AF%E3%80%81sql%E3%80%81%E6%9D%83%E9%99%90%E8%A6%81%E4%B8%80%E8%B5%B7%E9%AA%8C%E6%94%B6" tabindex="-1">7. 前端、后端、SQL、权限要一起验收</h2><p>AIOps 这类平台常见的问题是“页面看起来完成了，但生产不能用”。原因通常是其中一层没闭环。</p><p>一项功能至少要同时检查：</p><table><thead><tr><th>层</th><th>检查点</th></tr></thead><tbody><tr><td>前端</td><td>菜单、按钮、表单、表格、弹窗、导入导出、错误提示</td></tr><tr><td>后端</td><td>接口、参数校验、状态机、异常分支、权限强校验</td></tr><tr><td>SQL</td><td>表、字段、索引、菜单、接口权限、幂等升级</td></tr><tr><td>权限</td><td>有权限可见可用，无权限隐藏且后端拒绝</td></tr><tr><td>审计</td><td>高风险写操作有成功/失败记录，敏感字段脱敏</td></tr><tr><td>发布</td><td>组件版本、镜像 tag、升级文档、回滚路径</td></tr></tbody></table><p>Codex 可以同时改多层，但验收时必须按层拆开看。</p><p>这里的验收能力也是门槛。你要能看懂接口返回、构建日志、SQL 升级结果、权限缓存、浏览器截图和容器镜像 tag。否则 AI 说“完成了”，你也无法判断它是不是真的能上线。</p><h2 id="8.-%E6%B5%8F%E8%A7%88%E5%99%A8%E6%88%AA%E5%9B%BE%E6%98%AF%E5%BE%88%E6%9C%89%E6%95%88%E7%9A%84%E9%AA%8C%E6%94%B6%E6%96%B9%E5%BC%8F" tabindex="-1">8. 浏览器截图是很有效的验收方式</h2><p>在这个项目里，截图不是为了好看，而是为了发现真实 UI 问题。</p><p>比如搜索栏是否拥挤、表格横向滚动条是否可见、按钮是否越权展示、弹窗是否能表达二次确认、页面是否仍显示旧版本文案，这些问题靠读代码不一定能发现。</p><p>平台中也保留了前端自动截图工作流：扫描路由、自动登录、截取页面、收集 DOM 摘要和可访问性树。这样 AI 可以用截图和 DOM 反向分析页面结构，再给出修复建议。</p><h2 id="9.-edge-agent-%E5%8F%98%E6%9B%B4%E8%A6%81%E5%8D%95%E7%8B%AC%E4%B8%8A%E9%94%81" tabindex="-1">9. edge-agent 变更要单独上锁</h2><p>AIOps 的边缘能力依赖 edge-agent。只要变更涉及 runtime、capabilities、API 入参/出参、内置脚本或运行时资产，就必须把 edge-agent 纳入发布影响面。</p><p>典型场景包括：</p><table><thead><tr><th>场景</th><th>是否需要评估 edge-agent</th></tr></thead><tbody><tr><td>PVE 虚机清单字段采集</td><td>需要</td></tr><tr><td>PVE/Ceph/网络巡检</td><td>需要</td></tr><tr><td>网络设备测试连接</td><td>需要</td></tr><tr><td>巡检 artifact 下载</td><td>需要</td></tr><tr><td>只改前端展示文案</td><td>通常不需要</td></tr><tr><td>只改后端查询筛选</td><td>视是否依赖 Agent 返回字段而定</td></tr></tbody></table><p>这条规则能避免一个常见生产事故：backend/frontend 已升级，但边缘 Agent 仍是旧版本，结果页面有按钮，后端有接口，实际边缘执行失败。</p><h2 id="10.-%E7%89%88%E6%9C%AC%E5%92%8C%E5%8F%91%E5%B8%83%E8%A7%84%E5%88%99%E8%A6%81%E5%86%99%E6%AD%BB" tabindex="-1">10. 版本和发布规则要写死</h2><p>AI 开发很容易产生很多临时版本名，例如带日期、功能名、环境名或 <code>SNAPSHOT</code>。这对生产交付很危险。</p><p>AIOps 的版本规则被固定为：</p><pre><code class="language-text">backend/control：v0.1.xfrontend：v1.0.xedge-agent：v1.1.x</code></pre><p>生产镜像 tag 必须是 <code>vX.Y.Z</code>。禁止使用 <code>latest</code>、<code>local</code>、日期后缀、功能名后缀、环境名后缀或覆盖旧 tag。</p><p>发布文档也必须记录：组件版本、是否包含 MySQL 变更、SQL 文件清单、升级步骤、验证步骤、回滚方案和用户侧注意事项。</p><h2 id="11.-ai-%E5%BC%80%E5%8F%91%E7%9A%84%E7%9C%9F%E6%AD%A3%E4%BC%98%E5%8A%BF" tabindex="-1">11. AI 开发的真正优势</h2><p>Claude+Codex 的优势不是少写几行代码，而是能在同一轮工作里完成多层联动。</p><p>一个需求从设计到上线，可能同时涉及前端页面、后端接口、DAO、SQL、权限、审计、测试、文档、升级手册和截图验收。传统开发里这些工作容易分散到多人、多天、多次沟通。AI 适合在明确边界内连续推进。</p><p>但前提是人要把目标和边界说清楚。AI 越强，越需要明确约束，否则它会用很高的效率扩大错误。</p><p>当工程规模达到数千文件、数十万行时，真正节省的不是某一个接口的编码时间，而是跨模块联动的上下文切换成本。Codex 可以把“改页面、补接口、加 SQL、更新权限、写文档、跑验证”放在同一条工作流里完成，人的重点则转向设计、约束和验收。</p><p>这类项目的含金量，恰恰在于人能把复杂系统拆成 AI 可执行的任务，并持续判断 AI 的产出是否满足生产标准。</p><h2 id="12.-%E5%B0%8F%E7%BB%93" tabindex="-1">12. 小结</h2><p>用  Claude+Codex 开发生产级 AIOps，我认为最关键的是 5 件事。</p><p>第一，先有知识储备和脚手架，再谈 AI 开发速度。</p><p>第二，先写设计和非目标，再让 AI 执行。</p><p>第三，把提示词约束写成工程规则，尤其是安全、权限、版本、发布和敏感信息。</p><p>第四，开发流文档必须持续更新，让 AI 项目有长期记忆。</p><p>第五，前端、后端、SQL、权限、审计和发布要一起验收。</p><p>第六，截图和浏览器冒烟要进入流程，因为真实页面能暴露很多代码审查看不到的问题。</p><p>AI 可以写完整个平台，但生产边界必须由工程纪律来守住。所谓一行代码不写，不是没有能力门槛，而是把能力从“敲代码”迁移到“定义系统、约束系统、验收系统”。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[从0到上线：一个由Claude+Codex开发完成的AIOps自动化智能运维平台]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/cong-0-dao-shang-xian--yi-ge-you-claudecodex-kai-fa-wan-cheng-de-aiops-zi-dong-hua-zhi-neng-yun-wei-ping-tai" />
                <id>tag:https://blog.yongjie.top,2026-06-27:cong-0-dao-shang-xian--yi-ge-you-claudecodex-kai-fa-wan-cheng-de-aiops-zi-dong-hua-zhi-neng-yun-wei-ping-tai</id>
                <published>2026-06-27T11:15:31+08:00</published>
                <updated>2026-07-03T13:38:26+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%E4%BB%8E-0-%E5%88%B0%E4%B8%8A%E7%BA%BF%EF%BC%9A%E4%B8%80%E4%B8%AA%E7%94%B1-claude%2Bcodex-%E5%BC%80%E5%8F%91%E5%AE%8C%E6%88%90%E7%9A%84-aiops-%E6%99%BA%E8%83%BD%E8%BF%90%E7%BB%B4%E5%B9%B3%E5%8F%B0" tabindex="-1">从 0 到上线：一个由 Claude+Codex 开发完成的 AIOps 智能运维平台</h1><p>AIOps 平台最难的地方，不是把页面做出来，也不是把接口跑通，而是把“运维入口、边缘资源、权限审计、巡检产物、发布升级”放进同一个可长期维护的体系里。</p><p>这个项目已经完成开发并上线。整个代码实现由 Claude+Codex 驱动完成，人工没有逐行手写业务代码，但这不代表没有门槛。真正的门槛在于：能不能把运维经验、架构判断、基础脚手架、提示词约束、测试验收和生产发布规则讲清楚。</p><p>本文不展开源码，而是从产品功能、架构分层和实现方法看这个平台是怎么落地的。它更像一篇“AI 驱动生产开发复盘”，不是一篇“小白复制提示词就能做平台”的教程。</p><p><img src="/upload/2026/07/image-20260702192223086.png" alt="image-20260702192223086" /><br /><img src="/upload/2026/07/image-20260702192453140.png" alt="image-20260702192453140" /><br /><img src="/upload/2026/07/image-20260702191939082.png" alt="image-20260702191939082" /><br /><img src="/upload/2026/07/image-20260702192134220.png" alt="image-20260702192134220" /></p><h2 id="1.-%E9%A1%B9%E7%9B%AE%E8%A7%84%E6%A8%A1%EF%BC%9A%E4%B8%8D%E6%98%AF-demo-%E5%B7%A5%E7%A8%8B" tabindex="-1">1. 项目规模：不是 Demo 工程</h2><p>这不是一个只有几个页面、几个接口的演示项目。按非空行粗略统计，在排除 <code>node_modules</code>、<code>target</code>、<code>dist</code>、缓存目录和博客图片资产后，项目仍约有 2,957 个文件、30.7 万行工程内容；如果按仓库文件清单看，全仓约 3,100+ 个文件。</p><p><img src="/upload/2026/07/9750927b1d71d20b9df38286c34fd818.png" alt="9750927b1d71d20b9df38286c34fd818" /></p><p>这个数字不等同于“全部都是人工手写业务代码”，前端目录也包含部分静态运行时资产。但从模块数量、联动范围和发布物数量看，它已经是一个完整的生产级平台工程。</p><table><thead><tr><th>维度</th><th>规模与说明</th></tr></thead><tbody><tr><td>后端工程</td><td>约 2,067 个文件，Java 非空行约 10.6 万行，包含 42 个 Maven 模块/聚合、69 个 Controller、112 个 Service 实现、143 个 DAO 接口</td></tr><tr><td>前端控制台</td><td>约 702 个文件，TS/TSX 非空行约 7 万行，<code>src/pages</code> 下约 258 个页面文件，覆盖仪表盘、告警、CI/CD、资产、边缘运维和系统设置</td></tr><tr><td>边缘 Agent</td><td>约 153 个文件，Python 非空行约 3.5 万行，<code>src</code> 下约 82 个 Python 文件，负责 PVE、Ceph、网络设备、裸金属和巡检执行</td></tr><tr><td>数据与交付</td><td>SQL 非空行约 5,600 行，YAML 非空行约 5,300 行，Markdown 文档约 2.3 万行，覆盖菜单权限、升级、部署、验收和设计记录</td></tr></tbody></table><p>这意味着 Claude+Codex  不是在补一个页面，而是在同时维护前端体验、后端编排、权限模型、SQL 升级、边缘执行、审计闭环、发布文档和脱敏资料。</p><p>也正因为项目体量足够大，AI 开发的价值才体现出来：它能在清晰约束下快速推进多模块联动。但人的架构判断、领域知识和验收能力也必须同步放大，否则代码生成速度越快，失控范围也越大。</p><h2 id="2.-%E5%85%88%E5%AE%9A%E4%B9%89%E5%B9%B3%E5%8F%B0%E8%BE%B9%E7%95%8C" tabindex="-1">2. 先定义平台边界</h2><p>这个 AIOps 平台不是单点脚本工具，而是面向多机房、多资源域、多角色的运维控制台。</p><p>它覆盖的能力包括告警、CI/CD、数据库、通知、资产、边缘运维、系统管理、用户设置等模块。其中最有生产价值的部分，是把边缘机房里的 PVE、Ceph、网络设备、裸金属、公网 IP 和巡检任务统一纳入中心平台。</p><p>平台的核心定位可以概括成一句话：</p><pre><code class="language-text">中心平台负责状态、权限、审计和编排；边缘 Agent 负责受控执行。</code></pre><p>这个边界非常重要。只有中心平台掌握权威数据，后续的审计、权限、回滚、升级和多租户治理才不会散掉。</p><h2 id="3.-%E6%80%BB%E4%BD%93%E6%9E%B6%E6%9E%84" tabindex="-1">3. 总体架构</h2><p>平台采用“中心编排 + 无状态边缘执行”的结构。前端控制台访问后端 API；后端保存机房、Agent、凭据、任务、产物和审计；edge-agent 部署在边缘机房内，负责访问本地 PVE、Ceph、网络设备和裸金属资源。</p><p><img src="/upload/2026/07/aiops-platform-production-architecture.svg" alt="aiops-platform-production-architecture" /><br /><img src="/upload/2026/07/aiops-platform-architecture.svg" alt="aiops-platform-architecture" /></p><p>这张图里最关键的是 4 条线。</p><p>第一条是用户入口线。运维人员通过控制台发起操作，外部系统可以通过开放 API 对接告警、资产或自动化流程。</p><p>第二条是平台运行时线。Backend App 承载业务编排，Control Service 承载租户和权限能力，后端内部模块按 system、tenant、ops、asset、project、notice、plugins 等能力域组织，但它们不是独立部署服务。</p><p>第三条是数据与异步线。MySQL 保存业务数据、权限和审计；Redis 保存会话和权限缓存；MinIO 保存巡检产物、导入导出文件；RocketMQ 用于异步消息和任务解耦。</p><p>第四条是边缘执行线。中心平台通过受控 API 调用 edge-agent，edge-agent 在机房内网访问 PVE、Ceph、网络设备、裸金属等资源，并把结构化结果回传给中心。</p><h2 id="4.-%E5%8A%9F%E8%83%BD%E5%9F%9F%E6%80%8E%E4%B9%88%E6%8B%86" tabindex="-1">4. 功能域怎么拆</h2><p>AIOps 的菜单不是简单堆功能，而是按平台职责拆分。</p><p><code>EfficiencyManagement</code> 用于效率看板和指标概览。它适合做管理视角的入口，让用户先看到告警存量、处理状态和团队效率趋势。</p><p><code>Alert</code> 和 <code>Notification Center</code> 承载告警、分派策略、通知渠道和通知记录。它们是运维事件进入平台后的第一层治理。</p><p><code>CI/CD</code> 和 <code>Asset</code> 负责交付和资产底座。代码源、制品、集群、模板、变量等能力都在这里沉淀，避免每个业务模块重复维护基础配置。</p><p><code>Edge Operations</code> 是这个项目的重心，包含机房、Agent、PVE 集群、Ceph 集群、虚机清单、虚机操作台、裸金属节点、裸金属操作台、任务历史、自动化巡检和网络运维。</p><p><code>Settings</code> 负责角色、用户、群组、系统日志、系统属性、SSO 配置和 AI 诊断配置。高风险动作都要进入这里的权限和审计体系。</p><h2 id="5.-%E8%BE%B9%E7%BC%98-agent-%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BF%85%E9%A1%BB%E6%97%A0%E7%8A%B6%E6%80%81" tabindex="-1">5. 边缘 Agent 为什么必须无状态</h2><p>边缘机房常见问题是网络边界复杂、内网地址重复、机房数量多、资源类型混杂。如果中心平台直接保存一堆临时脚本和本地缓存，很快会变成不可控的运维黑盒。</p><p>因此 edge-agent 被设计成无状态执行器：</p><table><thead><tr><th>组件</th><th>负责</th><th>不负责</th></tr></thead><tbody><tr><td>AIOps 中心平台</td><td>机房、Agent、凭据、任务、审计、权限、产物</td><td>直接进入边缘内网执行命令</td></tr><tr><td>edge-agent</td><td>受控 API、只读采集、生命周期动作、结构化返回</td><td>保存平台配置、长期保存凭据、维护本地任务库</td></tr><tr><td>边缘资源</td><td>提供 PVE、Ceph、网络设备、裸金属状态和执行入口</td><td>平台权限、审计、报表和 AI 分析</td></tr></tbody></table><p>这个设计带来一个直接好处：边缘侧可以横向部署多个 Agent，但权威状态始终在中心平台。Agent 出问题可以重启或替换，不会丢失任务历史和审计链路。</p><h2 id="6.-%E7%94%9F%E4%BA%A7%E5%8A%9F%E8%83%BD%E4%B8%8D%E6%98%AF%E2%80%9C%E8%83%BD%E7%82%B9%E6%8C%89%E9%92%AE%E2%80%9D%E5%B0%B1%E7%BB%93%E6%9D%9F" tabindex="-1">6. 生产功能不是“能点按钮”就结束</h2><p>平台里很多能力看似只是按钮，但生产实现必须补齐闭环。</p><p>例如虚机操作台不是简单调用 PVE API。它需要先选择机房，输入虚机 IP，解析候选虚机，再让用户核对 VMID、节点、虚机名、IP、当前状态，最后执行启动、停止或重置。</p><p>公网 IP 管理也不是一张 Excel 表。它要支持 CIDR 规范化、父子段拆分、单 IP 状态、分配历史、回收、删除、批量导入、运营商识别和审计。</p><p>自动化巡检也不是跑一段脚本。它要支持 PVE、Ceph、网络设备等不同巡检类型，按机房和 Agent 分组执行，允许部分成功，保存 Excel、ZIP、JSON 等产物，并接入 AI 分析。</p><p>这些细节决定了平台能不能真的上线，而不是只停留在演示环境。</p><h2 id="7.-%E4%B8%80%E8%A1%8C%E4%BB%A3%E7%A0%81%E4%B8%8D%E5%86%99%EF%BC%8C%E4%BA%BA%E7%9A%84%E8%A7%92%E8%89%B2%E4%B8%8D%E6%98%AF%E6%B6%88%E5%A4%B1" tabindex="-1">7. 一行代码不写，人的角色不是消失</h2><p>这个项目的代码由 Claude+Codex 完成，但并不意味着“给一句需求，然后等它自己写完”。真正有效的方式，是把 AI 当成一个可以持续执行的工程团队，同时用人的经验给它搭好边界。</p><p>“一行代码不写”的含义，是不把人的精力消耗在重复编码上。人的工作前移到更高层：判断系统怎么拆、哪些动作危险、哪些数据不能泄露、哪些组件需要联动升级、哪些测试能证明上线安全。</p><p>人工主要做 5 件事：</p><table><thead><tr><th>人工职责</th><th>作用</th></tr></thead><tbody><tr><td>知识储备</td><td>理解边缘运维、PVE、Ceph、网络设备、权限审计和发布链路</td></tr><tr><td>脚手架搭建</td><td>明确项目结构、菜单体系、权限模型、前后端边界和基础工程规范</td></tr><tr><td>需求澄清</td><td>明确功能目标、非目标、状态机和验收方式</td></tr><tr><td>约束编写</td><td>把版本、SQL、权限、构建、密钥、审计写成规则</td></tr><tr><td>过程验收</td><td>看页面截图、读日志、确认接口和权限是否符合预期</td></tr><tr><td>生产决策</td><td>判断是否升级 edge-agent、是否执行 SQL、是否进入发布</td></tr></tbody></table><p>Codex 负责把这些约束转成代码、文档、测试、构建脚本和升级记录。</p><p>这种协作方式的重点不是让 AI “自由发挥”，而是把自由发挥限制在工程边界内。</p><h2 id="8.-%E6%9C%80%E5%85%B3%E9%94%AE%E7%9A%84%E5%B7%A5%E7%A8%8B%E7%BB%8F%E9%AA%8C" tabindex="-1">8. 最关键的工程经验</h2><p>第一，先有知识储备，再写提示词。提示词不是口号，而是把业务经验、架构经验和生产事故经验结构化表达出来。</p><p>第二，先搭基础脚手架，再让 AI 扩展功能。菜单、路由、权限、后端分层、SQL 规则、镜像版本、文档入口，这些地基不稳，AI 写得越快，返工越多。</p><p>第三，先写设计文档，再让 AI 开发。没有设计文档，AI 容易只补当前缺口，忽略状态机、权限、审计和发布路径。</p><p>第四，每个功能都要有开发流文档。开发流记录目标、修改范围、测试命令、测试结果、风险和下一步，避免长周期项目丢上下文。</p><p>第五，前端、后端、SQL、权限必须同步。只做页面按钮没有意义，后端不校验权限就有越权风险；只做后端接口也不够，菜单和角色权限不更新，用户看不到入口。</p><p>第六，edge-agent 相关能力必须单独判断发布影响。如果功能依赖 Agent runtime、capabilities、内置脚本或返回字段，就不能只升级 backend/frontend。</p><p>第七，发布版本必须有纪律。生产镜像 tag 使用 <code>vX.Y.Z</code>，不能使用 <code>latest</code>、<code>local</code>、日期后缀或功能名后缀。</p><h2 id="9.-%E5%B0%8F%E7%BB%93" tabindex="-1">9. 小结</h2><p>这个 AIOps 平台的落地经验可以总结为三点。</p><p>第一，平台架构要先把中心状态和边缘执行拆开。中心平台保存权威数据，edge-agent 做无状态受控执行。</p><p>第二，功能设计要围绕生产闭环。权限、审计、导入导出、失败处理、产物归档和升级回滚，和页面按钮一样重要。</p><p>第三， Claude+Codex  可以完成完整工程实现，但前提是知识储备、脚手架、需求约束、测试用例和发布纪律足够清晰。AI 负责产出，人负责判断和边界。</p><p>所以，这个项目的含金量不在“少写了多少代码”，而在**“把一个复杂运维平台拆成 AI 能稳定执行、能验收、能上线的工程系统”**。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第三章-AI 集群下的JuiceFS架构规划]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-san-zhang--ai-ji-qun-xia-de-juicefs-jia-gou-gui-hua" />
                <id>tag:https://blog.yongjie.top,2026-06-13:di-san-zhang--ai-ji-qun-xia-de-juicefs-jia-gou-gui-hua</id>
                <published>2026-06-13T18:06:17+08:00</published>
                <updated>2026-07-02T18:06:39+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%E5%8D%83%E5%8D%A1-ai-%E9%9B%86%E7%BE%A4%E4%B8%8B%E7%9A%84-juicefs-%E8%A7%84%E5%88%92%EF%BC%9A%E5%AE%B9%E9%87%8F%E3%80%81meta%E3%80%81gateway-%E4%B8%8E-cache-%E6%80%8E%E4%B9%88%E7%AE%97" tabindex="-1">千卡 AI 集群下的 JuiceFS 规划：容量、Meta、Gateway 与 Cache 怎么算</h1><p>AI 集群上共享文件系统的规划，不能只问“需要多少 PB”。真正决定落地规模的变量至少有 4 个：GPU 客户端数量、总吞吐目标、总文件数、故障域与容量水位。</p><p>本文以千卡级 AI 集群为例，整理 JuiceFS 方案评审时最容易混淆的几个问题：Gateway/Cache 到底跟谁扩展，Meta 是按文件系统数量算还是按总文件数算，社区版和企业版在超大命名空间下该怎么选。</p><p><img src="/upload/2026/07/juicefs-ai-capacity-planning.svg" alt="juicefs-ai-capacity-planning" /></p><h2 id="1.-%E5%85%88%E6%8B%86%E5%BC%80-4-%E4%B8%AA%E7%BB%B4%E5%BA%A6" tabindex="-1">1. 先拆开 4 个维度</h2><p>容量由对象存储层决定。对象存储节点数量受总容量、EC 策略、目标水位、磁盘规格和故障域影响。</p><p>接入层由 GPU 客户端规模和吞吐目标决定。Gateway、Cache 或等价接入组件不应该简单按文件系统个数乘，而应该看并发客户端、热点数据、回源带宽和业务峰值。</p><p>Meta 由总文件数、目录层级、元数据访问压力和产品形态决定。文件系统数量只是隔离和治理维度，不会自动把总元数据量变小。</p><p>文件系统拆分由命名空间、权限隔离、故障域、团队边界和运维复杂度决定。一个文件系统更简单，多个文件系统更利于隔离，但也会增加配额、凭据、挂载和数据治理成本。</p><h2 id="2.-%E5%AE%B9%E9%87%8F%E8%A7%84%E5%88%92%E5%85%AC%E5%BC%8F" tabindex="-1">2. 容量规划公式</h2><p>对象存储容量可以按以下公式估算：</p><pre><code class="language-text">单节点原始容量 = 单盘容量 * 单节点数据盘数量EC 后逻辑容量 = 单节点原始容量 * data_chunks / (data_chunks + parity_chunks)目标可用容量 = 节点数 * EC 后逻辑容量 * 目标水位</code></pre><p>例如一台存储节点有 24 块 7.68TB NVMe，采用 EC 8+2，则单节点原始容量约为 184.32TB，EC 后逻辑容量约为 147.456TB。若按 80% 水位规划，每节点可承载的规划容量约为 117.96TB。</p><p>如果目标是 16.5PB 级逻辑容量，按 80% 水位反推，存储节点下限约为 140 台。这个数字只是容量口径，还没有包含后续扩容余量、故障恢复、热点倾斜和业务峰值冗余。</p><h2 id="3.-%E6%95%85%E9%9A%9C%E5%9F%9F%E8%A6%81%E5%92%8C%E5%AE%B9%E9%87%8F%E6%B0%B4%E4%BD%8D%E4%B8%80%E8%B5%B7%E7%9C%8B" tabindex="-1">3. 故障域要和容量水位一起看</h2><p>“允许两个 rack 临时故障”与“两个 rack 永久丢失仍可无感恢复”不是一回事。</p><p>如果两个 rack 是临时故障，并且故障期间通过 Ceph 运维策略避免立即回填，集群可以在降级状态下维持业务，等待故障 rack 恢复后再回到正常水位。</p><p>如果两个 rack 永久丢失，则必须重新计算剩余节点容量、水位和恢复策略。此时要么补节点，要么接受水位上升和恢复窗口变长，不能把临时故障口径直接写成永久容灾能力。</p><p>下面这张图把 16.5PB 容量下限、140 台节点、12 台/rack 和 2 个 rack 故障口径放到同一个视图里。这样可以避免把“临时降级运行”误写成“永久容灾能力”。</p><p><img src="/upload/2026/07/juicefs-fault-domain-watermark.svg" alt="juicefs-fault-domain-watermark" /></p><p>生产规划里建议明确 3 个值：</p><table><thead><tr><th>指标</th><th>推荐写法</th></tr></thead><tbody><tr><td>正常水位</td><td>例如 70% 到 80%，保留扩容与恢复空间</td></tr><tr><td>临时故障策略</td><td>明确是否暂停回填、最长容忍时间和恢复流程</td></tr><tr><td>永久故障策略</td><td>明确补节点、重平衡和容量水位重新评估方式</td></tr></tbody></table><h2 id="4.-gateway-%E4%B8%8E-cache-%E8%B7%9F%E9%9A%8F%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%A7%84%E6%A8%A1" tabindex="-1">4. Gateway 与 Cache 跟随客户端规模</h2><p>很多方案评审会把 Gateway/Cache 误解成“每个文件系统固定几台”。这在小规模场景里看似成立，但到千卡级 AI 集群就会失真。</p><p>Gateway/Cache 更合理的驱动因素是 GPU 客户端数量、并发训练任务、数据集热点、回源带宽和读写峰值。</p><p>例如按“每 100 个 GPU 客户端配置 4 台 Gateway、1 台 Cache”的经验口径，千卡级集群的接入层大约需要 40 到 44 台 Gateway、10 到 11 台 Cache。无论最终是 1 个文件系统还是多个文件系统，接入层都要服务同一批 GPU 客户端和吞吐目标。</p><p>下面这张图说明 Gateway/Cache 是跟随 GPU 客户端和吞吐目标扩展，而不是跟文件系统数量简单相乘。文件系统数量影响隔离方式，接入层总规模仍要回到客户端规模。</p><p><img src="/upload/2026/07/juicefs-gateway-cache-sizing.svg" alt="juicefs-gateway-cache-sizing" /></p><p>如果只部署 1 个文件系统，Gateway/Cache 可以共同服务同一个命名空间。如果拆成多个文件系统，可以按业务组、训练队列或数据域把 Gateway/Cache 自然切分，但总规模仍要回到客户端和吞吐需求上。</p><h2 id="5.-meta-%E6%8C%89%E6%80%BB%E6%96%87%E4%BB%B6%E6%95%B0%E4%BC%B0%E7%AE%97" tabindex="-1">5. Meta 按总文件数估算</h2><p>Meta 规划最容易混淆的是“文件系统数量”和“总文件数”。</p><p>如果总文件数不变，只是从 1 个文件系统拆成 10 个文件系统，Meta 总量不会自动乘以 10。真正让 Meta 明显放大的，是总文件数从 500 亿变成 5000 亿。</p><p>在一次保守方案估算里，可以使用以下经验口径：</p><pre><code class="language-text">1 亿文件 &gt;= 30GB Meta 内存</code></pre><p>按这个口径，500 亿文件约需要 15TB Meta 内存。如果单台 Meta 节点内存为 1.5TB 左右，理论下限约 10 台，工程上建议按 12 到 16 台规划。</p><p>如果是 10 个文件系统，每个都要求 500 亿文件，总文件数会变成 5000 亿。此时 Meta 内存估算约为 150TB，已经是完全不同的工程规模，节点数量可能需要按 100 到 140 台级别重新评估。</p><p>这个估算不是通用承诺。真实 Meta 规模还会受到文件名长度、目录深度、扩展属性、ACL、快照、文件变更频率和产品版本影响。正式方案要结合厂商 sizing、压测和容量增长模型复核。</p><h2 id="6.-1-%E4%B8%AA%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E8%BF%98%E6%98%AF%E5%A4%9A%E4%B8%AA%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F" tabindex="-1">6. 1 个文件系统还是多个文件系统</h2><p>1 个大文件系统的优势是命名空间统一、业务迁移简单、训练任务不用处理多挂载点。适合数据域清晰、权限模型简单、希望统一治理的数据平台。</p><p>多个文件系统的优势是隔离更强。不同业务可以拥有独立 Volume、独立挂载 Token、独立 Bucket、独立对象存储凭据、独立配额和审计策略。适合多租户、多团队、强权限边界或不同生命周期的数据域。</p><p>但是多个文件系统不是免费的。它会增加凭据分发、挂载管理、数据复制、跨域访问、监控告警和运维排障复杂度。尤其是“每个文件系统都要承载数百亿文件”时，Meta 规模要按总文件数重新计算。</p><h2 id="7.-%E7%A4%BE%E5%8C%BA%E7%89%88%E4%B8%8E%E4%BC%81%E4%B8%9A%E7%89%88%E9%80%89%E6%8B%A9" tabindex="-1">7. 社区版与企业版选择</h2><p>社区版适合通用共享文件系统、对象存储接入和常规规模场景。Redis、TiKV、MySQL/PostgreSQL 等元数据后端各有适用边界，部署前要按文件数、并发和可用性目标评估。</p><p>当需求明确指向单文件系统数百亿文件、正式 SLA、分布式缓存、超大规模元数据和统一命名空间时，更合理的评审方式是引入企业版能力做 sizing。此时要关注分布式元数据、分布式缓存、主动失效、容量扩展和服务支持边界。</p><p>可以把方案分成 4 类：</p><table><thead><tr><th>方案</th><th>文件系统数量</th><th>版本形态</th><th>适用判断</th></tr></thead><tbody><tr><td>A</td><td>1 个</td><td>社区版</td><td>常规规模可讨论，数百亿文件级单命名空间不建议作为正式承诺</td></tr><tr><td>B</td><td>多个</td><td>社区版</td><td>可用于业务隔离，但如果每个都极大，总工程复杂度很高</td></tr><tr><td>C</td><td>1 个</td><td>企业版</td><td>更适合统一命名空间、超大文件数、正式 SLA 和集中治理</td></tr><tr><td>D</td><td>多个</td><td>企业版</td><td>仅在强隔离或明确多租户要求下讨论，总文件数会显著放大 Meta</td></tr></tbody></table><p>如果原始需求是“一个统一文件系统承载数百亿文件”，优先评估企业版 1FS。如果需求是“多个租户必须强隔离”，再评估企业版多 FS，并按总文件数重新计算 Meta。</p><h2 id="8.-%E7%94%9F%E4%BA%A7%E8%90%BD%E5%9C%B0%E5%BB%BA%E8%AE%AE" tabindex="-1">8. 生产落地建议</h2><p>第一，容量层按水位和故障域规划，不要只按裸容量除一下。至少保留正常水位、临时故障水位和扩容触发水位。</p><p>第二，接入层按 GPU 客户端规模和吞吐规划。Gateway/Cache 要随着客户端并发扩展，不要被文件系统数量误导。</p><p>第三，Meta 按总文件数规划。拆分文件系统是隔离手段，不是减少总元数据的手段。</p><p>第四，读多写少数据集要重视 Cache、warmup 和热点治理。checkpoint、小文件和中间产物要单独建模，不要混在同一个平均吞吐目标里。</p><p>第五，所有容量和性能数字都要经过业务压测闭环验证。方案估算只能决定初始规模，不能替代真实 workload 验证。</p><h2 id="9.-%E5%B0%8F%E7%BB%93" tabindex="-1">9. 小结</h2><p>千卡级 AI 集群的 JuiceFS 规划，核心不是“1FS 还是 10FS”这个表面选择，而是把容量、接入、Meta、缓存和隔离拆开计算。</p><p>对象存储节点主要跟容量、水位和故障域走；Gateway/Cache 主要跟 GPU 客户端和吞吐走；Meta 主要跟总文件数走；文件系统数量主要跟权限、命名空间和治理边界走。</p><p>只要这 4 条线拆清楚，方案评审就不会被“文件系统数量”和“节点数量”的简单乘法带偏。</p><p>参考资料：</p><ul><li><a href="https://juicefs.com/docs/community/architecture/" target="_blank">JuiceFS Architecture</a></li><li><a href="https://juicefs.com/docs/community/guide/cache/" target="_blank">JuiceFS Cache</a></li><li><a href="https://juicefs.com/docs/community/command_reference/" target="_blank">JuiceFS Command Reference</a></li><li><a href="https://juicefs.com/docs/community/redis_best_practices/" target="_blank">JuiceFS Redis Best Practices</a></li><li><a href="https://juicefs.com/en/product/enterprise-edition/" target="_blank">JuiceFS Enterprise Edition</a></li></ul>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第二章-JuiceFS客户端性能压测]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-er-zhang--juicefs-ke-hu-duan-xing-neng-ya-ce" />
                <id>tag:https://blog.yongjie.top,2026-05-23:di-er-zhang--juicefs-ke-hu-duan-xing-neng-ya-ce</id>
                <published>2026-05-23T13:34:03+08:00</published>
                <updated>2026-07-02T17:34:36+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="juicefs-%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%80%A7%E8%83%BD%E5%8E%8B%E6%B5%8B%E6%96%B9%E6%B3%95%EF%BC%9A%E9%A1%BA%E5%BA%8F%E5%90%9E%E5%90%90%E3%80%814k-%E9%9A%8F%E6%9C%BA%E8%AF%BB%E5%86%99%E4%B8%8E-nfs-%E5%AF%B9%E7%85%A7%E5%88%86%E6%9E%90" tabindex="-1">JuiceFS 客户端性能压测方法：顺序吞吐、4K 随机读写与 NFS 对照分析</h1><p>存储压测最容易踩的坑，是把不同测试口径的数字放在一张表里直接排名。对 JuiceFS 这类“客户端 + 元数据引擎 + 对象存储”的架构来说，顺序读写、随机读写、延迟约束、客户端缓存、对象存储回源、元数据压力都会影响结果。</p><p>本文只讨论客户端视角的压测方法和可公开汇总数据。原始终端截图不会直接公开；下文复用的是从项目报告中提取后裁剪脱敏的客户端 fio 结果片段，已去掉主机名、挂载路径、命令 prompt 和工具栏。</p><p><img src="/upload/2026/07/juicefs-client-benchmark-method.svg" alt="juicefs-client-benchmark-method" /></p><h2 id="1.-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E4%BB%8E%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%A7%86%E8%A7%92%E7%9C%8B" tabindex="-1">1. 为什么要从客户端视角看</h2><p>业务真正感知到的是客户端返回的吞吐、IOPS 和延迟，而不是存储集群后台某个监控面板上的聚合值。服务端聚合带宽可以证明后端集群有能力承载流量，但不能直接说明单个训练任务、单个数据加工任务或单个挂载点的体验。</p><p>客户端视角要回答 4 个问题：</p><ol><li>大块顺序写能否支撑 checkpoint、归档和大文件生成。</li><li>大块顺序读能否支撑训练样本扫描、模型权重加载和批处理读取。</li><li>4K 随机写能否承受小文件、索引、日志和元数据密集写入。</li><li>4K 随机读能否承受小样本、索引查询和随机访问。</li></ol><p>如果只看顺序读，很容易高估系统能力；如果只看 4K 随机写，又会低估对象存储后端在大文件场景里的价值。</p><h2 id="2.-%E6%B5%8B%E8%AF%95%E7%9F%A9%E9%98%B5%E8%AE%BE%E8%AE%A1" tabindex="-1">2. 测试矩阵设计</h2><p>建议至少保留两个测试矩阵。</p><p>第一组是吞吐与 IOPS 基线：</p><table><thead><tr><th>场景</th><th>块大小</th><th>访问模式</th><th>观察指标</th></tr></thead><tbody><tr><td>顺序写</td><td>1M</td><td>write</td><td>MB/s、平均延迟、错误</td></tr><tr><td>顺序读</td><td>1M</td><td>read</td><td>MB/s、平均延迟、缓存命中</td></tr><tr><td>随机写</td><td>4K</td><td>randwrite</td><td>IOPS、平均延迟、p95/p99</td></tr><tr><td>随机读</td><td>4K</td><td>randread</td><td>IOPS、平均延迟、p95/p99</td></tr></tbody></table><p>第二组是延迟受控测试。它不追求最大吞吐，而是把延迟压在业务可接受范围内，观察在该约束下能给多少吞吐或 IOPS。</p><table><thead><tr><th>场景</th><th>目标</th><th>适用业务</th></tr></thead><tbody><tr><td>1M 顺序写延迟受控</td><td>降低写入尾延迟</td><td>checkpoint、归档</td></tr><tr><td>1M 顺序读延迟受控</td><td>稳定大文件读取</td><td>样本扫描、权重加载</td></tr><tr><td>4K 随机写延迟受控</td><td>控制小写抖动</td><td>小文件生成、索引写入</td></tr><tr><td>4K 随机读延迟受控</td><td>控制随机访问时延</td><td>小样本、索引读取</td></tr></tbody></table><p>压测命令要固定并记录 <code>bs</code>、<code>iodepth</code>、<code>numjobs</code>、<code>runtime</code>、<code>direct</code>、文件大小、文件数量、挂载参数和缓存状态。否则同一套系统不同轮次之间也不能直接比较。</p><h2 id="3.-juicefs-%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BB%93%E6%9E%9C%E6%91%98%E8%A6%81" tabindex="-1">3. JuiceFS 客户端结果摘要</h2><p>以下数据来自客户端侧汇总表，单位已统一。过程截图中的局部值属于不同轮次或不同参数组合，公开稿以汇总表为准。</p><table><thead><tr><th>测试类型</th><th>场景</th><th>指标</th><th>客户端观测值</th><th>平均延迟</th></tr></thead><tbody><tr><td>带宽测试</td><td>1M 顺序写</td><td>MB/s</td><td>86.6</td><td>46.9ms</td></tr><tr><td>带宽测试</td><td>1M 顺序读</td><td>MB/s</td><td>5215</td><td>1.6ms</td></tr><tr><td>性能测试</td><td>4K 随机写</td><td>IOPS</td><td>2485</td><td>6.3ms</td></tr><tr><td>性能测试</td><td>4K 随机读</td><td>IOPS</td><td>55237</td><td>0.5ms</td></tr></tbody></table><p>延迟受控轮次如下：</p><table><thead><tr><th>测试类型</th><th>场景</th><th>指标</th><th>客户端观测值</th><th>平均延迟</th></tr></thead><tbody><tr><td>延迟测试</td><td>1M 顺序写</td><td>MB/s</td><td>97.6</td><td>21.4ms</td></tr><tr><td>延迟测试</td><td>1M 顺序读</td><td>MB/s</td><td>5258</td><td>1.5ms</td></tr><tr><td>延迟测试</td><td>4K 随机写</td><td>IOPS</td><td>1286</td><td>4.1ms</td></tr><tr><td>延迟测试</td><td>4K 随机读</td><td>IOPS</td><td>27543</td><td>0.5ms</td></tr></tbody></table><p>这组结果的第一结论不是“某个数字绝对好或坏”，而是读写画像很清楚：顺序读和随机读表现更突出，写入路径更受对象上传、元数据提交、flush、缓存策略和后端写入能力影响。</p><h3 id="3.1-juicefs-%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%88%AA%E5%9B%BE" tabindex="-1">3.1 JuiceFS 客户端截图</h3><p>以下截图来自项目报告中的客户端 fio 结果，已做公开版裁剪，只保留测试结果区域。</p><p><img src="/upload/2026/07/juicefs-client-seq-write-sanitized.png" alt="juicefs-client-seq-write-sanitized" /><br /><img src="/upload/2026/07/juicefs-client-seq-read-sanitized.png" alt="juicefs-client-seq-read-sanitized" /><br /><img src="/upload/2026/07/juicefs-client-rand-write-latency-sanitized.png" alt="juicefs-client-rand-write-latency-sanitized" /><br /><img src="/upload/2026/07/juicefs-client-rand-read-sanitized.png" alt="juicefs-client-rand-read-sanitized" /></p><h2 id="4.-nfs-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%AF%B9%E7%85%A7%E7%BB%93%E6%9E%9C" tabindex="-1">4. NFS 客户端对照结果</h2><p>为了避免跨口径误读，这里只放物理客户端汇总表中可以公开表达的 NFS 客户端观测值，不引入服务端聚合截图。</p><table><thead><tr><th>测试类型</th><th>场景</th><th>指标</th><th>客户端观测值</th><th>平均延迟</th></tr></thead><tbody><tr><td>带宽测试</td><td>1M 顺序写</td><td>MB/s</td><td>104</td><td>20ms</td></tr><tr><td>带宽测试</td><td>1M 顺序读</td><td>MB/s</td><td>4515</td><td>1.85ms</td></tr><tr><td>性能测试</td><td>4K 随机写</td><td>IOPS</td><td>885</td><td>8.9ms</td></tr><tr><td>性能测试</td><td>4K 随机读</td><td>IOPS</td><td>70.5k</td><td>2.7ms</td></tr></tbody></table><p>延迟受控轮次如下：</p><table><thead><tr><th>测试类型</th><th>场景</th><th>指标</th><th>客户端观测值</th><th>平均延迟</th></tr></thead><tbody><tr><td>延迟测试</td><td>4K 随机写</td><td>IOPS</td><td>1286</td><td>4.1ms</td></tr><tr><td>延迟测试</td><td>4K 随机读</td><td>IOPS</td><td>70.6k</td><td>1.8ms</td></tr></tbody></table><p>这张表只能作为同类测试项的参考，不能扩展成“所有场景下 NFS 与 JuiceFS 的绝对优劣”。NFS 的实现方式、服务端导出参数、客户端数量、缓存状态、网络路径和后端磁盘都会改变结果。</p><h3 id="4.1-%E4%B8%BA%E4%BB%80%E4%B9%88%E5%8A%A0%E4%B8%80%E5%B1%82-nfs-%E5%90%8E%E4%BC%9A%E5%8F%98%E6%85%A2" tabindex="-1">4.1 为什么加一层 NFS 后会变慢</h3><p>本次 NFS 口径里，客户端不是直接访问后端分布式存储能力，而是先访问 NFS 导出层，再由 NFS 服务端访问后端存储。链路从“客户端并发访问存储后端”变成了“客户端 -&gt; NFS 服务端 -&gt; 后端存储”，多了一次协议转换、一次网络转发和一层服务端调度。</p><p>这层 NFS 服务端会变成明显的收敛点。即使后端存储集群具备更高聚合带宽，客户端看到的吞吐也会受 NFS 服务端 CPU、内存、网卡、内核 NFS 线程、导出参数、页缓存、单连接/多连接行为和后端挂载性能限制。换句话说，后端有多强，不等于经过 NFS 导出后客户端就能吃满。</p><p>写入场景尤其容易被放大。NFS 需要处理客户端请求、权限检查、锁、属性更新、写入提交和缓存一致性；如果导出配置偏同步、服务端后端写入延迟偏高，1M 顺序写和 4K 随机写都会被服务端等待链路拖慢。小块随机写还会进一步放大 RPC 次数和元数据更新次数，所以随机写 IOPS 往往比直连或客户端原生并发路径更难看。</p><p>读取场景也不是总能靠缓存解决。热数据可能被 NFS 服务端页缓存加速，但冷读、跨客户端读、随机读和缓存失效后仍要回到后端存储。多客户端并发读取时，NFS 服务端既要承担网络出口，又要承担后端读取和缓存管理，很容易从“共享入口”变成“共享瓶颈”。</p><p>因此，这里看到的 NFS 性能偏低，并不是简单说明“NFS 协议一定差”，而是说明在本项目这种二次导出链路下，NFS 更适合作为兼容性入口或少量通用共享目录，不适合作为高并发 AI 数据面主路径。AI 训练、样本扫描、checkpoint 和大量小文件读写，应该优先让客户端直接接入 JuiceFS/CephFS/对象存储网关等能横向扩展的数据路径。</p><h3 id="4.2-nfs-%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%88%AA%E5%9B%BE" tabindex="-1">4.2 NFS 客户端截图</h3><p>以下截图同样来自项目报告中的物理客户端 fio 结果，已做公开版裁剪。它们用于证明对照数据的来源，不引入服务端监控聚合图。</p><p><img src="/upload/2026/07/nfs-client-seq-write-sanitized.png" alt="nfs-client-seq-write-sanitized" /><br /><img src="/upload/2026/07/nfs-client-seq-read-sanitized.png" alt="nfs-client-seq-read-sanitized" /><br /><img src="/upload/2026/07/nfs-client-rand-write-sanitized.png" alt="nfs-client-rand-write-sanitized" /><br /><img src="/upload/2026/07/nfs-client-rand-read-sanitized.png" alt="nfs-client-rand-read-sanitized" /></p><h2 id="5.-%E5%A6%82%E4%BD%95%E8%A7%A3%E9%87%8A%E8%BF%99%E7%BB%84%E6%95%B0%E6%8D%AE" tabindex="-1">5. 如何解释这组数据</h2><p>JuiceFS 顺序读表现较好，通常来自对象存储并发读取、客户端 buffer、预读和缓存的组合收益。读多写少的数据集、模型权重、训练样本扫描更容易发挥这类优势。</p><p>JuiceFS 写入要经历客户端 buffer、对象存储上传、元数据提交和 flush 过程。对于小文件随机写，写入延迟更容易受对象存储回写、元数据压力和文件碎片影响。大量小文件写入时，不要只调大并发，还要观察 Redis 延迟、RGW 上传延迟、客户端 buffer 是否拥塞。</p><p>NFS 对照结果则说明，给后端存储再包一层通用文件共享入口，会牺牲一部分横向扩展能力。它能降低客户端接入复杂度，但也把并发、缓存、锁和网络出口集中到 NFS 服务端，适合通用共享和兼容性场景，不适合把高并发训练数据面全部压在这一层。</p><p><code>--prefetch</code> 和 <code>--max-readahead</code> 对顺序读、局部随机读有帮助，但也可能引入读放大。读取稀疏大文件时，预取过大反而会浪费对象存储带宽。</p><p><code>--cache-size</code> 可以提升重复读取和随机读体验，但本地缓存不等于端到端数据保护。对缓存盘可靠性敏感的场景，要评估 <code>--verify-cache-checksum</code> 等完整性策略。</p><p><code>--writeback</code> 能改善某些小文件写入体验，但会把数据可靠性压力转移到本地缓存盘。关键数据、跨节点共享和生产强一致场景不要轻易启用。</p><h2 id="6.-%E9%9D%A2%E5%90%91-ai-%E4%B8%9A%E5%8A%A1%E7%9A%84%E6%98%A0%E5%B0%84" tabindex="-1">6. 面向 AI 业务的映射</h2><p>训练数据集通常以读为主。如果数据集已经固化，可以使用缓存盘、预热和较大的 readahead 提高吞吐。此时重点观察顺序读、随机读、cache hit、对象存储回源带宽。</p><p>checkpoint 是大块写入，对象存储写带宽、flush 延迟和写入并发更关键。此时要重点观察 1M 或更大块顺序写、尾延迟、失败重试和写入完成后的跨客户端可见性。</p><p>小文件样本和索引会放大元数据压力。此时 4K 随机读写只是一个近似入口，还要补充真实目录层级、文件数量、文件大小分布和并发客户端数。</p><p>日志、临时文件和中间产物不一定适合全部放入共享文件系统。高频短生命周期数据可以考虑本地盘、临时卷或专门的缓存层，最终产物再落 JuiceFS。</p><h2 id="7.-%E5%B0%8F%E7%BB%93" tabindex="-1">7. 小结</h2><p>JuiceFS 压测要从“客户端实际体验”出发，而不是追求单个最大数字。顺序读、顺序写、随机读、随机写、延迟受控轮次要分别解释。</p><p>参考资料：</p><ul><li><a href="https://juicefs.com/docs/community/benchmark/" target="_blank">JuiceFS Benchmark and Profiling</a></li><li><a href="https://juicefs.com/docs/community/guide/cache/" target="_blank">JuiceFS Cache</a></li><li><a href="https://juicefs.com/docs/community/command_reference/" target="_blank">JuiceFS Command Reference</a></li><li><a href="https://juicefs.com/docs/community/fault_diagnosis_and_analysis/" target="_blank">JuiceFS Fault Diagnosis and Analysis</a></li></ul>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第一章-JuiceFS生产实践]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-yi-zhang--juicefs-sheng-chan-shi-jian" />
                <id>tag:https://blog.yongjie.top,2026-05-04:di-yi-zhang--juicefs-sheng-chan-shi-jian</id>
                <published>2026-05-04T19:48:28+08:00</published>
                <updated>2026-07-02T14:49:17+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="juicefs-%E5%9C%A8-ceph-rgw-%E4%B8%8A%E7%9A%84%E7%94%9F%E4%BA%A7%E9%83%A8%E7%BD%B2%E5%AE%9E%E8%B7%B5%EF%BC%9Aredis-cluster-%E5%85%83%E6%95%B0%E6%8D%AE%E4%B8%8E-s3-%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8%E8%A7%A3%E8%80%A6" tabindex="-1">JuiceFS 在 Ceph RGW 上的生产部署实践：Redis Cluster 元数据与 S3 对象存储解耦</h1><p>在 AI 训练、数据湖、离线分析和多节点共享数据场景里，单机文件系统很快会遇到容量、并发和多客户端一致性问题。JuiceFS 的价值在于把文件系统语义拆成两层：元数据放在独立的 Metadata Engine，文件数据切块后落到对象存储。</p><p>本文以 Redis Cluster + Ceph RGW/S3 + JuiceFS Client 的组合为例，整理一套可以落地的生产部署方法。文中的地址、凭据、卷名、Bucket 和路径均使用占位符，实际发布和交付时不要把真实环境信息写入文档、命令历史或截图。</p><p><img src="/upload/2026/07/juicefs-redis-rgw-production-architecture.svg" alt="juicefs-redis-rgw-production-architecture" /></p><h2 id="1.-%E6%9E%B6%E6%9E%84%E6%8B%86%E5%88%86" tabindex="-1">1. 架构拆分</h2><p>这套架构可以分成 4 个平面。</p><p>第一层是业务客户端。训练任务、推理服务、数据加工任务通过 POSIX 接口访问挂载点，不需要感知对象存储的分片、命名和生命周期。</p><p>第二层是 JuiceFS Client。客户端负责 FUSE 挂载、元数据访问、读写缓冲、预读、本地缓存、对象上传下载和后台任务。</p><p>第三层是 Redis Cluster。Redis 保存文件名、目录树、inode、权限、chunk/slice/block 映射、会话等元数据。Redis 需要按核心生产组件对待，必须做持久化、备份、访问控制和容量监控。</p><p>第四层是 Ceph RGW/S3。对象存储保存真实数据块。RGW 可以通过同一个 Ceph 集群、同一个 RGW zone 内的多个入口做负载均衡，但不要跨异步复制 zone 或跨独立集群做同一个 JuiceFS 卷的数据入口。</p><h2 id="2.-%E7%94%9F%E4%BA%A7%E5%89%8D%E7%BD%AE%E6%9D%A1%E4%BB%B6" tabindex="-1">2. 生产前置条件</h2><p>Redis Cluster 要开启持久化，并设置 <code>maxmemory-policy noeviction</code>。如果 Redis 因内存策略主动淘汰元数据，文件系统会出现严重风险。Redis 的 RDB/AOF、快照、备份恢复演练都要纳入常规运维。</p><p>Ceph RGW 建议启用 HTTPS，对外提供稳定的 S3 endpoint。内网测试环境可以使用 HTTP，但公开最佳实践不要把 HTTP 写成生产推荐。Bucket 建议按文件系统隔离，至少要做到不同业务使用不同访问凭据。</p><p>JuiceFS Client 建议统一版本，并在上线前完成 <code>juicefs bench</code>、业务 fio/vdbench、长稳读写、重启自动挂载、元数据恢复、RGW 故障切换等验证。</p><h2 id="3.-%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%88%9B%E5%BB%BA%E7%A4%BA%E4%BE%8B" tabindex="-1">3. 文件系统创建示例</h2><p>创建文件系统时，命令里不要直接写真实密钥。更稳妥的做法是通过受控的环境变量、密钥管理系统或 systemd EnvironmentFile 注入。</p><pre><code class="language-bash">export META_PASSWORD=&#39;&lt;redis-password&gt;&#39;export S3_ACCESS_KEY=&#39;&lt;s3-access-key&gt;&#39;export S3_SECRET_KEY=&#39;&lt;s3-secret-key&gt;&#39;juicefs format \  --storage s3 \  --bucket https://&lt;bucket&gt;.&lt;rgw-domain&gt; \  --access-key &quot;$S3_ACCESS_KEY&quot; \  --secret-key &quot;$S3_SECRET_KEY&quot; \  --trash-days 1 \  --capacity &lt;capacity-gib&gt; \  --inodes &lt;inode-limit&gt; \  &quot;redis://&lt;redis-user&gt;@&lt;redis-cluster-endpoints&gt;/&lt;fs-prefix&gt;&quot; \  &lt;volume-name&gt;</code></pre><p><code>--capacity</code> 的单位是 GiB，<code>--inodes</code> 用来限制 inode 数量。它们是文件系统配额，不会自动发现 Ceph/RGW 的真实容量。设置这两个值的意义是让容量展示、配额治理和业务预期保持一致。</p><p><code>--trash-days</code> 默认会启用回收站。回收站可以降低误删风险，但会增加对象存储实际占用。高频覆盖写或删除场景要同时监控逻辑用量和对象存储用量。</p><h2 id="4.-%E6%8C%82%E8%BD%BD%E5%8F%82%E6%95%B0%E5%88%86%E5%B1%82" tabindex="-1">4. 挂载参数分层</h2><p>生产环境不要把某一组参数当成所有场景的唯一答案。JuiceFS 的缓存参数本质上是在一致性可见性、元数据压力、对象存储读放大和吞吐之间做取舍。</p><h3 id="4.1-%E4%B8%80%E8%87%B4%E6%80%A7%E4%BC%98%E5%85%88%E9%85%8D%E7%BD%AE" tabindex="-1">4.1 一致性优先配置</h3><p>多客户端同时写同一批目录、对跨客户端可见性敏感、或者排障阶段希望减少缓存变量时，可以使用保守配置。</p><pre><code class="language-bash">export META_PASSWORD=&#39;&lt;redis-password&gt;&#39;juicefs mount \  --attr-cache=0 \  --entry-cache=0 \  --dir-entry-cache=0 \  --negative-entry-cache=0 \  --open-cache=0 \  --prefetch=0 \  --max-readahead=0 \  --cache-size=0 \  &quot;redis://&lt;redis-user&gt;@&lt;redis-cluster-endpoints&gt;/&lt;fs-prefix&gt;&quot; \  /mnt/&lt;volume-name&gt;</code></pre><p>这组参数会减少内核元数据缓存、客户端 open 缓存、本地数据缓存和预取带来的变量，但代价是 Redis 请求、对象存储请求和客户端延迟都会上升。它适合一致性排查和强可见性场景，不适合直接作为所有读密集任务的性能配置。</p><h3 id="4.2-%E8%AF%BB%E5%AF%86%E9%9B%86%E9%85%8D%E7%BD%AE" tabindex="-1">4.2 读密集配置</h3><p>AI 数据集、模型权重、离线样本等大量只读或读多写少场景，可以保留 close-to-open 语义，同时开启本地缓存、预读和预取来提升吞吐。</p><pre><code class="language-bash">export META_PASSWORD=&#39;&lt;redis-password&gt;&#39;juicefs mount \  --attr-cache=1 \  --entry-cache=1 \  --dir-entry-cache=1 \  --open-cache=0 \  --prefetch=1 \  --max-readahead=256 \  --cache-size=&lt;cache-size-mib&gt; \  --cache-dir /var/jfsCache/&lt;volume-name&gt; \  &quot;redis://&lt;redis-user&gt;@&lt;redis-cluster-endpoints&gt;/&lt;fs-prefix&gt;&quot; \  /mnt/&lt;volume-name&gt;</code></pre><p>如果业务明确是只读训练数据集，可以进一步评估 <code>--open-cache</code>、<code>juicefs warmup</code> 和更大的本地 SSD 缓存。但一旦存在多客户端频繁修改同一批文件，就要谨慎放大元数据缓存 TTL。</p><h3 id="4.3-%E5%90%8E%E5%8F%B0%E4%BB%BB%E5%8A%A1%E9%85%8D%E7%BD%AE" tabindex="-1">4.3 后台任务配置</h3><p><code>--no-bgjob</code> 不是一致性开关，而是运维负载分配开关。它可以让重负载业务客户端不承担后台清理、session 清理、回收站过期处理和元数据备份等任务。</p><p>生产环境可以让业务客户端使用 <code>--no-bgjob</code>，但必须至少保留一个维护客户端或独立运维挂载点不加 <code>--no-bgjob</code>，并显式开启元数据备份。</p><pre><code class="language-bash">juicefs mount \  --backup-meta 1h \  &quot;redis://&lt;redis-user&gt;@&lt;redis-cluster-endpoints&gt;/&lt;fs-prefix&gt;&quot; \  /mnt/&lt;volume-name-maintenance&gt;</code></pre><p><code>--backup-meta</code> 默认间隔是 3600 秒，<code>0</code> 表示关闭。它能把 JuiceFS 元数据备份到对象存储，但不能替代 Redis 自身的 RDB/AOF、快照和恢复演练。</p><h2 id="5.-systemd-%E8%87%AA%E5%8A%A8%E6%8C%82%E8%BD%BD" tabindex="-1">5. systemd 自动挂载</h2><p>不建议用 <code>--update-fstab</code> 把包含凭据的 META URL 写入 <code>/etc/fstab</code>。生产环境更建议使用 systemd service，并把敏感变量放到权限受控的 EnvironmentFile。JuiceFS 支持用 <code>META_PASSWORD</code> 或 <code>META_PASSWORD_FILE</code> 提供元数据密码，因此 URL 中可以不直接出现密码。</p><pre><code class="language-ini"># /etc/systemd/system/juicefs-&lt;volume-name&gt;.service[Unit]Description=JuiceFS &lt;volume-name&gt; MountAfter=network-online.targetWants=network-online.target[Service]Type=simpleEnvironmentFile=/etc/juicefs/&lt;volume-name&gt;.envExecStartPre=/usr/bin/mkdir -p /mnt/&lt;volume-name&gt;ExecStart=/usr/local/bin/juicefs mount \  --attr-cache=0 \  --entry-cache=0 \  --dir-entry-cache=0 \  --negative-entry-cache=0 \  --open-cache=0 \  --prefetch=0 \  --max-readahead=0 \  --cache-size=0 \  redis://&lt;redis-user&gt;@&lt;redis-cluster-endpoints&gt;/&lt;fs-prefix&gt; \  /mnt/&lt;volume-name&gt;ExecStop=/bin/fusermount -uz /mnt/&lt;volume-name&gt;Restart=on-failureRestartSec=5[Install]WantedBy=multi-user.target</code></pre><p>环境文件示例：</p><pre><code class="language-bash"># /etc/juicefs/&lt;volume-name&gt;.envMETA_PASSWORD=&lt;redis-password&gt;S3_ACCESS_KEY=&lt;s3-access-key&gt;S3_SECRET_KEY=&lt;s3-secret-key&gt;</code></pre><p>如果使用的是 <code>redis://:&lt;password&gt;@...</code> 这类 URL 形式，应改成 <code>redis://&lt;redis-user&gt;@...</code> 或 <code>redis://...</code> 并由 <code>META_PASSWORD</code> 注入密码，避免凭据出现在进程参数、配置文件和审计日志里。</p><pre><code class="language-bash">sudo chown root:root /etc/juicefs/&lt;volume-name&gt;.envsudo chmod 600 /etc/juicefs/&lt;volume-name&gt;.envsudo systemctl daemon-reloadsudo systemctl enable --now juicefs-&lt;volume-name&gt;.service</code></pre><p>如果系统使用 <code>fusermount3</code>，需要把 <code>ExecStop</code> 改成对应路径。不同发行版的 FUSE 包名和命令路径可能不同，上线前要在目标 OS 上验证。</p><h2 id="6.-%E5%A4%9A%E4%B8%9A%E5%8A%A1%E9%9A%94%E7%A6%BB" tabindex="-1">6. 多业务隔离</h2><p>持有元数据地址和密码的客户端，基本等同于拥有该 JuiceFS 文件系统的管理能力。多业务共享时，不要只依赖 Linux 用户权限做隔离。</p><p>推荐模型是“一个业务域一个文件系统”。每个文件系统使用独立 Volume Name、独立 Redis ACL 用户、独立 key 前缀、独立 S3 凭据，最好使用独立 Bucket。</p><pre><code class="language-text">业务 A -&gt; Redis ACL user-a + key prefix {a}* -&gt; bucket-a -&gt; mount-a业务 B -&gt; Redis ACL user-b + key prefix {b}* -&gt; bucket-b -&gt; mount-b业务 C -&gt; Redis ACL user-c + key prefix {c}* -&gt; bucket-c -&gt; mount-c</code></pre><p>在 Redis Cluster 场景里，URL 里的 <code>/&lt;fs-prefix&gt;</code> 更适合理解成 key 前缀或 hash tag 隔离，而不是传统单机 Redis 的多 DB 隔离。ACL 要在 Redis 节点上限制对应 key 前缀，并配合网络 ACL 限制客户端来源。</p><h2 id="7.-%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%BF%90%E7%BB%B4%E9%97%AD%E7%8E%AF" tabindex="-1">7. 监控与运维闭环</h2><p>JuiceFS 客户端侧需要关注吞吐、延迟、buffer、cache 命中、对象存储错误和后台任务状态。常用命令包括 <code>juicefs stats</code>、<code>juicefs profile</code>、<code>juicefs status</code>、<code>juicefs info</code>。</p><p>Redis 侧要监控内存、key 数、持久化状态、主从复制、Cluster 状态、慢查询、连接数和过期/淘汰风险。<code>noeviction</code>、持久化成功率和恢复演练比单纯看存活更重要。</p><p>RGW/S3 侧要监控 4xx/5xx、请求延迟、带宽、连接重试、bucket 用量、对象数量和多 part 上传异常。负载均衡必须限定在同一个 Ceph 集群和同一个 RGW zone 内。</p><p>日志要接入 logrotate 或集中日志系统。root 用户通常写 <code>/var/log/juicefs.log</code>，非 root 用户通常写 <code>$HOME/.juicefs/juicefs.log</code>，具体路径以实际版本为准。</p><h2 id="8.-%E5%B0%8F%E7%BB%93" tabindex="-1">8. 小结</h2><p>Redis Cluster + Ceph RGW/S3 + JuiceFS Client 是一套工程上清晰的共享文件系统架构。真正决定稳定性的不是单条 mount 命令，而是元数据保护、对象存储可靠性、缓存策略、后台任务、自动挂载和监控告警能否形成闭环。</p><p>一致性优先参数适合强可见性和排障场景，读密集任务则应该结合业务访问模式开启缓存和预读。生产部署时要把参数写成“场景化策略”，而不是一组固定模板。</p><p>参考资料：</p><ul><li><a href="https://juicefs.com/docs/community/architecture/" target="_blank">JuiceFS Architecture</a></li><li><a href="https://juicefs.com/docs/community/guide/cache/" target="_blank">JuiceFS Cache</a></li><li><a href="https://juicefs.com/docs/community/command_reference/" target="_blank">JuiceFS Command Reference</a></li><li><a href="https://juicefs.com/docs/community/redis_best_practices/" target="_blank">JuiceFS Redis Best Practices</a></li><li><a href="https://juicefs.com/docs/community/metadata_dump_load/" target="_blank">JuiceFS Metadata Backup and Recovery</a></li><li><a href="https://juicefs.com/docs/community/reference/how_to_set_up_object_storage/" target="_blank">JuiceFS Object Storage Setup</a></li></ul>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第三章-vLLM-BenchServe压测与性能分析]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-san-zhang--vllm-benchserve-ya-ce-yu-xing-neng-fen-xi" />
                <id>tag:https://blog.yongjie.top,2026-04-26:di-san-zhang--vllm-benchserve-ya-ce-yu-xing-neng-fen-xi</id>
                <published>2026-04-26T21:11:12+08:00</published>
                <updated>2026-06-30T17:12:29+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="qwen3-235b-vllm-bench-serve-%E5%8E%8B%E6%B5%8B%E4%B8%8E%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90" tabindex="-1">Qwen3-235B vLLM Bench Serve 压测与性能分析</h1><p>这篇文章整理的是 Qwen3-235B-A22B-FP4 在 vLLM OpenAI API 上的压测方法与结果解读。项目资料里使用 <code>vllm bench serve</code> 做统一 workload 压测，并对 CX8 与 89144 两套平台做了同口径对比。为了公开发布，文中隐藏真实 endpoint、路径、端口和内部脚本目录，只保留测试方法、参数口径和聚合指标。</p><p><img src="/upload/2026/06/vllm-qwen3-benchmark-flow.svg" alt="vllm-qwen3-benchmark-flow" /></p><h2 id="%E4%B8%80%E3%80%81%E6%8E%A8%E7%90%86%E5%8E%8B%E6%B5%8B%E4%B8%8D%E8%83%BD%E5%8F%AA%E7%9C%8B-tok%2Fs" tabindex="-1">一、推理压测不能只看 tok/s</h2><p>很多大模型推理压测只展示 <code>tokens/s</code>，这很容易误导。推理服务的用户体验至少由四类指标共同决定：</p><table><thead><tr><th>指标</th><th>含义</th><th>关注点</th></tr></thead><tbody><tr><td>TTFT</td><td>Time To First Token，首 token 延迟</td><td>用户是否等得住</td></tr><tr><td>TPOT</td><td>Time Per Output Token，输出阶段每 token 延迟</td><td>decode 是否顺滑</td></tr><tr><td>E2E latency</td><td>端到端请求延迟</td><td>请求整体完成时间</td></tr><tr><td>tok/s / QPM / TPM</td><td>吞吐指标</td><td>系统容量和成本效率</td></tr></tbody></table><p>对于长输入场景，TTFT 受 prefill 影响很大；对于长输出场景，TPOT 更能反映 decode 性能；对于高并发，排队和调度会显著影响 P95/P99。只看平均值或峰值都不够。</p><h2 id="%E4%BA%8C%E3%80%81%E6%B5%8B%E8%AF%95%E5%AF%B9%E8%B1%A1%E5%92%8C%E6%9C%8D%E5%8A%A1%E5%8F%82%E6%95%B0" tabindex="-1">二、测试对象和服务参数</h2><table><thead><tr><th>项目</th><th>说明</th></tr></thead><tbody><tr><td>模型</td><td>Qwen3-235B-A22B-FP4</td></tr><tr><td>Serving</td><td>vLLM OpenAI-compatible API</td></tr><tr><td>并行</td><td>单机 TP8 / PP1</td></tr><tr><td>数据集</td><td>random synthetic dataset</td></tr><tr><td>接口</td><td><code>/v1/completions</code></td></tr><tr><td>工具</td><td><code>vllm bench serve</code></td></tr></tbody></table><p>服务启动参数核心如下：</p><pre><code class="language-bash">vllm serve &lt;model-path&gt; \  --served-model-name Qwen3-235B \  --host 0.0.0.0 \  --port &lt;api-port&gt; \  --tensor-parallel-size 8 \  --enable-expert-parallel \  --max-num-seqs 32 \  --max-model-len 8192 \  --max-num-batched-tokens 8192 \  --gpu-memory-utilization 0.9 \  --enable-chunked-prefill \  --no-enable-prefix-caching \  --kv-cache-dtype fp8 \  --trust-remote-code</code></pre><p>项目里还记录了一组 forced-RoCE/Spectrum-X 通信参数，其中比较关键的是：</p><pre><code class="language-bash">NCCL_P2P_DISABLE=1NCCL_SHM_DISABLE=1NCCL_IB_GID_INDEX=3NCCL_CROSS_NIC=1NCCL_IB_ADAPTIVE_ROUTING=1NCCL_NET_PLUGIN=&lt;spectrum-x-plugin&gt;</code></pre><p>这里要特别注意：<code>NCCL_P2P_DISABLE=1</code> 会禁用本机 GPU P2P，使 TP8 decode 中频繁的 GPU 间通信更可能走 NET/IB 路径。它是解释性能差异的重要假设，但最终必须通过单变量 A/B 验证，不能只凭参数推断下结论。</p><h2 id="%E4%B8%89%E3%80%81vllm-bench-serve-%E5%91%BD%E4%BB%A4%E6%A8%A1%E6%9D%BF" tabindex="-1">三、vLLM Bench Serve 命令模板</h2><p><code>vllm bench serve</code> 的价值是把请求形态、并发、结果保存和百分位指标固定下来：</p><pre><code class="language-bash">vllm bench serve \  --backend openai \  --model Qwen3-235B \  --tokenizer &lt;model-path&gt; \  --endpoint /v1/completions \  --host &lt;api-host&gt; \  --port &lt;api-port&gt; \  --dataset-name random \  --random-input-len 4000 \  --random-output-len 1000 \  --ignore-eos \  --max-concurrency 16 \  --num-prompts 64 \  --request-rate 10000 \  --percentile-metrics ttft,tpot,itl,e2el \  --metric-percentiles 50,90,95,99 \  --save-result \  --save-detailed \  --plot-timeline \  --plot-dataset-stats \  --result-dir ./results/public \  --result-filename qwen3_4000_1000_c16.json</code></pre><p>几个参数需要解释：</p><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>--dataset-name random</code></td><td>用固定 token 长度生成合成请求，便于复现</td></tr><tr><td><code>--ignore-eos</code></td><td>尽量让请求生成到指定输出长度</td></tr><tr><td><code>--max-concurrency</code></td><td>控制最大并发，而不是无限冲击服务</td></tr><tr><td><code>--num-prompts</code></td><td>请求总数，过少容易被偶发抖动影响</td></tr><tr><td><code>--request-rate 10000</code></td><td>近似 burst 压测，观察系统可承载能力</td></tr><tr><td><code>--save-detailed</code></td><td>保存单请求明细，便于分析长尾</td></tr><tr><td><code>--percentile-metrics</code></td><td>输出 TTFT、TPOT、ITL、E2E 的分位数</td></tr></tbody></table><h2 id="%E5%9B%9B%E3%80%81%E5%BD%93%E5%89%8D%E4%B8%A4%E7%BB%84%E7%BB%93%E6%9E%9C" tabindex="-1">四、当前两组结果</h2><p>CX8 架构当前完成了两组同口径测试，每组 64 请求，全部成功：</p><table><thead><tr><th>case</th><th style="text-align:right">completed</th><th style="text-align:right">failed</th><th style="text-align:right">avg latency ms</th><th style="text-align:right">p95 latency ms</th><th style="text-align:right">avg TTFT ms</th><th style="text-align:right">p95 TTFT ms</th><th style="text-align:right">avg TPOT ms</th><th style="text-align:right">p95 TPOT ms</th><th style="text-align:right">QPM</th><th style="text-align:right">TPM</th><th style="text-align:right">completion tok/s</th><th style="text-align:right">total tok/s</th></tr></thead><tbody><tr><td><code>1000/300/c6</code></td><td style="text-align:right">64</td><td style="text-align:right">0</td><td style="text-align:right">10,148.55</td><td style="text-align:right">10,425.89</td><td style="text-align:right">517.11</td><td style="text-align:right">717.29</td><td style="text-align:right">32.21</td><td style="text-align:right">34.02</td><td style="text-align:right">34.62</td><td style="text-align:right">45,007.20</td><td style="text-align:right">173.10</td><td style="text-align:right">750.12</td></tr><tr><td><code>4000/1000/c16</code></td><td style="text-align:right">64</td><td style="text-align:right">0</td><td style="text-align:right">43,333.27</td><td style="text-align:right">45,863.40</td><td style="text-align:right">2,436.43</td><td style="text-align:right">5,451.42</td><td style="text-align:right">40.94</td><td style="text-align:right">42.35</td><td style="text-align:right">22.14</td><td style="text-align:right">110,679.00</td><td style="text-align:right">368.93</td><td style="text-align:right">1,844.65</td></tr></tbody></table><p>这两组 workload 的含义不同：</p><p><code>1000/300/c6</code> 更接近短输入、短输出、中等并发，用来看 TTFT、排队和 decode 轻负载表现。</p><p><code>4000/1000/c16</code> 更重，输出更长，并发更高，更能放大 decode 阶段每 token 延迟和通信路径差异。</p><h2 id="%E4%BA%94%E3%80%81cx8-%E4%B8%8E-89144-%E5%AF%B9%E6%AF%94%EF%BC%88%E8%BF%99%E9%87%8C%E6%98%AF%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%9C%BA%E5%9E%8B%E5%AF%B9%E6%AF%94%EF%BC%89" tabindex="-1">五、CX8 与 89144 对比（这里是服务器机型对比）</h2><p>89144 的同口径手工重跑结果。两组对比都显示 89144 在吞吐和 TPOT 上明显优于 CX8。</p><p>89144是可以混跑IB网络与P2P，CX8是GPU之间通信统一走IB网卡</p><h3 id="1000%2F300%2Fc6" tabindex="-1">1000/300/c6</h3><table><thead><tr><th>metric</th><th style="text-align:right">CX8</th><th style="text-align:right">89144</th><th>结论</th></tr></thead><tbody><tr><td>QPM</td><td style="text-align:right">34.62</td><td style="text-align:right">51.12</td><td>89144 高约 47.67%</td></tr><tr><td>TPM</td><td style="text-align:right">45,007.20</td><td style="text-align:right">66,465.00</td><td>89144 高约 47.68%</td></tr><tr><td>completion tok/s</td><td style="text-align:right">173.10</td><td style="text-align:right">255.64</td><td>89144 高约 47.68%</td></tr><tr><td>avg latency ms</td><td style="text-align:right">10,148.55</td><td style="text-align:right">6,874.84</td><td>89144 低约 32.26%</td></tr><tr><td>avg TTFT ms</td><td style="text-align:right">517.11</td><td style="text-align:right">464.14</td><td>89144 低约 10.24%</td></tr><tr><td>avg TPOT ms</td><td style="text-align:right">32.21</td><td style="text-align:right">21.44</td><td>89144 低约 33.44%</td></tr></tbody></table><h3 id="4000%2F1000%2Fc16" tabindex="-1">4000/1000/c16</h3><table><thead><tr><th>metric</th><th style="text-align:right">CX8</th><th style="text-align:right">89144</th><th>结论</th></tr></thead><tbody><tr><td>QPM</td><td style="text-align:right">22.14</td><td style="text-align:right">27.81</td><td>89144 高约 25.65%</td></tr><tr><td>TPM</td><td style="text-align:right">110,679.00</td><td style="text-align:right">139,068.60</td><td>89144 高约 25.65%</td></tr><tr><td>completion tok/s</td><td style="text-align:right">368.93</td><td style="text-align:right">463.56</td><td>89144 高约 25.65%</td></tr><tr><td>avg latency ms</td><td style="text-align:right">43,333.27</td><td style="text-align:right">34,494.69</td><td>89144 低约 20.40%</td></tr><tr><td>avg TTFT ms</td><td style="text-align:right">2,436.43</td><td style="text-align:right">2,466.93</td><td>两者接近，CX8 略低</td></tr><tr><td>avg TPOT ms</td><td style="text-align:right">40.94</td><td style="text-align:right">32.06</td><td>89144 低约 21.69%</td></tr></tbody></table><p>从这两张表可以看出，差距主要不在 TTFT，而在 TPOT/ITL，也就是 decode 阶段。</p><h2 id="%E5%85%AD%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E6%80%80%E7%96%91%E9%80%9A%E4%BF%A1%E8%B7%AF%E5%BE%84" tabindex="-1">六、为什么怀疑通信路径</h2><p>当前 CX8 服务使用 forced-RoCE 参数，其中 <code>NCCL_P2P_DISABLE=1</code> 和 <code>NCCL_SHM_DISABLE=1</code> 会让本机 TP8 中的 GPU 间通信更多走 NET/IB/GDRDMA。项目资料里也记录了一个现象：89144 一旦禁用 P2P，吞吐会接近 CX8；而保留 P2P 时，TPOT 和 completion tok/s 更好。</p><p>因此当前最值得优先验证的假设，不是模型、压测工具或 tokenizer，而是通信路径：</p><pre><code class="language-text">P2P/CUMEM 可用路径  -&gt; 本机 GPU 间 decode 通信更短  -&gt; TPOT 下降  -&gt; completion tok/s 上升forced-RoCE/NET 路径  -&gt; decode 阶段频繁通信经过 NET/IB  -&gt; 每 token 延迟升高  -&gt; E2E latency 被拉长</code></pre><p>当然，最终结论必须通过单变量 A/B 验证，不能只凭经验判断。</p><h2 id="%E4%B8%83%E3%80%81%E6%8E%A8%E8%8D%90-a%2Fb-%E5%AE%9E%E9%AA%8C" tabindex="-1">七、推荐 A/B 实验</h2><p>下一步建议只改一个变量：打开本机 P2P。</p><pre><code class="language-bash">NCCL_P2P_DISABLE=0NCCL_DEBUG=INFONCCL_DEBUG_SUBSYS=INIT,NET,GRAPH,ENV</code></pre><p>然后重跑：</p><pre><code class="language-text">1000/300/c64000/1000/c16</code></pre><p>观察四类证据：</p><table><thead><tr><th>证据</th><th>判断</th></tr></thead><tbody><tr><td>NCCL 日志</td><td>是否从 <code>NET/IB/GDRDMA</code> 转向更多 <code>P2P/CUMEM</code></td></tr><tr><td>TPOT / ITL</td><td>是否显著下降</td></tr><tr><td>completion tok/s</td><td>是否显著上升</td></tr><tr><td>E2E latency</td><td>是否随 TPOT 改善而下降</td></tr></tbody></table><p>如果打开 P2P 后 CX8 的 TPOT 明显下降，就可以把通信路径作为主要归因，并继续用相同 workload 复测 batch token、并发和插件版本等变量。</p><h2 id="%E5%85%AB%E3%80%81%E5%8E%8B%E6%B5%8B%E6%8A%A5%E5%91%8A%E5%BA%94%E8%AF%A5%E6%80%8E%E4%B9%88%E5%86%99" tabindex="-1">八、压测报告应该怎么写</h2><p>一份可复盘的 vLLM 压测报告建议至少包含：</p><ol><li>模型、量化格式、served name。</li><li>服务参数：TP、PP、max model len、max num seqs、batch tokens、KV cache dtype。</li><li>NCCL/RDMA 参数，尤其是 P2P、SHM、IB、GID、插件。</li><li>workload：input/output/concurrency/requests/request rate。</li><li>成功数、失败数、错误类型。</li><li>TTFT、TPOT、ITL、E2E 的 P50/P90/P95/P99。</li><li>QPM、TPM、completion tok/s、total tok/s。</li><li>是否预热，是否冷启动，是否存在 lazy compile。</li><li>原始 JSON 和图表路径。</li></ol><h2 id="%E4%B9%9D%E3%80%81%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" tabindex="-1">九、参考资料</h2><ul><li>vLLM Bench Serve：<a href="https://docs.vllm.ai/en/latest/cli/bench/serve/" target="_blank">https://docs.vllm.ai/en/latest/cli/bench/serve/</a></li><li>vLLM Benchmark CLI：<a href="https://docs.vllm.ai/en/latest/benchmarking/cli/" target="_blank">https://docs.vllm.ai/en/latest/benchmarking/cli/</a></li><li>vLLM Metrics：<a href="https://docs.vllm.ai/en/stable/design/metrics/" target="_blank">https://docs.vllm.ai/en/stable/design/metrics/</a></li></ul><h2 id="%E5%8D%81%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">十、总结</h2><p>Qwen3-235B 这组压测最有价值的地方，不是某个 tok/s 数字，而是展示了如何用 vLLM bench serve 固定 workload，用 TTFT/TPOT/E2E/吞吐矩阵拆解瓶颈，并进一步通过 NCCL 单变量 A/B 定位通信路径。</p><p>对于 TP8 这类强通信场景，decode 阶段的 TPOT 往往比平均延迟更能说明问题。只要把 workload、服务参数、NCCL 参数和结果指标记录完整，后续换平台、换驱动、换 NCCL 插件或调整 P2P/RoCE 策略，都可以用同一套方法复测。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第二章-DeepSeek-V4-Flash双机拓扑选型对比TP8x2/TP4x4与PP2/TP8]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/deepseek-v4-flash-shuang-ji-ta-pu-xuan-xing-dui-bi-tp8x2tp4x4-yu-pp2tp8" />
                <id>tag:https://blog.yongjie.top,2026-04-11:deepseek-v4-flash-shuang-ji-ta-pu-xuan-xing-dui-bi-tp8x2tp4x4-yu-pp2tp8</id>
                <published>2026-04-11T13:07:17+08:00</published>
                <updated>2026-06-30T15:00:49+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="deepseek-v4-flash-%E5%8F%8C%E6%9C%BA%E6%8B%93%E6%89%91%E9%80%89%E5%9E%8B%EF%BC%9Atp8x2%E3%80%81tp4x4-%E4%B8%8E-pp2%2Ftp8" tabindex="-1">DeepSeek-V4-Flash 双机拓扑选型：TP8x2、TP4x4 与 PP2/TP8</h1><p>大模型推理拓扑没有“万能最佳”。同样是双机 16 卡，真正要先分清的是两类扩展：一类是服务级副本扩展，例如 <code>TP8x2</code>、<code>TP4x4</code>；另一类是单模型副本的模型并行扩展，例如 <code>PP2/TP8</code>。</p><p><code>TP8x2</code> 和 <code>PP2/TP8</code> 看起来都用了 16 张卡，但含义完全不同。<code>TP8x2</code> 是两个独立 TP=8 的 vLLM 实例，每个请求只落到其中一台 8 卡机器。<code>PP2/TP8</code> 是一个 vLLM engine，TP=8、PP=2，单个请求会跨两台机器完成。</p><p>本文基于项目中的 DeepSeek-V4-Flash 双机压测资料整理公开版经验。文中不暴露真实节点、IP、端口和原始结果路径，只保留拓扑含义、指标矩阵和生产判断方法。</p><p><img src="/upload/2026/06/vllm-deepseek-topology-selection.svg" alt="vllm-deepseek-topology-selection" /></p><h2 id="%E4%B8%80%E3%80%81%E4%B8%89%E7%A7%8D%E6%8B%93%E6%89%91%E5%88%86%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88" tabindex="-1">一、三种拓扑分别是什么</h2><table><thead><tr><th>拓扑</th><th>准确含义</th><th style="text-align:right">endpoint 数</th><th>单请求执行路径</th><th>主要风险</th></tr></thead><tbody><tr><td><code>TP8x2</code></td><td>2 个独立 vLLM 副本，每个副本 <code>TP=8, PP=1</code></td><td style="text-align:right">2</td><td>请求只进入其中一台 8 卡节点</td><td>需要入口层负载均衡，单请求无法同时用满双机</td></tr><tr><td><code>TP4x4</code></td><td>4 个独立 vLLM 副本，每个副本 <code>TP=4, PP=1</code></td><td style="text-align:right">4</td><td>请求只进入其中 4 张卡</td><td>模型必须能在 4 卡副本内容纳，长上下文能力可能下降</td></tr><tr><td><code>PP2/TP8</code></td><td>1 个跨双机 vLLM engine，<code>TP=8, PP=2</code></td><td style="text-align:right">1</td><td>请求跨两台机器，按 pipeline stage 执行</td><td>pipeline bubble、跨机通信、单入口队列放大</td></tr></tbody></table><p>所以 <code>TP8x2</code> 和 <code>PP2/TP8</code> 不是同一个东西。前者是“两个服务副本”，优势是隔离简单、入口可横向扩展；后者是“一个跨机模型副本”，优势是单个模型副本可以使用两台机器的显存和算力，但每个请求会经历跨机 pipeline。</p><p>从直觉看，<code>TP4x4</code> 副本更多，似乎适合并发；<code>PP2/TP8</code> 用满两台机器，似乎适合超大模型；<code>TP8x2</code> 则是容易生产化的折中。但最终不能靠直觉，必须用业务 workload 和高并发扫描共同判断。</p><h2 id="%E4%BA%8C%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%8A%8A-workload-%E5%88%86%E6%88%90%E4%B8%A4%E7%B1%BB" tabindex="-1">二、为什么要把 workload 分成两类</h2><p>项目资料里使用了两类压测口径。</p><p>第一类是业务三组：</p><table><thead><tr><th>case</th><th>含义</th></tr></thead><tbody><tr><td><code>1000/300/c6</code></td><td>输入约 1000 token，输出约 300 token，并发 6</td></tr><tr><td><code>9000/600/c2</code></td><td>输入约 9000 token，输出约 600 token，并发 2</td></tr><tr><td><code>50000/1000/c1</code></td><td>输入约 50000 token，输出约 1000 token，并发 1</td></tr></tbody></table><p>这类 workload 适合看业务体验，重点看 TTFT、TPOT、P95 latency、QPM、TPM。</p><p>第二类是高并发短输出扫描，例如：</p><pre><code class="language-text">c1024_r2048_t128c2048_r4096_t128c2560_r5120_t128</code></pre><p>这里 <code>c2048_r4096_t128</code> 表示并发 2048、总请求 4096、每个请求最大输出 128 token。它更适合找吞吐峰值、过载拐点和 P95 latency 退化。</p><p>生产选型时不能只看其中一类。只看业务三组，可能看不到满载能力；只看高并发短输出，又可能误判长上下文体验。</p><h2 id="%E4%B8%89%E3%80%81deepseek-v4-flash-%E5%8E%86%E5%8F%B2%E6%8B%93%E6%89%91%E7%BB%93%E8%AE%BA" tabindex="-1">三、DeepSeek-V4-Flash 历史拓扑结论</h2><p><code>TP4x4</code> 在 DeepSeek-V4-Flash 上表现非常强，双机高并发短输出峰值达到：</p><table><thead><tr><th>拓扑</th><th>峰值 case</th><th style="text-align:right">completion tok/s</th><th style="text-align:right">total tok/s</th><th style="text-align:right">P95 latency</th></tr></thead><tbody><tr><td><code>TP4x4</code></td><td><code>c2048_r4096_t128</code></td><td style="text-align:right">11,866.308</td><td style="text-align:right">14,091.241</td><td style="text-align:right">25.192 s</td></tr><tr><td><code>TP8x2</code></td><td><code>c1536_r3072_t128</code></td><td style="text-align:right">1,525.866</td><td style="text-align:right">1,811.965</td><td style="text-align:right">129.028 s</td></tr><tr><td><code>PP2/TP8</code></td><td><code>c2048_r4096_t128</code></td><td style="text-align:right">1,528.639</td><td style="text-align:right">2,028.048</td><td style="text-align:right">98.160 s</td></tr></tbody></table><p>在这组结果里，<code>TP4x4</code> 明显适合高并发短输出。原因也很清楚：<code>TP=4</code> 已能容纳模型和 65K KV cache，单副本通信域更小，4 个独立副本可以提供更多请求队列。</p><p>但后续 H20 + DSFlash 项目中，同口径与 6000D 对齐时，<code>TP8x2</code> 成为了当前服务级推荐基线。这里的 <code>TP8x2</code> 指两个独立 TP8 endpoint，而不是一个 <code>TP=16</code> 或 <code>PP=2</code> 的跨机 engine。原因是新一轮同字段测试显示：</p><table><thead><tr><th>source</th><th>topology</th><th>case</th><th style="text-align:right">P95 latency</th><th style="text-align:right">TTFT</th><th style="text-align:right">TPOT</th><th style="text-align:right">QPM</th><th style="text-align:right">total tok/s</th></tr></thead><tbody><tr><td>H20</td><td><code>TP8x2</code></td><td><code>1000/300/c6</code></td><td style="text-align:right">7.302 s</td><td style="text-align:right">2,519.993 ms</td><td style="text-align:right">15.987 ms</td><td style="text-align:right">49.287</td><td style="text-align:right">1,076.090</td></tr><tr><td>H20</td><td><code>TP4x4</code></td><td><code>1000/300/c6</code></td><td style="text-align:right">24.273 s</td><td style="text-align:right">18,860.387 ms</td><td style="text-align:right">18.097 ms</td><td style="text-align:right">14.830</td><td style="text-align:right">323.786</td></tr><tr><td>H20</td><td><code>TP8x2</code></td><td><code>9000/600/c2</code></td><td style="text-align:right">8.786 s</td><td style="text-align:right">224.857 ms</td><td style="text-align:right">14.291 ms</td><td style="text-align:right">13.657</td><td style="text-align:right">2,182.083</td></tr><tr><td>H20</td><td><code>TP4x4</code></td><td><code>9000/600/c2</code></td><td style="text-align:right">10.765 s</td><td style="text-align:right">967.701 ms</td><td style="text-align:right">16.340 ms</td><td style="text-align:right">11.145</td><td style="text-align:right">1,780.738</td></tr><tr><td>H20</td><td><code>TP8x2</code></td><td><code>50000/1000/c1</code></td><td style="text-align:right">14.774 s</td><td style="text-align:right">353.421 ms</td><td style="text-align:right">14.435 ms</td><td style="text-align:right">4.061</td><td style="text-align:right">3,442.439</td></tr><tr><td>H20</td><td><code>TP4x4</code></td><td><code>50000/1000/c1</code></td><td style="text-align:right">27.114 s</td><td style="text-align:right">11,128.173 ms</td><td style="text-align:right">16.002 ms</td><td style="text-align:right">2.213</td><td style="text-align:right">1,875.849</td></tr></tbody></table><p>这说明同一个模型，在不同镜像、内核、调度参数、入口策略、压测口径下，最佳拓扑会变化。</p><h2 id="%E5%9B%9B%E3%80%81%E5%BD%93%E5%89%8D-h20-tp8x2-%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%90%E4%B8%BA%E6%8E%A8%E8%8D%90%E5%9F%BA%E7%BA%BF" tabindex="-1">四、当前 H20 TP8x2 为什么成为推荐基线</h2><p>当前 H20 与 6000D 的同拓扑对齐结果显示，H20 <code>TP8x2</code> 在三组 workload 上均优于 6000D 参考口径。它适合作为生产基线的原因，不是“单请求用了 16 卡”，而是“每台机器一个独立 TP8 副本，入口层可以稳定分流”：</p><table><thead><tr><th>case</th><th style="text-align:right">H20 P95 latency</th><th style="text-align:right">H20 QPM</th><th style="text-align:right">H20 total tok/s</th><th style="text-align:right">6000D QPM</th><th style="text-align:right">6000D total tok/s</th></tr></thead><tbody><tr><td><code>1000/300/c6</code></td><td style="text-align:right">7.302 s</td><td style="text-align:right">49.287</td><td style="text-align:right">1,076.090</td><td style="text-align:right">19.276</td><td style="text-align:right">417.642</td></tr><tr><td><code>9000/600/c2</code></td><td style="text-align:right">8.786 s</td><td style="text-align:right">13.657</td><td style="text-align:right">2,182.083</td><td style="text-align:right">10.314</td><td style="text-align:right">1,650.300</td></tr><tr><td><code>50000/1000/c1</code></td><td style="text-align:right">14.774 s</td><td style="text-align:right">4.061</td><td style="text-align:right">3,442.439</td><td style="text-align:right">2.044</td><td style="text-align:right">1,737.605</td></tr></tbody></table><p>这组结果说明，当前服务的瓶颈不一定在 H20、RoCE/NCCL 或显存硬件能力本身。对 <code>TP8x2</code> 来说，每个请求主要在单机 8 卡副本内完成，跨机 RoCE 更偏向服务编排和入口侧，而不是单请求每层必经路径。更值得关注的是 vLLM 调度、HTTP 连接复用和入口分流。</p><h2 id="%E4%BA%94%E3%80%81%E7%9F%AD%E8%AF%B7%E6%B1%82%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%8D%95%E7%8B%AC%E4%BC%98%E5%8C%96%E5%85%A5%E5%8F%A3" tabindex="-1">五、短请求为什么要单独优化入口</h2><p>TP8x2 的优化记录里，一个很重要的发现是：短输入高并发时，HTTP 连接复用和 API worker 分配不均可能导致 TTFT 排队。对 <code>1000/300/c6</code> 做不同策略后结果如下：</p><table><thead><tr><th>variant</th><th style="text-align:right">P95 latency</th><th style="text-align:right">TTFT p50</th><th style="text-align:right">TPOT p50</th><th style="text-align:right">QPM</th><th style="text-align:right">total tok/s</th></tr></thead><tbody><tr><td>baseline</td><td style="text-align:right">7.302 s</td><td style="text-align:right">2,519.993 ms</td><td style="text-align:right">15.987 ms</td><td style="text-align:right">49.287</td><td style="text-align:right">1,076.090</td></tr><tr><td>batched16384</td><td style="text-align:right">5.261 s</td><td style="text-align:right">500.742 ms</td><td style="text-align:right">15.914 ms</td><td style="text-align:right">68.397</td><td style="text-align:right">1,493.336</td></tr><tr><td>force-close</td><td style="text-align:right">4.836 s</td><td style="text-align:right">246.449 ms</td><td style="text-align:right">15.347 ms</td><td style="text-align:right">74.429</td><td style="text-align:right">1,625.034</td></tr><tr><td>HAProxy short entry</td><td style="text-align:right">5.001 s</td><td style="text-align:right">552.773 ms</td><td style="text-align:right">14.870 ms</td><td style="text-align:right">71.968</td><td style="text-align:right">1,571.308</td></tr></tbody></table><p>但是 <code>force-close</code> 和更大的 batched tokens 并不适合所有请求。中长上下文可能退化。因此更合理的生产策略不是全局改参数，而是分入口：</p><table><thead><tr><th>入口</th><th>适合流量</th><th>策略</th></tr></thead><tbody><tr><td>短请求入口</td><td>短输入、高并发、低 TTFT</td><td>多连接分发，减少队列集中</td></tr><tr><td>长上下文入口</td><td>9k/50k 这类中长输入</td><td>保持默认连接策略，避免退化</td></tr><tr><td>原生调试入口</td><td>排障、回归、直接访问 vLLM</td><td>保留最少中间层</td></tr></tbody></table><p>这也是拓扑选型之外非常重要的一点：很多“模型慢”其实是入口层和客户端连接策略导致的排队。</p><h2 id="%E5%85%AD%E3%80%81pp2%2Ftp8-%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E6%9C%89%E4%BB%B7%E5%80%BC" tabindex="-1">六、PP2/TP8 什么时候有价值</h2><p><code>PP2/TP8</code> 不适合作为当前默认生产吞吐拓扑，但并非没有价值。它和 <code>TP8x2</code> 的核心差别是：<code>PP2/TP8</code> 是一个跨机模型副本，因此适合验证“单请求跨双机”的能力，而不是替代两个独立 endpoint 做高并发吞吐。</p><p>它适合三类场景：</p><ol><li>复现跨机 pipeline 性能问题。</li><li>做超长输入、低并发、单请求专项对照。</li><li>验证跨机模型内部执行路径的稳定性。</li></ol><p><code>50000/1000/c1</code> 这类单请求超长输入下，<code>PP2/TP8</code> 的 TPM 曾比 <code>TP4x4</code> 高约 2.99%。但这不能代表整体吞吐最佳。只要进入中短输入或高并发，pipeline bubble、跨机通信和单 endpoint 队列都会放大延迟。</p><p>如果模型单机 8 卡能放下，生产上通常优先考虑 <code>TP8x2</code> 这类副本并行；如果模型单机放不下，或需要验证单请求使用双机显存，才更自然地进入 <code>PP2/TP8</code> 或更复杂的 TP/PP 组合。</p><h2 id="%E4%B8%83%E3%80%81%E9%80%89%E5%9E%8B%E6%96%B9%E6%B3%95%E8%AE%BA" tabindex="-1">七、选型方法论</h2><p>我建议把推理拓扑选择做成四步：</p><p>第一步，确定业务流量形态。短问答、代码助手、RAG 长上下文、批量摘要、多轮 Agent，它们需要看的指标不一样。</p><p>第二步，定义统一 workload。至少包含短输入并发、中长输入、超长输入、吞吐峰值扫描四类。</p><p>第三步，固定变量做 A/B。一次只改一个变量：拓扑、batch token、连接策略、P2P/RoCE、CUDA graph、MTP、max-num-seqs。</p><p>第四步，按场景给入口，而不是追求一个全局最优参数。短请求和长上下文混在同一个入口，很容易互相拖累。</p><h2 id="%E5%85%AB%E3%80%81%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" tabindex="-1">八、参考资料</h2><ul><li>vLLM Parallelism and Scaling：<a href="https://docs.vllm.ai/en/stable/serving/parallelism_scaling/" target="_blank">https://docs.vllm.ai/en/stable/serving/parallelism_scaling/</a></li><li>vLLM Distributed Inference and Serving：<a href="https://docs.vllm.ai/en/v0.6.0/serving/distributed_serving.html" target="_blank">https://docs.vllm.ai/en/v0.6.0/serving/distributed_serving.html</a></li></ul><h2 id="%E4%B9%9D%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">九、总结</h2><p>DeepSeek-V4-Flash 的双机拓扑选型不能用一句“TP4x4 更快”或“TP8x2 更稳”概括。更准确的说法是：<code>TP8x2</code>、<code>TP4x4</code> 是服务副本扩展，<code>PP2/TP8</code> 是单模型副本跨机扩展，二者解决的问题不同。</p><p>历史探索中，<code>TP4x4</code> 在高并发短输出峰值上非常强；但当前 H20 + DSFlash 同口径复测里，<code>TP8x2</code> 是服务级推荐基线，并且通过短/长入口分流进一步改善了业务体验。</p><p>真正可落地的结论是：拓扑选择要绑定 workload、指标口径和入口策略。生产默认可以选当前综合最稳的 <code>TP8x2</code>，但保留 <code>TP4x4</code> 和 <code>PP2/TP8</code> 作为专项对照，才能在业务变化时快速复测和切换。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第一章-H20双机16卡DeepSeek-V4-Flash-vLLM生产部署实践]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-yi-zhang--h20-shuang-ji-16-ka-deepseek-v4-flash-vllm-sheng-chan-bu-shu-shi-jian" />
                <id>tag:https://blog.yongjie.top,2026-03-28:di-yi-zhang--h20-shuang-ji-16-ka-deepseek-v4-flash-vllm-sheng-chan-bu-shu-shi-jian</id>
                <published>2026-03-28T20:42:10+08:00</published>
                <updated>2026-06-30T14:42:59+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="h20-%E5%8F%8C%E6%9C%BA-16-%E5%8D%A1-deepseek-v4-flash-vllm-%E7%94%9F%E4%BA%A7%E9%83%A8%E7%BD%B2%E5%AE%9E%E8%B7%B5" tabindex="-1">H20 双机 16 卡 DeepSeek-V4-Flash vLLM 生产部署实践</h1><p>这篇文章整理的是一个 H20 双机 16 卡推理服务的公开版落地经验。目标模型是 DeepSeek-V4-Flash，服务框架使用 vLLM OpenAI-compatible API，分布式执行使用 Ray，运行形态使用 Docker 容器。为了适合公网发布，文中不会出现原始节点名、IP 地址、模型绝对路径、内部镜像仓库、日志路径和内部业务信息，相关位置统一用占位符表示。</p><p><img src="/upload/2026/06/vllm-deepseek-docker-ray-architecture.svg" alt="vllm-deepseek-docker-ray-architecture" /></p><h2 id="%E4%B8%80%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E6%98%AF%E7%AE%80%E5%8D%95-docker-run-vllm" tabindex="-1">一、为什么不是简单 <code>docker run vllm</code></h2><p>单机小模型部署时，一个 vLLM 容器、一个模型路径、一个 API 端口通常就能跑起来。但 DeepSeek-V4-Flash 这类 MoE、FP8、长上下文模型部署到双机 16 卡时，真正的难点在于四层协同：</p><table><thead><tr><th>层级</th><th>关键点</th><th>失败表现</th></tr></thead><tbody><tr><td>宿主机层</td><td>GPU、驱动、Docker、NVIDIA Container Toolkit</td><td>容器看不到 GPU、驱动能力不匹配</td></tr><tr><td>网络层</td><td>管理网、RoCE 数据面、GID、HCA、ARP/RPF</td><td>Ray 加入失败、NCCL fallback、跨机慢</td></tr><tr><td>分布式层</td><td>Ray head/worker、placement group、GPU 资源</td><td>worker lost、GPU 资源不足、服务启动卡住</td></tr><tr><td>vLLM 层</td><td>TP/PP、KV cache、MoE、FP8、tool parser</td><td>OOM、模型加载失败、首 token 延迟异常</td></tr></tbody></table><p>生产部署的目标不是“启动一次成功”，而是要做到环境可检查、参数可复现、日志可定位、服务可压测、失败可回滚。</p><h2 id="%E4%BA%8C%E3%80%81%E6%8E%A8%E8%8D%90%E6%9E%B6%E6%9E%84" tabindex="-1">二、推荐架构</h2><p>第一条是业务入口链路。业务方访问入口代理或 vLLM API Server，API 使用 OpenAI-compatible 协议，最常用的是 <code>/v1/chat/completions</code>、<code>/v1/completions</code>、<code>/v1/models</code> 和 <code>/health</code>。生产中建议前面接 Nginx、Envoy 或 HAProxy，用于健康检查、TLS、鉴权、限流和多入口分流。</p><p>第二条是 Ray 控制面链路。Ray head 运行在 head 节点，worker 节点加入 Ray 集群。控制面建议绑定管理网，例如 <code>&lt;management-bond&gt;</code>，不要和 RoCE 数据面混用。Ray 看到的 GPU 资源应该是双机共 16 张卡。</p><p>第三条是 NCCL 数据面链路。跨机 Tensor Parallel 或 Pipeline Parallel 通信依赖 RoCE/RDMA。容器必须能看到 <code>/dev/infiniband</code>，NCCL 需要绑定正确的 HCA 列表、GID index 和 socket interface。日志中应能看到 <code>Using network IB</code>、<code>GDRDMA</code> 等证据，避免静默退回 TCP/socket。</p><p>这里要先区分两种生产形态：如果目标是一个跨双机的单模型副本，vLLM 主流写法是每节点内做 TP、节点间做 PP，例如 2 台 8 卡节点使用 <code>TP=8, PP=2</code>。如果模型单机 8 卡就能放下，也可以部署两个独立 <code>TP=8, PP=1</code> 副本，再由入口代理分流；这属于服务级副本扩展，不是一个跨 16 卡的 vLLM engine。</p><p>生产上还要把多节点通信放在隔离网络里。Ray、PyTorch Distributed、NCCL、KV cache 传输都不应该直接暴露到公网；对外只暴露经过鉴权、限流和审计的 API 入口。</p><h2 id="%E4%B8%89%E3%80%81%E9%83%A8%E7%BD%B2%E5%89%8D%E7%8E%AF%E5%A2%83%E6%A3%80%E6%9F%A5" tabindex="-1">三、部署前环境检查</h2><table><thead><tr><th>检查项</th><th>目标</th></tr></thead><tbody><tr><td>OS / Kernel</td><td>双节点版本一致，内核参数可控</td></tr><tr><td>GPU</td><td>每台 8 卡可见，<code>nvidia-smi -L</code> 正常</td></tr><tr><td>Driver / CUDA capability</td><td>满足目标 vLLM 镜像和模型要求</td></tr><tr><td>Docker</td><td>支持 <code>--gpus all</code>，容器内可见 GPU</td></tr><tr><td>NVIDIA Container Toolkit</td><td>版本一致，runtime 正常</td></tr><tr><td><code>/dev/infiniband</code></td><td>宿主机和容器内均可见</td></tr><tr><td>RoCE GID</td><td>目标 GID index 为 RoCE v2</td></tr><tr><td>ARP / RPF</td><td>多网卡场景下避免回包路径异常</td></tr><tr><td>模型目录</td><td>本地可读，权重、tokenizer、config 完整</td></tr><tr><td>镜像能力</td><td>包含 vLLM、Ray、PyTorch、RDMA 工具</td></tr></tbody></table><p>容器 GPU 检查可以用类似命令：</p><pre><code class="language-bash">docker run --rm \  --gpus all \  --network=host \  --ipc=host \  &lt;cuda-or-vllm-image&gt; nvidia-smi</code></pre><p>RDMA 工具建议至少包含：</p><pre><code class="language-bash">ibv_devinfoibdev2netdevshow_gids</code></pre><p>如果官方 vLLM 镜像没有这些工具，可以做一个很薄的派生镜像，只增加 <code>rdma-core</code>、<code>iproute2</code>、<code>curl</code>、<code>net-tools</code> 等诊断工具。</p><h2 id="%E5%9B%9B%E3%80%81%E5%AE%B9%E5%99%A8%E8%BF%90%E8%A1%8C%E6%96%B9%E5%BC%8F" tabindex="-1">四、容器运行方式</h2><p>双机推理服务建议使用 host network，减少容器网络对 Ray、NCCL、RoCE 的干扰。典型容器形态如下：</p><pre><code class="language-bash">docker run -d \  --name &lt;vllm-ray-container&gt; \  --gpus all \  --network=host \  --ipc=host \  --shm-size=256g \  --ulimit memlock=-1:-1 \  --ulimit stack=67108864 \  --cap-add IPC_LOCK \  --device=/dev/infiniband \  -v /dev/infiniband:/dev/infiniband \  -v &lt;model-path&gt;:&lt;model-path&gt;:ro \  -v &lt;log-dir&gt;:&lt;log-dir&gt; \  -e NCCL_NET=IB \  -e NCCL_SOCKET_IFNAME=&lt;management-bond&gt; \  -e NCCL_IB_GID_INDEX=&lt;gid-index&gt; \  -e NCCL_IB_HCA=&lt;hca-list&gt; \  -e NCCL_ASYNC_ERROR_HANDLING=1 \  -e NCCL_IB_DISABLE=0 \  -e GLOO_SOCKET_IFNAME=&lt;management-bond&gt; \  -e VLLM_HOST_IP=&lt;node-management-ip&gt; \  &lt;vllm-image&gt; sleep infinity</code></pre><p>这里有几个容易踩坑的点。</p><p><code>--network=host</code> 不是为了省事，而是为了让 Ray、vLLM、NCCL 在多网卡环境中更可控。<code>--ipc=host</code> 或足够大的 <code>--shm-size</code> 对 PyTorch/vLLM 也很重要。<code>--device=/dev/infiniband</code> 和卷挂载同时配置，是为了让容器内 RDMA 诊断工具和 NCCL 都能看到设备。</p><h2 id="%E4%BA%94%E3%80%81ray-%E5%90%AF%E5%8A%A8%E9%A1%BA%E5%BA%8F" tabindex="-1">五、Ray 启动顺序</h2><p>推荐顺序是先容器、再 Ray、再 vLLM。</p><pre><code class="language-text">head 节点：启动容器 -&gt; ray start --headworker 节点：启动容器 -&gt; ray start --address &lt;head-ip&gt;:6379head 节点：检查 ray status -&gt; 启动 vLLM API Server</code></pre><p>Ray 检查重点不是“命令返回成功”，而是资源视图是否正确。应该看到两台节点 active，并且总 GPU 数为 16。若 Ray 资源不足，vLLM 后续 placement group 会失败或一直等待。</p><h2 id="%E5%85%AD%E3%80%81vllm-%E5%8F%82%E6%95%B0%E5%9F%BA%E7%BA%BF" tabindex="-1">六、vLLM 参数基线</h2><p>DeepSeek-V4-Flash 是 MoE + FP8 + 长上下文模型，不建议一开始就把上下文和并发拉满。一个更稳的首轮基线可以这样设计：</p><pre><code class="language-bash">vllm serve &lt;model-path&gt; \  --host 0.0.0.0 \  --port &lt;api-port&gt; \  --served-model-name DeepSeek-V4-Flash \  --trust-remote-code \  --distributed-executor-backend ray \  --tensor-parallel-size 8 \  --pipeline-parallel-size 2 \  --dtype auto \  --kv-cache-dtype fp8 \  --max-model-len 65536 \  --gpu-memory-utilization 0.88 \  --max-num-seqs 8 \  --enable-expert-parallel \  --tokenizer-mode deepseek_v4 \  --tool-call-parser deepseek_v4 \  --enable-auto-tool-choice \  --reasoning-parser deepseek_v4</code></pre><p>参数解释：</p><table><thead><tr><th>参数</th><th>建议</th></tr></thead><tbody><tr><td><code>tensor-parallel-size</code></td><td>对 2 台 8 卡节点，单跨机副本优先设为每节点 GPU 数，即 8</td></tr><tr><td><code>pipeline-parallel-size</code></td><td>对 2 节点跨机副本设为 2，让模型层按 pipeline stage 分布到两台机器</td></tr><tr><td><code>max-model-len</code></td><td>先用 65K，稳定后再扩到 128K/256K</td></tr><tr><td><code>kv-cache-dtype</code></td><td>FP8 有利于降低 KV cache 压力</td></tr><tr><td><code>gpu-memory-utilization</code></td><td>先保守，避免碎片和 OOM</td></tr><tr><td><code>max-num-seqs</code></td><td>从小并发启动，逐步增加</td></tr></tbody></table><p>如果使用的是深度定制 DSFlash 镜像，可能还有额外 parser、block size 或 speculative decoding 参数。不建议把这类非通用参数写死；先用 vLLM 主线参数跑通，再按镜像说明做专项 A/B。</p><p><code>TP=16, PP=1</code> 也可以作为实验项，即 Tensor Parallel 横跨两台机器。但这会让大量逐层 all-reduce 穿过跨机 RoCE，对网络、NCCL、GDRDMA 和拓扑一致性要求更高。除非跨机互联已经被验证足够稳定，否则它更适合作为压测对照，而不是首轮生产基线。</p><p>如果显存不足，优先按下面顺序收敛：降低 <code>max-num-seqs</code>，再降低 <code>max-model-len</code>，最后再调整 <code>gpu-memory-utilization</code>。不要一开始就把显存利用率拉到极限，否则后续排障很难判断是模型、KV cache、碎片还是通信导致的问题。</p><h2 id="%E4%B8%83%E3%80%81%E6%9C%8D%E5%8A%A1%E9%AA%8C%E8%AF%81" tabindex="-1">七、服务验证</h2><p>服务启动后，最少做三类验证。</p><p>健康检查：</p><pre><code class="language-bash">curl -fsS http://&lt;api-host&gt;:&lt;api-port&gt;/healthcurl -fsS http://&lt;api-host&gt;:&lt;api-port&gt;/v1/models</code></pre><p>功能冒烟：</p><pre><code class="language-bash">curl http://&lt;api-host&gt;:&lt;api-port&gt;/v1/chat/completions \  -H &#39;Content-Type: application/json&#39; \  -d &#39;{    &quot;model&quot;: &quot;DeepSeek-V4-Flash&quot;,    &quot;messages&quot;: [      {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;请用一句话介绍你自己。&quot;}    ],    &quot;max_tokens&quot;: 128,    &quot;temperature&quot;: 0  }&#39;</code></pre><p>通信证据：</p><pre><code class="language-bash">grep -E &quot;Using network IB|GDRDMA|NCCL|Ray&quot; &lt;vllm-log&gt;</code></pre><p>如果只看 API 能返回，可能会漏掉 NCCL fallback。跨机推理服务必须确认数据面真的走 RoCE/IB/GDRDMA。</p><h2 id="%E5%85%AB%E3%80%81%E7%94%9F%E4%BA%A7%E6%8E%92%E9%9A%9C%E7%BB%8F%E9%AA%8C" tabindex="-1">八、生产排障经验</h2><p>第一，镜像能力要先验收。模型 config 支持 DeepSeek V4 不等于当前镜像一定支持对应 architecture、parser、MoE、FP8 和长上下文。</p><p>第二，控制面和数据面要分清。Ray、Gloo、API 健康检查可以走管理网；NCCL 数据面要绑定 RoCE HCA。两者混在一起会导致“能启动但性能不稳定”。</p><p>第三，日志要从启动阶段开始保留。TileLang lazy compile、Ray worker lost、NCCL fallback、OOM、KV cache 不足都可能只在特定阶段出现。</p><p>第四，长上下文不能只靠 <code>max_position_embeddings</code> 判断。模型配置可能支持极长上下文，但实际可服务长度取决于 KV cache、并发、显存碎片和 batch token 策略。</p><h2 id="%E4%B9%9D%E3%80%81%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" tabindex="-1">九、参考资料</h2><ul><li>vLLM Parallelism and Scaling：<a href="https://docs.vllm.ai/en/stable/serving/parallelism_scaling/" target="_blank">https://docs.vllm.ai/en/stable/serving/parallelism_scaling/</a></li><li>vLLM Docker Deployment：<a href="https://docs.vllm.ai/en/stable/deployment/docker/" target="_blank">https://docs.vllm.ai/en/stable/deployment/docker/</a></li><li>vLLM Security：<a href="https://docs.vllm.ai/en/latest/usage/security/" target="_blank">https://docs.vllm.ai/en/latest/usage/security/</a></li></ul><h2 id="%E5%8D%81%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">十、总结</h2><p>H20 双机 16 卡部署 DeepSeek-V4-Flash 的关键，是把 Docker、Ray、vLLM、RoCE/NCCL 和模型参数放在同一个工程闭环里。正确的落地方式不是复制一条启动命令，而是先检查环境，再固化容器和网络参数，然后按小并发到大并发、短上下文到长上下文逐步压测。</p><p>当服务能稳定通过健康检查、功能冒烟、NCCL 数据面验证和三组 workload 压测后，才适合作为后续拓扑优化和业务接入的基线。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第四章-智算集群GPU掉卡、NVSwitch与FabricManager排障闭环]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-si-zhang---zhi-suan-ji-qun-gpu-diao-ka-nvswitch-yu-fabricmanager-pai-zhang-bi-huan" />
                <id>tag:https://blog.yongjie.top,2026-03-15:di-si-zhang---zhi-suan-ji-qun-gpu-diao-ka-nvswitch-yu-fabricmanager-pai-zhang-bi-huan</id>
                <published>2026-03-15T20:57:24+08:00</published>
                <updated>2026-06-29T17:58:21+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="h20-%E9%9B%86%E7%BE%A4-gpu-%E6%8E%89%E5%8D%A1%E3%80%81nvswitch-%E4%B8%8E-fabric-manager-%E6%8E%92%E9%9A%9C%E9%97%AD%E7%8E%AF" tabindex="-1">H20 集群 GPU 掉卡、NVSwitch 与 Fabric Manager 排障闭环</h1><p>进入生产后，真正考验运维能力的不是“有没有故障”，而是故障出现后能否快速定界。GPU 掉卡、NVSwitch 未识别、Fabric Manager 启动失败、NCCL 慢节点，这些问题在单台机器上看似独立，在训练集群里都会表现为任务失败、通信超时或整体带宽下降。</p><p><img src="/upload/2026/06/h20-gpu-troubleshooting-flow-optimized.svg" alt="h20-gpu-troubleshooting-flow-optimized" /></p><h2 id="%E4%B8%80%E3%80%81%E6%8E%92%E9%9A%9C%E5%8E%9F%E5%88%99%EF%BC%9A%E5%85%88%E5%AE%9A%E7%95%8C%EF%BC%8C%E5%86%8D%E6%8D%A2%E4%BB%B6" tabindex="-1">一、排障原则：先定界，再换件</h2><p>生产现场最容易犯的错误是看到 <code>nvidia-smi</code> 少卡就直接判断 GPU 坏了。实际上，GPU 不识别可能来自多种原因：</p><table><thead><tr><th>类别</th><th>可能原因</th></tr></thead><tbody><tr><td>GPU 单卡</td><td>硬件故障、ECC/Row Remapper 异常、温度或供电问题</td></tr><tr><td>槽位/线缆</td><td>连接不良、线缆或转接板异常、插槽问题</td></tr><tr><td>基板/整机</td><td>PCIe 拓扑异常、主板或基板问题</td></tr><tr><td>NVSwitch</td><td>模块未识别、Fabric Manager 启动失败</td></tr><tr><td>软件栈</td><td>驱动、内核模块、Fabric Manager 版本不匹配</td></tr><tr><td>网络链路</td><td>RoCE、HCA、PFC/ECN、路由异常导致 NCCL 慢</td></tr></tbody></table><p>正确方法是先收集证据，再缩小范围，最后决定是否换 GPU、换线缆、处理 NVSwitch、调整配置或升级软件。</p><h2 id="%E4%BA%8C%E3%80%81%E7%AC%AC%E4%B8%80%E6%AD%A5%EF%BC%9A%E5%B8%A6%E5%A4%96%E5%92%8C-os-%E5%8F%8C%E8%A7%86%E8%A7%92%E7%A1%AE%E8%AE%A4" tabindex="-1">二、第一步：带外和 OS 双视角确认</h2><p>GPU 掉卡排障建议先看带外，再看 OS。</p><p>带外视角用于判断硬件管理面是否能识别 GPU。这里可以通过 BMC 页面或 Redfish 接口查看设备状态、传感器、错误告警。</p><p>OS 视角用于判断驱动和系统是否识别 GPU：</p><pre><code class="language-bash">nvidia-sminvidia-smi -Llspci | grep -i nvidiadmesg | egrep -i &#39;nvrm|xid|nvidia|pcie&#39;</code></pre><p>如果 BMC 能识别但 OS 不能识别，优先看驱动、PCIe、系统日志和 Fabric。若 BMC 和 OS 都不能识别，硬件路径问题概率更高。</p><h2 id="%E4%B8%89%E3%80%81%E7%AC%AC%E4%BA%8C%E6%AD%A5%EF%BC%9A%E6%A3%80%E6%9F%A5-ecc-%E4%B8%8E-row-remapper" tabindex="-1">三、第二步：检查 ECC 与 Row Remapper</h2><p>如果 GPU 仍然可见，但训练任务异常、NCCL 报错或性能不稳定，建议检查 ECC 与 Row Remapper：</p><pre><code class="language-bash">nvidia-smi -q -d ECC,ROW_REMAPPER</code></pre><p>关注项包括：</p><table><thead><tr><th>项目</th><th>风险</th></tr></thead><tbody><tr><td>Uncorrectable ECC</td><td>可能导致任务失败或数据错误</td></tr><tr><td>Row Remapper Pending</td><td>可能需要维护窗口处理</td></tr><tr><td>Retired Pages 异常增长</td><td>需要结合厂商策略评估</td></tr><tr><td>Xid 错误</td><td>需要结合内核日志判断原因</td></tr></tbody></table><p>如果错误已经指向 GPU 硬件健康问题，就不应继续把它放回训练池。生产上可以先隔离节点，再做进一步定界。</p><h2 id="%E5%9B%9B%E3%80%81%E7%AC%AC%E4%B8%89%E6%AD%A5%EF%BC%9Anvswitch-%E4%B8%8E-fabric-manager" tabindex="-1">四、第三步：NVSwitch 与 Fabric Manager</h2><p>8 卡服务器如果使用 NVSwitch 或 Fabric Manager，GPU 可见不代表 Fabric 正常。Fabric Manager 异常会导致 GPU 间通信能力受影响，NCCL 可能表现为初始化失败、带宽异常或任务挂住。</p><p>常用检查：</p><pre><code class="language-bash">dmesg | grep -i nvswitchsystemctl status nvidia-fabricmanagerjournalctl -u nvidia-fabricmanager -n 100 --no-pagerlspci -nn | grep -i nvidia</code></pre><p>排障时建议分三层看：</p><ol><li><code>lspci</code> 是否能看到对应 NVIDIA 设备。</li><li>内核日志是否有 NVSwitch 或 PCIe 错误。</li><li>Fabric Manager 服务是否启动成功，日志里是否有模块未识别、拓扑不一致或版本不匹配。</li></ol><p>如果确认某个 NVSwitch 模块未识别，现场可以按厂商维护规范做断电复位、插拔清洁或部件替换。公开文章建议写方法，不建议公开现场照片和模块编号。</p><h2 id="%E4%BA%94%E3%80%81%E7%AC%AC%E5%9B%9B%E6%AD%A5%EF%BC%9Anccl-%E6%85%A2%E8%8A%82%E7%82%B9%E5%8D%95%E5%8D%A1%E9%9A%94%E7%A6%BB" tabindex="-1">五、第四步：NCCL 慢节点单卡隔离</h2><p>有些故障不会表现为 GPU 掉卡，而是表现为 NCCL 慢。定位这类问题时，最有效的方法是逐步隔离 GPU 和 HCA。</p><p>单机全卡测试：</p><pre><code class="language-bash">./all_reduce_perf -g 8 -b 8M -e 12G -f 2</code></pre><p>排除某一张 GPU：</p><pre><code class="language-bash">CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6 ./all_reduce_perf \  -g 7 \  -b 8M \  -e 12G \  -f 2</code></pre><p>轮流排除 GPU 0 到 GPU 7。如果排除某一张 GPU 后性能恢复，要继续判断问题是 GPU 本身、对应 HCA、PCIe 路径、NVSwitch 路径，还是局部链路。</p><p>跨机慢节点也可以用类似方法：</p><ol><li>从大规模测试中拿掉可疑节点。</li><li>对可疑节点做双机测试。</li><li>对可疑节点做单机 8 卡测试。</li><li>轮流排除 GPU 或 HCA。</li><li>结合 RoCE 队列、端口错误、PFC/ECN 统计判断。</li></ol><h2 id="%E5%85%AD%E3%80%81%E7%AC%AC%E4%BA%94%E6%AD%A5%EF%BC%9A%E6%A7%BD%E4%BD%8D%E5%AF%B9%E8%B0%83%E5%88%A4%E6%96%AD%E2%80%9C%E8%B7%9F%E5%8D%A1%E8%B5%B0%E8%BF%98%E6%98%AF%E8%B7%9F%E6%A7%BD%E8%B5%B0%E2%80%9D" tabindex="-1">六、第五步：槽位对调判断“跟卡走还是跟槽走”</h2><p>当 OS 不识别某张 GPU，且重启无法恢复时，可以通过槽位对调进一步定界：</p><pre><code class="language-text">对调前：  Slot-X 中 GPU-A 不识别。操作：  将 GPU-A 与 Slot-Y 中 GPU-B 对调。对调后：  如果故障跟着 GPU-A 走，倾向 GPU 单卡问题。  如果故障仍停留在 Slot-X，倾向槽位、线缆、基板或主板路径问题。</code></pre><p>生产现场一定要记录 GPU UUID、SN、槽位、线缆、BDF 和对调前后照片</p><h2 id="%E4%B8%83%E3%80%81%E6%8E%92%E9%9A%9C%E8%AF%81%E6%8D%AE%E6%B8%85%E5%8D%95" tabindex="-1">七、排障证据清单</h2><table><thead><tr><th>证据</th><th>命令或来源</th><th>公开处理</th></tr></thead><tbody><tr><td>GPU 列表</td><td><code>nvidia-smi -L</code></td><td>脱敏 UUID</td></tr><tr><td>GPU 健康</td><td><code>nvidia-smi -q -d ECC,ROW_REMAPPER</code></td><td>保留异常类型，隐藏 SN</td></tr><tr><td>PCIe 设备</td><td><code>lspci</code></td><td>隐藏完整 BDF 映射</td></tr><tr><td>内核日志</td><td><code>dmesg</code></td><td>截取错误类型</td></tr><tr><td>Fabric 状态</td><td><code>systemctl status nvidia-fabricmanager</code></td><td>隐藏主机名</td></tr><tr><td>Fabric 日志</td><td><code>journalctl -u nvidia-fabricmanager</code></td><td>隐藏节点信息</td></tr><tr><td>NCCL 结果</td><td><code>all_reduce_perf</code></td><td>隐藏主机名/IP</td></tr><tr><td>槽位对调</td><td>现场记录</td><td>不公开照片、SN、地址</td></tr></tbody></table><h2 id="%E5%85%AB%E3%80%81%E6%95%85%E9%9A%9C%E9%97%AD%E7%8E%AF%E6%A8%A1%E6%9D%BF" tabindex="-1">八、故障闭环模板</h2><p>建议每一次生产故障都按同一个模板记录：</p><pre><code class="language-text">故障现象：  训练任务失败 / NCCL 带宽下降 / nvidia-smi 少卡 / Fabric Manager 异常。影响范围：  单节点 / 单 Leaf / 多节点 / 整网。初步证据：  告警、日志、NCCL 结果、GPU 健康状态。定界过程：  BMC -&gt; OS -&gt; GPU 健康 -&gt; Fabric -&gt; 单卡隔离 -&gt; 槽位对调。结论：  GPU 单卡 / NVSwitch / HCA / 线缆 / 槽位 / 软件配置。恢复动作：  隔离、替换、复位、升级、回滚。复测结果:  单机 NCCL、双机 NCCL、目标规模 NCCL。</code></pre><p>这个模板的价值在于减少误判。没有复测结果的“恢复”，不能算闭环。</p><h2 id="%E4%B9%9D%E3%80%81%E7%94%9F%E4%BA%A7%E7%BB%8F%E9%AA%8C" tabindex="-1">九、生产经验</h2><p>第一，慢节点优先隔离，避免影响正在排队的训练任务。大规模集群里，一个慢节点可能拖垮整个作业。</p><p>第二，硬件证据和软件证据要分开看。<code>nvidia-smi</code> 正常不代表 NVSwitch 正常，BMC 正常也不代表 OS 驱动正常。</p><p>第三，换件前要尽量完成槽位对调或路径定界。否则可能出现 GPU 换了、问题还在原槽位的情况。</p><p>第四，恢复后必须复跑 NCCL。硬件恢复只是第一步，训练通信恢复才是生产可用的证据。</p><h2 id="%E5%8D%81%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">十、总结</h2><p>智算集群故障定界的关键是流程化：先带外，再 OS；先设备识别，再健康指标；先单机，再跨机；先隔离，再换件；最后用 NCCL 复测闭环。</p><p>在 生产环境里，排障方法本身就是平台能力的一部分。只有把 GPU 掉卡、NVSwitch 异常、Fabric Manager 故障和 NCCL 慢节点纳入统一闭环，集群才能长期稳定运行。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[GPU-on-Kubernetes-8卡Worker使用单张GPU隔离]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/gpu-on-kubernetes-8-ka-worker-shi-yong-dan-zhang-gpu-ge-li" />
                <id>tag:https://blog.yongjie.top,2026-02-22:gpu-on-kubernetes-8-ka-worker-shi-yong-dan-zhang-gpu-ge-li</id>
                <published>2026-02-22T18:33:54+08:00</published>
                <updated>2026-06-29T17:58:56+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="gpu-on-kubernetes%EF%BC%9A8-%E5%8D%A1-worker-%E4%B8%8A-1-%E4%B8%AA-pod-%E5%8F%AA%E4%BD%BF%E7%94%A8-1-%E5%BC%A0-gpu-%E7%9A%84%E9%9A%94%E7%A6%BB%E4%B8%8E-run%3Aai-%E8%B0%83%E5%BA%A6%E5%AE%9E%E8%B7%B5" tabindex="-1">GPU on Kubernetes：8 卡 Worker 上 1 个 Pod 只使用 1 张 GPU 的隔离与 Run:ai 调度实践</h1><p>上一篇文章解决的是“GPU 节点如何接入 Kubernetes，让 Pod 能调度到 GPU Worker 上”。</p><p>这篇文章继续往下走一步：</p><blockquote><p>一台 Worker 有 8 张 GPU，我不想让一个 Pod 使用整台机器，只想让它使用其中 1 张 GPU，应该怎么做隔离？能不能配合 NVIDIA Run:ai 做管控调度？Run:ai 是不是开源免费？</p></blockquote><p>先给结论：</p><ul><li><strong>生产默认方案</strong>：使用 NVIDIA GPU Operator 或 NVIDIA device plugin 暴露 <code>nvidia.com/gpu</code>，Pod 声明 <code>limits.nvidia.com/gpu: 1</code>。这表示 Pod 申请并独占 1 张 full GPU，不是独占整台 8 卡 Worker。</li><li><strong>传统 device plugin 模式下</strong>：不能在普通 Pod spec 里可靠表达“我要这台机器上的物理 GPU 3”。Kubernetes 调度器看到的是节点资源数量，具体哪张卡由 device plugin/kubelet/runtime 分配。</li><li><strong>需要硬隔离</strong>：优先考虑 MIG，但前提是 GPU 型号支持 MIG。</li><li><strong>需要提升利用率、能接受弱隔离</strong>：可以考虑 time-slicing 或 MPS。</li><li><strong>需要队列、配额、公平性、多租户治理</strong>：开源生产优先评估 Kueue、Volcano、KAI Scheduler；商业平台再评估 NVIDIA Run:ai。</li><li><strong>Run:ai 平台本身不要理解成开源免费平台</strong>。当前可公开确认的是 <strong>KAI Scheduler 是开源调度器</strong>；NVIDIA Run:ai 是面向企业 AI 集群的商业平台能力，需要按 NVIDIA/Run:ai 官方授权和产品形态评估。</li></ul><p><img src="/upload/2026/06/one-pod-one-gpu-isolation.svg" alt="one-pod-one-gpu-isolation" /></p><h2 id="1.-nvidia.com%2Fgpu%3A-1-%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E4%B9%88%E6%84%8F%E6%80%9D" tabindex="-1">1. <code>nvidia.com/gpu: 1</code> 到底是什么意思</h2><p>假设一台 Worker 有 8 张 NVIDIA GPU。</p><p>NVIDIA device plugin 正常工作后，节点会向 kubelet 注册扩展资源：</p><pre><code class="language-yaml">status:  capacity:    nvidia.com/gpu: &quot;8&quot;  allocatable:    nvidia.com/gpu: &quot;8&quot;</code></pre><p>业务 Pod 只需要声明：</p><pre><code class="language-yaml">resources:  limits:    nvidia.com/gpu: 1</code></pre><p>这表示：</p><ul><li>调度器只会把 Pod 放到至少还有 1 张可分配 GPU 的节点。</li><li>kubelet 会向 device plugin 请求 1 个 GPU device。</li><li>NVIDIA runtime 会把分配到的 GPU 注入容器。</li><li>容器内通常只看得到这一张 GPU。</li></ul><p>所以，<strong>1 个 Pod 申请 <code>nvidia.com/gpu: 1</code>，不是占用整台 8 卡 Worker，而是占用该节点上的 1 张 GPU 资源。</strong></p><p>Kubernetes 官方对 GPU 这类扩展资源还有一个重要规则：</p><ul><li>GPU 资源只能写在 <code>limits</code>。</li><li>如果同时写 <code>requests</code> 和 <code>limits</code>，两者必须相等。</li><li>只写 <code>limits</code> 时，Kubernetes 会把它当作 request 使用。</li></ul><p>因此，最推荐的写法就是只写：</p><pre><code class="language-yaml">resources:  limits:    nvidia.com/gpu: 1</code></pre><h2 id="2.-%E6%9C%80%E5%B0%8F%E5%8F%AF%E8%90%BD%E5%9C%B0-pod-%E7%A4%BA%E4%BE%8B" tabindex="-1">2. 最小可落地 Pod 示例</h2><p>下面是一个 1 Pod / 1 GPU 的最小验证示例。</p><pre><code class="language-yaml">apiVersion: v1kind: Podmetadata:  name: one-gpuspec:  restartPolicy: Never  runtimeClassName: nvidia  nodeSelector:    nvidia.com/gpu.present: &quot;true&quot;  tolerations:    - key: nvidia.com/gpu      operator: Exists      effect: NoSchedule  containers:    - name: app      image: nvidia/cuda:12.4.1-base-ubuntu22.04      command: [&quot;bash&quot;, &quot;-lc&quot;, &quot;nvidia-smi -L &amp;&amp; echo GPU_OK &amp;&amp; sleep infinity&quot;]      resources:        limits:          nvidia.com/gpu: 1</code></pre><p>如果你的集群把 NVIDIA runtime 配成了默认 runtime，<code>runtimeClassName: nvidia</code> 可以不写。</p><p>但我更建议显式写上。这样排障时不会混淆“资源已经分配”和“容器是否走 NVIDIA runtime”这两件事。</p><p>部署：</p><pre><code class="language-bash">kubectl apply -f one-gpu.yamlkubectl wait --for=condition=Ready pod/one-gpu --timeout=300skubectl logs one-gpu</code></pre><p>验证容器内可见 GPU：</p><pre><code class="language-bash">kubectl exec one-gpu -- nvidia-smi -Lkubectl exec one-gpu -- bash -lc &#39;echo $NVIDIA_VISIBLE_DEVICES&#39;</code></pre><p>容器里常见输出是：</p><pre><code class="language-text">GPU 0: NVIDIA ...</code></pre><p>这里的 <code>GPU 0</code> 是容器内部可见设备编号，<strong>不等于宿主机物理 GPU0</strong>。</p><p>严谨验证应使用 GPU UUID 对齐。</p><p>宿主机侧：</p><pre><code class="language-bash">nvidia-smi -L</code></pre><p>容器侧：</p><pre><code class="language-bash">kubectl exec one-gpu -- nvidia-smi -L</code></pre><p>如果容器内只看到 1 个 GPU UUID，就说明“1 Pod / 1 GPU”的设备可见性隔离已经生效。</p><h2 id="3.-%E9%9A%94%E7%A6%BB%E4%BA%86%E4%BB%80%E4%B9%88%EF%BC%8C%E6%B2%A1%E6%9C%89%E9%9A%94%E7%A6%BB%E4%BB%80%E4%B9%88" tabindex="-1">3. 隔离了什么，没有隔离什么</h2><p>默认 <code>nvidia.com/gpu: 1</code> 提供的是：</p><ul><li>调度层面的整数资源独占。</li><li>容器可见设备隔离。</li><li>runtime 注入层面的 GPU device 控制。</li></ul><p>它不提供：</p><ul><li>GPU 显存配额细分。</li><li>同一张 GPU 上多个租户之间的硬隔离。</li><li>防止特权容器绕过设备隔离的安全边界。</li><li>按物理 GPU index 的稳定调度契约。</li></ul><p>换句话说，默认 full GPU 模式的语义是：</p><pre><code class="language-text">这张卡分给这个容器，这个容器只看得到这张卡。</code></pre><p>不是：</p><pre><code class="language-text">这张卡的 20% 显存和 20% 算力分给这个容器。</code></pre><p>如果你需要“显存/算力份额”这种能力，需要看 MIG、time-slicing、MPS 或 DRA，而不是普通 <code>nvidia.com/gpu: 1</code>。</p><h2 id="4.-%E4%B8%8D%E8%A6%81%E6%89%8B%E5%86%99-nvidia_visible_devices-%E6%9D%A5%E6%8A%A2%E5%8D%A1" tabindex="-1">4. 不要手写 <code>NVIDIA_VISIBLE_DEVICES</code> 来抢卡</h2><p>有些人会尝试在 Pod 里写：</p><pre><code class="language-yaml">env:  - name: NVIDIA_VISIBLE_DEVICES    value: &quot;3&quot;</code></pre><p>这不是生产推荐方式。</p><p>原因是：</p><ul><li>它绕开 Kubernetes device plugin 的分配语义。</li><li>它可能和 kubelet/device plugin 的分配结果冲突。</li><li>GPU index 并不是可靠的长期契约。</li><li>驱动、硬件枚举、节点维护后，index 可能造成误解。</li></ul><p>更稳的做法是：</p><ul><li>让 device plugin 分配 GPU。</li><li>用 GPU UUID 做观测和审计。</li><li>如果必须按特定 GPU 属性选择，评估 DRA。</li></ul><h2 id="5.-%E8%83%BD%E4%B8%8D%E8%83%BD%E6%8C%87%E5%AE%9A%E6%9F%90%E4%B8%80%E5%BC%A0%E7%89%A9%E7%90%86-gpu" tabindex="-1">5. 能不能指定某一张物理 GPU</h2><p>传统 device plugin 模式下，普通 Pod spec 不能可靠表达：</p><pre><code class="language-text">我要 &lt;gpu-worker-01&gt; 上的 GPU-UUID-xxx</code></pre><p>你可以做到的是：</p><h3 id="5.1-%E5%9B%BA%E5%AE%9A%E5%88%B0%E6%9F%90%E5%8F%B0-gpu-%E8%8A%82%E7%82%B9" tabindex="-1">5.1 固定到某台 GPU 节点</h3><pre><code class="language-yaml">nodeSelector:  kubernetes.io/hostname: &lt;gpu-worker-01&gt;</code></pre><p>或用 node affinity：</p><pre><code class="language-yaml">affinity:  nodeAffinity:    requiredDuringSchedulingIgnoredDuringExecution:      nodeSelectorTerms:        - matchExpressions:            - key: kubernetes.io/hostname              operator: In              values:                - &lt;gpu-worker-01&gt;</code></pre><p>这只能固定节点，不能固定该节点里的某张卡。</p><h3 id="5.2-%E7%94%A8-uuid-%E5%81%9A%E9%AA%8C%E8%AF%81%EF%BC%8C%E4%B8%8D%E7%94%A8-index-%E5%81%9A%E5%A5%91%E7%BA%A6" tabindex="-1">5.2 用 UUID 做验证，不用 index 做契约</h3><p>宿主机：</p><pre><code class="language-bash">nvidia-smi -L</code></pre><p>容器：</p><pre><code class="language-bash">kubectl exec one-gpu -- nvidia-smi -L</code></pre><p>把容器里看到的 UUID 和宿主机 UUID 对齐。</p><p>注意：容器内部看到的 <code>GPU 0</code> 通常只是容器视角的第 0 张可见卡。</p><h3 id="5.3-%E9%9C%80%E8%A6%81%E6%8C%89-uuid%2Fpci%2F%E6%8B%93%E6%89%91%E8%B0%83%E5%BA%A6%EF%BC%8C%E8%AF%84%E4%BC%B0-dra" tabindex="-1">5.3 需要按 UUID/PCI/拓扑调度，评估 DRA</h3><p>DRA 是 Kubernetes 面向动态设备分配的新机制。</p><p>它允许通过 <code>ResourceClaim</code>、<code>DeviceClass</code>、<code>ResourceSlice</code> 表达更细的设备选择。</p><p>概念上可以写成：</p><pre><code class="language-yaml">apiVersion: resource.k8s.io/v1kind: ResourceClaimTemplatemetadata:  name: gpu-by-uuidspec:  spec:    devices:      requests:        - name: gpu          exactly:            deviceClassName: gpu.nvidia.com            selectors:              - cel:                  expression: device.attributes[&quot;gpu.nvidia.com&quot;].uuid == &quot;GPU-xxxx&quot;</code></pre><p>但这不是所有集群都能直接使用。</p><p>它依赖：</p><ul><li>Kubernetes 版本。</li><li>NVIDIA DRA driver。</li><li>GPU Operator 版本。</li><li>CDI 支持。</li><li>driver 版本。</li><li>ResourceSlice 实际暴露了哪些字段。</li></ul><p>因此，DRA 更适合新集群规划或平台化建设，不建议在已有生产集群里临时硬塞。</p><h2 id="6.-%E4%B8%89%E7%A7%8D%E9%9A%94%E7%A6%BB%2F%E5%85%B1%E4%BA%AB%E6%A8%A1%E5%BC%8F%E7%9A%84%E8%BE%B9%E7%95%8C" tabindex="-1">6. 三种隔离/共享模式的边界</h2><p><img src="/upload/2026/06/gpu-isolation-decision-flow.svg" alt="gpu-isolation-decision-flow" /></p><p>这张图是简化决策图：先区分“整卡独占、硬隔离、弱共享、平台调度”。完整开源生产选型建议看第 9 节，那里会把 Kueue、Volcano、KAI Scheduler 分开。</p><h3 id="6.1-full-gpu-%E7%8B%AC%E5%8D%A0" tabindex="-1">6.1 Full GPU 独占</h3><p>这是最简单、最稳的默认方案。</p><p>写法：</p><pre><code class="language-yaml">resources:  limits:    nvidia.com/gpu: 1</code></pre><p>适合：</p><ul><li>训练任务。</li><li>性能敏感推理。</li><li>单租户或强可预测资源场景。</li><li>不希望互相干扰的业务。</li></ul><p>优点：</p><ul><li>调度语义清楚。</li><li>风险低。</li><li>排障简单。</li><li>和大多数框架兼容。</li></ul><p>缺点：</p><ul><li>小任务可能浪费 GPU。</li><li>无法表达“半张卡”。</li></ul><h3 id="6.2-mig%EF%BC%9A%E7%A1%AC%E5%88%87%E5%88%86%E9%9A%94%E7%A6%BB" tabindex="-1">6.2 MIG：硬切分隔离</h3><p>MIG 可以把一张支持 MIG 的 GPU 切成多个硬件实例。</p><p>每个 MIG 实例有相对独立的计算、显存、缓存和带宽边界。</p><p>适合：</p><ul><li>多租户推理。</li><li>需要更强隔离的小模型任务。</li><li>A100/H100 等支持 MIG 的 GPU。</li></ul><p>典型资源名可能类似：</p><pre><code class="language-text">nvidia.com/mig-1g.10gbnvidia.com/mig-2g.20gb</code></pre><p>Pod 示例：</p><pre><code class="language-yaml">resources:  limits:    nvidia.com/mig-1g.10gb: 1</code></pre><p>注意：</p><ul><li>MIG 需要 GPU 硬件支持。</li><li>MIG 模式/切分策略变更会影响节点上已有 workload。</li><li>生产上应通过 GPU Operator 的 MIG Manager 管理，而不是手工在节点上随意切换。</li></ul><h3 id="6.3-time-slicing%EF%BC%9A%E5%BC%B1%E9%9A%94%E7%A6%BB%E5%85%B1%E4%BA%AB" tabindex="-1">6.3 Time-slicing：弱隔离共享</h3><p>time-slicing 会把一张 GPU 暴露成多个逻辑 replica。</p><p>比如 8 张 GPU，每张切成 10 个 replica，节点可能显示出 80 个逻辑共享资源。</p><p>适合：</p><ul><li>开发调试。</li><li>轻量推理。</li><li>低优先级任务。</li><li>可以接受互相影响的场景。</li></ul><p>风险：</p><ul><li>没有显存硬隔离。</li><li>没有故障硬隔离。</li><li>一个进程 OOM 或异常可能影响共享体验。</li><li>申请多个共享 GPU 不代表获得线性算力。</li></ul><p>最佳实践：</p><ul><li>使用 <code>renameByDefault=true</code> 把共享资源改名为 <code>nvidia.com/gpu.shared</code>。</li><li>使用 <code>failRequestsGreaterThanOne=true</code>，避免用户误以为申请 2 个 shared GPU 就有 2 倍算力。</li></ul><p>示意：</p><pre><code class="language-yaml">resources:  limits:    nvidia.com/gpu.shared: 1</code></pre><h3 id="6.4-mps%EF%BC%9A%E5%85%B1%E4%BA%AB%E4%BD%86%E6%9B%B4%E5%8F%AF%E6%8E%A7%EF%BC%8C%E4%BB%8D%E9%9C%80%E8%B0%A8%E6%85%8E" tabindex="-1">6.4 MPS：共享但更可控，仍需谨慎</h3><p>MPS 可以让多个 CUDA 进程共享一张 GPU，并通过 MPS daemon 做一定的计算和显存份额控制。</p><p>相比 time-slicing，它更可控一些。</p><p>但在 Kubernetes device plugin 语境里，MPS 支持仍应谨慎看待：</p><ul><li>不是所有 GPU/模式都适合。</li><li>和 time-slicing 互斥。</li><li>不适合和 MIG-enabled 设备混用。</li><li>运维复杂度高于 full GPU 独占。</li></ul><p>除非你明确知道 workload 特性，否则生产默认仍建议 full GPU 或 MIG。</p><h2 id="7.-run%3Aai-%E8%83%BD%E8%A7%A3%E5%86%B3%E4%BB%80%E4%B9%88" tabindex="-1">7. Run:ai 能解决什么</h2><p>NVIDIA Run:ai 可以理解为面向 AI 集群的 GPU 资源治理平台。</p><p>它关注的问题不是“一个容器怎样看到某张 GPU”，而是更上层的治理：</p><ul><li>多租户项目。</li><li>队列。</li><li>配额。</li><li>公平调度。</li><li>GPU 利用率提升。</li><li>训练/推理 workload 编排。</li><li>组织级资源可视化。</li><li>管理员策略控制。</li></ul><p>也就是说，Run:ai 可以和 NVIDIA GPU Operator、device plugin、MIG、共享 GPU 等能力配合。</p><p>它更像是：</p><pre><code class="language-text">平台治理层 + 调度层 + 资源抽象层</code></pre><p>而不是单纯替代 <code>nvidia.com/gpu: 1</code>。</p><h2 id="8.-run%3Aai-%E6%98%AF%E4%B8%8D%E6%98%AF%E5%BC%80%E6%BA%90%E5%85%8D%E8%B4%B9" tabindex="-1">8. Run:ai 是不是开源免费</h2><p>这个问题要拆开说。</p><h3 id="8.1-kai-scheduler-%E6%98%AF%E5%BC%80%E6%BA%90%E7%9A%84" tabindex="-1">8.1 KAI Scheduler 是开源的</h3><p>KAI Scheduler 是 NVIDIA 开源的 Kubernetes 原生调度器，来自 Run:ai 的调度能力。</p><p>它适合关注：</p><ul><li>队列。</li><li>公平性。</li><li>GPU sharing。</li><li>AI workload 的调度优化。</li></ul><p>如果你的目标是研究或落地开源 GPU 调度器，可以评估 KAI Scheduler。</p><h3 id="8.2-nvidia-run%3Aai-%E5%B9%B3%E5%8F%B0%E4%B8%8D%E5%BA%94%E9%BB%98%E8%AE%A4%E7%90%86%E8%A7%A3%E6%88%90%E5%BC%80%E6%BA%90%E5%85%8D%E8%B4%B9" tabindex="-1">8.2 NVIDIA Run:ai 平台不应默认理解成开源免费</h3><p>NVIDIA Run:ai 是完整企业平台能力，包含远不止调度器的部分。</p><p>它通常涉及：</p><ul><li>平台控制面。</li><li>用户/项目/租户。</li><li>配额和策略。</li><li>可视化。</li><li>企业集成。</li><li>SaaS 或 self-hosted 部署形态。</li><li>商业支持。</li></ul><p>因此，<strong>不能把“开源 KAI Scheduler”直接等同于“完整 Run:ai 平台开源免费”。</strong></p><p>如果要采购或生产使用完整 Run:ai 平台，应以 NVIDIA/Run:ai 官方授权、报价和部署合同为准。</p><h2 id="9.-%E5%AE%8C%E6%95%B4%E5%BC%80%E6%BA%90%E7%94%9F%E4%BA%A7%E8%90%BD%E5%9C%B0%E6%96%B9%E6%A1%88" tabindex="-1">9. 完整开源生产落地方案</h2><p>如果目标是“开源、可生产落地”，不要把所有能力都压到一个调度器里。</p><p>更稳的做法是分层：</p><pre><code class="language-text">底座层：  NVIDIA Driver  NVIDIA Container Toolkit  NVIDIA device plugin / GPU Operator  DCGM Exporter资源隔离层：  full GPU 独占  MIG  time-slicing / MPS  DRA平台治理层：  Kueue / Volcano / KAI Scheduler  namespace / quota / priority / queue  多团队资源池 / 队列 / 公平共享 / Gang Scheduling</code></pre><h3 id="9.1-%E6%96%B9%E6%A1%88-a%EF%BC%9A%E5%9F%BA%E7%A1%80%E7%94%9F%E4%BA%A7%E6%A0%88%EF%BC%8C%E6%9C%80%E6%8E%A8%E8%8D%90%E5%85%88%E8%90%BD%E5%9C%B0" tabindex="-1">9.1 方案 A：基础生产栈，最推荐先落地</h3><p>这是最适合先上线的方案。</p><p>组件：</p><ul><li>NVIDIA GPU Operator</li><li>NVIDIA device plugin</li><li>GPU Feature Discovery</li><li>DCGM Exporter</li><li>kube-scheduler</li><li>Namespace / RBAC / ResourceQuota</li><li>Taint / Toleration</li><li>Prometheus / Grafana</li></ul><p>适合：</p><ul><li>1 Pod / 1 full GPU。</li><li>小团队或单团队 GPU 集群。</li><li>训练任务数量还没有明显排队。</li><li>优先要求稳定、可观测、容易排障。</li></ul><p>这套方案就可以回答本文最核心的问题：</p><pre><code class="language-text">8 卡 Worker 上，1 个 Pod 只使用 1 张 GPU。</code></pre><p>Pod 侧只需要：</p><pre><code class="language-yaml">resources:  limits:    nvidia.com/gpu: 1</code></pre><p>Namespace 配额控制团队上限：</p><pre><code class="language-yaml">apiVersion: v1kind: ResourceQuotametadata:  name: gpu-quota  namespace: team-aspec:  hard:    requests.nvidia.com/gpu: &quot;4&quot;    limits.nvidia.com/gpu: &quot;4&quot;</code></pre><p>这套方案的优点是足够朴素。</p><p>朴素不是缺点。GPU 生产环境里，第一优先级通常不是“调度器功能最多”，而是：</p><ul><li>资源语义清楚。</li><li>任务失败容易定位。</li><li>不发生隐性超卖。</li><li>不让用户误以为共享 GPU 是硬隔离。</li><li>监控和告警能解释 GPU 利用率、显存、温度、XID 错误。</li></ul><h3 id="9.2-%E6%96%B9%E6%A1%88-b%EF%BC%9Akueue%EF%BC%8C%E5%A4%9A%E5%9B%A2%E9%98%9F%E6%8E%92%E9%98%9F%E5%92%8C%E9%85%8D%E9%A2%9D%E6%B2%BB%E7%90%86" tabindex="-1">9.2 方案 B：Kueue，多团队排队和配额治理</h3><p>当 GPU 集群开始被多个团队共享，单纯 <code>ResourceQuota</code> 往往不够。</p><p>典型问题是：</p><ul><li>team-a 一次提交很多 Job，占满 GPU。</li><li>team-b 有配额但抢不到队列顺序。</li><li>不同 GPU 型号需要做资源池区分。</li><li>希望任务先进入队列，等资源满足后再 admission。</li></ul><p>这时可以引入 Kueue。</p><p>组件：</p><ul><li>NVIDIA GPU Operator</li><li>Kueue</li><li>Kubernetes Job / JobSet</li><li>Kubeflow Training Operator</li><li>ResourceFlavor</li><li>ClusterQueue / LocalQueue</li></ul><p>Kueue 的核心不是替代 kubelet 或 device plugin，而是做批任务 admission 和队列配额。</p><p>推荐结构：</p><pre><code class="language-text">ResourceFlavor:  表达资源类型，例如 A800、H800、L40S、RTX 4090ClusterQueue:  表达集群级资源池和配额LocalQueue:  namespace 内团队提交入口Workload:  Kueue 接管的待 admission 任务</code></pre><p>适合：</p><ul><li>多团队训练。</li><li>Job/JobSet 形态任务。</li><li>Kubeflow PyTorchJob、MPIJob、RayJob 等可和 Kueue 集成的任务。</li><li>希望资源先排队，再被准入。</li></ul><p>不适合：</p><ul><li>只是要跑少量长驻 Deployment。</li><li>只需要最简单的 1 Pod / 1 GPU。</li><li>团队还没有明显的排队和公平共享诉求。</li></ul><p>Kueue 落地建议：</p><ol><li>先把 GPU 型号打成 node label。</li><li>用 ResourceFlavor 对应 GPU 型号或节点池。</li><li>用 ClusterQueue 给团队分配 nominal quota。</li><li>用 LocalQueue 作为 namespace 内提交入口。</li><li>训练任务统一通过 Job/JobSet 或 Training Operator 提交。</li></ol><h3 id="9.3-%E6%96%B9%E6%A1%88-c%EF%BC%9Avolcano%EF%BC%8C%E5%88%86%E5%B8%83%E5%BC%8F%E8%AE%AD%E7%BB%83%E5%92%8C-gang-scheduling" tabindex="-1">9.3 方案 C：Volcano，分布式训练和 Gang Scheduling</h3><p>如果你的训练任务是分布式的，比如：</p><ul><li>8 个 worker 必须一起启动。</li><li>部分 Pod 先启动也没意义。</li><li>训练框架需要 gang scheduling。</li><li>Spark、Ray、Flink、MPI、PyTorch 分布式训练很多。</li></ul><p>这时 Volcano 更合适。</p><p>组件：</p><ul><li>NVIDIA GPU Operator</li><li>Volcano Scheduler</li><li>Volcano Queue</li><li>VolcanoJob</li><li>PodGroup</li><li>PyTorchJob / MPIJob / Spark/Ray 集成</li></ul><p>Volcano 的核心价值是 batch scheduling 和 gang scheduling。</p><p>它适合解决：</p><pre><code class="language-text">一组 Pod 要么一起拿到资源并启动，要么都等着。</code></pre><p>否则很容易出现：</p><ul><li>rank 0 启动了，rank 1~7 没资源。</li><li>GPU 被部分 Pod 占住，但任务跑不起来。</li><li>集群资源碎片化。</li><li>分布式训练长时间卡住。</li></ul><p>适合：</p><ul><li>大规模分布式训练。</li><li>大数据和 AI 混部。</li><li>需要队列、优先级、Gang Scheduling 的集群。</li></ul><p>不适合：</p><ul><li>单 Pod / 单卡为主。</li><li>只需要普通 Deployment 或简单 Job。</li><li>团队还没有分布式训练队列治理需求。</li></ul><h3 id="9.4-%E6%96%B9%E6%A1%88-d%EF%BC%9Akai-scheduler%EF%BC%8Crun%3Aai-%E8%B0%83%E5%BA%A6%E5%99%A8%E7%9A%84%E5%BC%80%E6%BA%90%E8%B7%AF%E7%BA%BF" tabindex="-1">9.4 方案 D：KAI Scheduler，Run:ai 调度器的开源路线</h3><p>KAI Scheduler 是 NVIDIA 开源的 Kubernetes 原生 GPU 调度器，源自 Run:ai 的调度能力。</p><p>它适合评估这些方向：</p><ul><li>GPU-aware scheduling。</li><li>AI/ML workload 调度优化。</li><li>队列和公平共享。</li><li>gang scheduling。</li><li>workload priority。</li><li>更贴近 Run:ai 调度器思路的开源方案。</li></ul><p>但要注意两个边界：</p><p>第一，KAI Scheduler 是开源调度器，不是完整 Run:ai 平台。</p><p>第二，生产引入任何新调度器，都要 PoC：</p><ul><li>是否和现有 workload API 兼容。</li><li>是否支持你当前的训练入口。</li><li>是否和 GPU Operator、MIG、time-slicing、MPS 策略一致。</li><li>是否能接入现有监控、审计和告警。</li><li>失败时是否容易回滚到默认 kube-scheduler。</li></ul><p>我的建议是：</p><pre><code class="language-text">如果你明确想走 Run:ai 风格的开源调度路线，再评估 KAI Scheduler。如果只是多团队排队，先看 Kueue。如果是强 gang scheduling 和 AI/big data batch，先看 Volcano。</code></pre><h3 id="9.5-%E5%BC%80%E6%BA%90%E6%96%B9%E6%A1%88%E9%80%89%E5%9E%8B%E8%A1%A8" tabindex="-1">9.5 开源方案选型表</h3><table><thead><tr><th>场景</th><th>推荐开源方案</th><th>是否建议第一阶段引入</th></tr></thead><tbody><tr><td>1 Pod 独占 1 张 full GPU</td><td>GPU Operator + device plugin + ResourceQuota</td><td>是</td></tr><tr><td>多团队 GPU 配额和排队</td><td>Kueue + GPU Operator</td><td>第二阶段</td></tr><tr><td>分布式训练，Pod 必须一起启动</td><td>Volcano + GPU Operator</td><td>按训练形态引入</td></tr><tr><td>类 Run:ai 的开源 GPU 调度方向</td><td>KAI Scheduler + GPU Operator</td><td>PoC 后引入</td></tr><tr><td>一张 GPU 硬切分给多个租户</td><td>GPU Operator + MIG</td><td>仅支持 MIG 的 GPU</td></tr><tr><td>一张 GPU 弱共享给多个小任务</td><td>GPU Operator + time-slicing/MPS</td><td>谨慎引入</td></tr><tr><td>按 GPU UUID/属性/拓扑精确选择</td><td>NVIDIA DRA Driver + DRA</td><td>新集群规划优先</td></tr></tbody></table><h3 id="9.6-%E6%88%91%E6%8E%A8%E8%8D%90%E7%9A%84%E5%BC%80%E6%BA%90%E7%94%9F%E4%BA%A7%E8%90%BD%E5%9C%B0%E9%A1%BA%E5%BA%8F" tabindex="-1">9.6 我推荐的开源生产落地顺序</h3><p>第一阶段：先稳住 GPU 底座。</p><pre><code class="language-text">GPU Operatordevice pluginGPU Feature DiscoveryDCGM ExporterResourceQuotataint / tolerationPrometheus / Grafana</code></pre><p>目标：</p><ul><li>节点正确上报 <code>nvidia.com/gpu: 8</code>。</li><li>业务 Pod 使用 <code>nvidia.com/gpu: 1</code>。</li><li>容器只看到 1 张 GPU。</li><li>团队 namespace 有 GPU 配额。</li><li>监控能看到 GPU 使用率和错误。</li></ul><p>第二阶段：引入队列。</p><p>如果是普通训练 Job 排队，优先 Kueue。</p><p>如果是分布式训练和 gang scheduling，优先 Volcano。</p><p>第三阶段：评估高级调度。</p><p>如果想贴近 Run:ai 的 GPU 调度能力，再 PoC KAI Scheduler。</p><p>如果需要完整平台能力，比如 UI、租户、项目、策略、审计、企业支持，再评估商业 Run:ai。</p><p>第四阶段：精细化设备模型。</p><p>如果有硬隔离诉求，评估 MIG。</p><p>如果有低风险共享诉求，评估 time-slicing/MPS。</p><p>如果有按设备属性选择诉求，评估 DRA。</p><p>这个顺序的好处是每一步都能独立验收，不会一上来把 GPU 驱动、device plugin、队列、共享、平台全部混在一起排障。</p><h2 id="10.-%E7%94%9F%E4%BA%A7%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E6%B8%85%E5%8D%95" tabindex="-1">10. 生产最佳实践清单</h2><p>节点侧：</p><pre><code class="language-bash">kubectl describe node &lt;gpu-node&gt; | grep -A10 -E &#39;Capacity|Allocatable&#39;kubectl get node &lt;gpu-node&gt; \  -L nvidia.com/gpu.present,nvidia.com/gpu.count,nvidia.com/gpu.product,nvidia.com/gpu.replicas,nvidia.com/gpu.sharing-strategy</code></pre><p>期望：</p><ul><li>full GPU 独占模式下，8 卡节点显示 <code>nvidia.com/gpu: 8</code>。</li><li>如果显示远大于 8，要检查是否启用了 time-slicing。</li><li>如果出现 <code>nvidia.com/gpu.shared</code>，说明共享资源命名已经启用。</li></ul><p>Pod 侧：</p><pre><code class="language-bash">kubectl exec one-gpu -- nvidia-smi -Lkubectl exec one-gpu -- bash -lc &#39;echo $NVIDIA_VISIBLE_DEVICES&#39;</code></pre><p>调度侧：</p><pre><code class="language-bash">kubectl describe pod one-gpukubectl get pod one-gpu -o wide</code></pre><p>安全侧：</p><ul><li>不给普通业务容器 <code>privileged: true</code>。</li><li>不手写 <code>NVIDIA_VISIBLE_DEVICES</code>。</li><li>不把 GPU 节点开放给任意 namespace。</li><li>用 <code>ResourceQuota</code> 限制 namespace 可用 GPU 数量。</li><li>用 taint/toleration 控制 GPU 节点调度入口。</li><li>用 DCGM Exporter 监控 GPU 利用率、显存、温度和错误。</li></ul><p>Namespace 配额示例：</p><pre><code class="language-yaml">apiVersion: v1kind: ResourceQuotametadata:  name: gpu-quota  namespace: team-aspec:  hard:    requests.nvidia.com/gpu: &quot;4&quot;    limits.nvidia.com/gpu: &quot;4&quot;</code></pre><p>GPU 节点 taint 示例：</p><pre><code class="language-bash">kubectl taint node &lt;gpu-node&gt; nvidia.com/gpu=true:NoSchedule</code></pre><p>业务 Pod 显式容忍：</p><pre><code class="language-yaml">tolerations:  - key: nvidia.com/gpu    operator: Equal    value: &quot;true&quot;    effect: NoSchedule</code></pre><h2 id="11.-%E6%88%91%E7%9A%84%E6%8E%A8%E8%8D%90%E8%B7%AF%E5%BE%84" tabindex="-1">11. 我的推荐路径</h2><p>如果你是从 0 到 1 落地 GPU on Kubernetes，我建议这样走：</p><ol><li>先用 GPU Operator 或 device plugin 跑通 full GPU 独占。</li><li>业务统一用 <code>limits.nvidia.com/gpu: 1</code> 1 GPU。</li><li>用 namespace quota、taint/toleration、priority class 做基础治理。</li><li>用 DCGM Exporter 做 GPU 观测。</li><li>多团队普通训练 Job 排队，优先评估 Kueue。</li><li>分布式训练需要 gang scheduling，优先评估 Volcano。</li><li>想走 Run:ai 调度器的开源路线，再 PoC KAI Scheduler。</li><li>如果 GPU 支持 MIG，并且有多租户硬隔离诉求，再启用 MIG。</li><li>如果只是开发/低风险推理，需要提高利用率，再评估 time-slicing/MPS。</li><li>如果必须按 UUID/拓扑选择设备，规划 DRA，不要用环境变量硬指定。</li><li>如果需要完整平台 UI、企业支持、跨团队策略和审计，再评估商业 Run:ai。</li></ol><p>一句话总结：</p><blockquote><p>“1 Pod 使用 1 张 GPU”用 <code>nvidia.com/gpu: 1</code> ；多团队排队优先 Kueue；分布式训练优先 Volcano；类 Run:ai 开源调度路线再看 KAI Scheduler；完整企业平台能力再评估商业 Run:ai。</p></blockquote><h2 id="%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" tabindex="-1">参考资料</h2><ul><li><a href="https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/" target="_blank">Kubernetes：Schedule GPUs</a></li><li><a href="https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/" target="_blank">Kubernetes：Device Plugins</a></li><li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/dynamic-resource-allocation/" target="_blank">Kubernetes：Dynamic Resource Allocation</a></li><li><a href="https://github.com/NVIDIA/k8s-device-plugin" target="_blank">NVIDIA k8s-device-plugin</a></li><li><a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/index.html" target="_blank">NVIDIA GPU Operator</a></li><li><a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html" target="_blank">NVIDIA GPU Operator：GPU Sharing</a></li><li><a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html" target="_blank">NVIDIA GPU Operator：MIG</a></li><li><a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/dra-intro-install.html" target="_blank">NVIDIA GPU Operator：DRA</a></li><li><a href="https://github.com/NVIDIA/k8s-dra-driver-gpu" target="_blank">NVIDIA DRA Driver for GPUs</a></li><li><a href="https://kueue.sigs.k8s.io/" target="_blank">Kueue</a></li><li><a href="https://volcano.sh/" target="_blank">Volcano</a></li><li><a href="https://github.com/NVIDIA/KAI-Scheduler" target="_blank">KAI Scheduler</a></li><li><a href="https://docs.nvidia.com/run-ai/" target="_blank">NVIDIA Run:ai Documentation</a></li></ul>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[GPU-on-K8s-RKE2落地方案-POD算力多网卡]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/gpu-on-k8s-rke2-luo-de-fang-an--pod-suan-li-duo-wang-ka" />
                <id>tag:https://blog.yongjie.top,2026-02-21:gpu-on-k8s-rke2-luo-de-fang-an--pod-suan-li-duo-wang-ka</id>
                <published>2026-02-21T16:53:20+08:00</published>
                <updated>2026-06-29T17:58:44+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<hr /><p>title: “GPU on Kubernetes 落地：RKE2、NVIDIA Runtime、Multus 与可登录训练 Pod”<br />tags:</p><ul><li>Kubernetes</li><li>RKE2</li><li>GPU</li><li>NVIDIA</li><li>Multus</li><li>Whereabouts</li><li>MLOps</li></ul><hr /><h1 id="gpu-on-kubernetes-%E8%90%BD%E5%9C%B0%EF%BC%9Arke2%E3%80%81nvidia-runtime%E3%80%81multus-%E4%B8%8E%E5%8F%AF%E7%99%BB%E5%BD%95%E8%AE%AD%E7%BB%83-pod" tabindex="-1">GPU on Kubernetes 落地：RKE2、NVIDIA Runtime、Multus 与可登录训练 Pod</h1><blockquote><p>这篇文章解决一个很常见、也很容易被低估的问题：</p></blockquote><p><strong>机器上 <code>nvidia-smi</code> 明明能看到 GPU，为什么 Kubernetes 还是调度不了 GPU Pod？</strong></p><p>答案通常不在“显卡有没有插好”这一层，而在这条链路里：</p><pre><code class="language-text">Host NVIDIA Driver  -&gt; NVIDIA Container Toolkit  -&gt; containerd 的 nvidia runtime handler  -&gt; Kubernetes RuntimeClass  -&gt; NVIDIA device plugin 初始化 NVML  -&gt; kubelet 上报 nvidia.com/gpu  -&gt; 业务 Pod request/limit nvidia.com/gpu</code></pre><p>只要其中一环断开，Kubernetes 调度器就可能“看不见” GPU。</p><p>本文基于 RKE2 + containerd + NVIDIA Container Toolkit + NVIDIA device plugin + Multus + Whereabouts 的组合，整理一套可复制的 GPU on K8s 落地方案。</p><p><img src="/upload/2026/06/gpu-on-k8s-architecture.svg" alt="gpu-on-k8s-architecture" /></p><h2 id="1.-%E6%96%B9%E6%A1%88%E7%9B%AE%E6%A0%87" tabindex="-1">1. 方案目标</h2><p>本次目标不是单纯跑通一个 <code>nvidia-smi</code> 测试 Pod，而是要交付一个可用于训练、调试和后续扩展的基础形态：</p><ul><li>GPU 节点加入 RKE2 集群。</li><li>Kubernetes 能正确识别并调度 <code>nvidia.com/gpu</code>。</li><li>GPU Pod 显式使用 NVIDIA RuntimeClass。</li><li>提供一个可 SSH 登录的 GPU 训练 Pod，便于交互式调试。</li><li>保留默认 Pod 网络，同时通过 Multus 添加二层网卡。</li><li>支持固定 IP 或地址池自动分配。</li><li>支持后续扩展为管理网、算力网多网卡模型。</li><li>明确容器模型下“系统盘”和“数据盘”的边界。</li></ul><p>公开博客里的示例采用这些占位符：</p><table><thead><tr><th>项目</th><th>示例</th></tr></thead><tbody><tr><td>GPU 节点</td><td><code>&lt;gpu-worker-01&gt;</code></td></tr><tr><td>GPU 节点内网 IP</td><td><code>&lt;node-ip&gt;</code></td></tr><tr><td>GPU 节点主机二层网卡</td><td><code>&lt;master-nic&gt;</code>，例如 <code>enp5s0</code> 或 <code>bond0</code></td></tr><tr><td>管理二层网段</td><td><code>10.20.30.0/24</code></td></tr><tr><td>Pod 固定二层 IP</td><td><code>10.20.30.240/24</code></td></tr><tr><td>Whereabouts 地址池</td><td><code>10.20.30.241-10.20.30.250/24</code></td></tr><tr><td>SSH NodePort</td><td><code>30022</code>，仅作示例</td></tr><tr><td>镜像仓库</td><td>按你的环境替换为公网仓库或内网镜像仓库</td></tr></tbody></table><h2 id="2.-%E4%B8%BA%E4%BB%80%E4%B9%88%E2%80%9C%E8%8A%82%E7%82%B9%E6%9C%89-gpu%E2%80%9D%E4%B8%8D%E7%AD%89%E4%BA%8E%E2%80%9Ck8s-%E8%83%BD%E8%B0%83%E5%BA%A6-gpu%E2%80%9D" tabindex="-1">2. 为什么“节点有 GPU”不等于“K8s 能调度 GPU”</h2><p>很多 GPU 接入问题都卡在一个认知误区：</p><blockquote><p><code>nvidia-smi</code> 在宿主机能跑通，Kubernetes 就应该自动知道这台机器有 GPU。</p></blockquote><p>实际上不是。</p><p>Kubernetes 不会天然扫描宿主机上的所有 GPU 并把它们变成可调度资源。它依赖 device plugin 框架把厂商设备注册给 kubelet。</p><p>NVIDIA GPU 的常见资源名是：</p><pre><code class="language-text">nvidia.com/gpu</code></pre><p>只有当 NVIDIA device plugin 在节点上成功启动、成功初始化 NVML、成功向 kubelet 注册后，节点状态里才会出现类似：</p><pre><code class="language-yaml">status:  capacity:    nvidia.com/gpu: &quot;1&quot;  allocatable:    nvidia.com/gpu: &quot;1&quot;</code></pre><p>如果这里没有 <code>nvidia.com/gpu</code>，调度器就不会把这个节点当作 GPU 节点。</p><p><img src="/upload/2026/06/gpu-resource-registration-chain.svg" alt="gpu-resource-registration-chain" /></p><h2 id="3.-%E6%9C%AC%E6%AC%A1%E9%97%AE%E9%A2%98%E7%9A%84%E5%85%B8%E5%9E%8B%E7%8E%B0%E8%B1%A1" tabindex="-1">3. 本次问题的典型现象</h2><p>落地过程中看到的典型现象是：</p><ul><li>GPU 节点已经打了标签，例如 <code>nvidia.com/gpu.present=true</code>。</li><li>宿主机执行 <code>nvidia-smi</code> 正常。</li><li>业务 Pod 申请了 <code>nvidia.com/gpu: 1</code> 后一直 Pending。</li><li><code>kubectl describe node</code> 看不到 <code>nvidia.com/gpu</code> capacity。</li><li>NVIDIA device plugin 日志出现类似错误：</li></ul><pre><code class="language-text">Failed to initialize NVML: could not load NVML library.</code></pre><p>这类错误的关键不是“device plugin 本身坏了”，而是它的容器没有通过 NVIDIA runtime 启动。</p><p>device plugin 容器如果走默认 <code>runc</code>，容器内可能拿不到 NVIDIA 驱动库和 NVML 能力。它就无法发现 GPU，也无法向 kubelet 注册 <code>nvidia.com/gpu</code>。</p><p>最终表现就是：节点上有卡，但 Kubernetes 看不到卡。</p><h2 id="4.-%E8%8A%82%E7%82%B9%E4%BE%A7%E5%9F%BA%E7%BA%BF%EF%BC%9A%E9%A9%B1%E5%8A%A8%E3%80%81toolkit%E3%80%81containerd" tabindex="-1">4. 节点侧基线：驱动、Toolkit、containerd</h2><p>GPU 节点首先要满足宿主机层面的条件：</p><ol><li>NVIDIA 驱动安装完成。</li><li>宿主机 <code>nvidia-smi</code> 正常。</li><li>NVIDIA Container Toolkit 安装完成。</li><li>containerd 已配置 <code>nvidia</code> runtime handler。</li><li>RKE2 agent 重启后加载 containerd 配置。</li></ol><p>Ubuntu/Debian 系节点安装 Toolkit 的官方流程大致是：</p><pre><code class="language-bash">sudo apt-get updatesudo apt-get install -y --no-install-recommends ca-certificates curl gnupg2curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \  | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpgcurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \  | sed &#39;s#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g&#39; \  | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.listsudo apt-get updatesudo apt-get install -y nvidia-container-toolkit</code></pre><p>然后配置 containerd：</p><pre><code class="language-bash">sudo nvidia-ctk runtime configure --runtime=containerd</code></pre><p>普通 containerd 环境通常会生成或修改：</p><pre><code class="language-text">/etc/containerd/conf.d/99-nvidia.toml/etc/containerd/config.toml</code></pre><p>但 RKE2 使用的是内置 containerd。实际配置路径通常在：</p><pre><code class="language-text">/var/lib/rancher/rke2/agent/etc/containerd/config.toml</code></pre><p>所以在 RKE2 场景里要特别确认：RKE2 最终生成的 containerd 配置里确实包含 <code>nvidia</code> runtime handler，并且 handler 指向 <code>nvidia-container-runtime</code>。</p><p>可在 GPU 节点上检查：</p><pre><code class="language-bash">sudo grep -n &quot;nvidia&quot; /var/lib/rancher/rke2/agent/etc/containerd/config.toml || truewhich nvidia-container-runtimenvidia-smi</code></pre><p>如果修改过 runtime 配置，重启 RKE2 agent：</p><pre><code class="language-bash">sudo systemctl restart rke2-agent</code></pre><h2 id="5.-runtimeclass%EF%BC%9A%E6%8A%8A-nvidia-runtime-%E6%9A%B4%E9%9C%B2%E7%BB%99-pod" tabindex="-1">5. RuntimeClass：把 nvidia runtime 暴露给 Pod</h2><p>Kubernetes 的 RuntimeClass 用来让 Pod 选择不同的容器运行时配置。</p><p>如果你没有把 NVIDIA runtime 设置成节点默认 runtime，就应该显式创建一个 <code>RuntimeClass/nvidia</code>：</p><pre><code class="language-yaml">apiVersion: node.k8s.io/v1kind: RuntimeClassmetadata:  name: nvidiahandler: nvidia</code></pre><p>验证：</p><pre><code class="language-bash">kubectl get runtimeclasskubectl get runtimeclass nvidia -o yaml</code></pre><p>业务 Pod 使用时写：</p><pre><code class="language-yaml">spec:  runtimeClassName: nvidia</code></pre><p>这一步非常关键。</p><p>仅仅申请 <code>nvidia.com/gpu: 1</code>，并不必然代表容器会用 NVIDIA runtime 启动。资源分配和运行时选择是两件事。</p><p>在本文方案里，我们让两类 Pod 都显式使用 <code>runtimeClassName: nvidia</code>：</p><ul><li>NVIDIA device plugin DaemonSet。</li><li>实际使用 GPU 的业务 Pod。</li></ul><h2 id="6.-%E4%BF%AE%E5%A4%8D%E5%85%B3%E9%94%AE%EF%BC%9Adevice-plugin-%E8%87%AA%E5%B7%B1%E4%B9%9F%E8%A6%81%E8%B5%B0-nvidia-runtime" tabindex="-1">6. 修复关键：device plugin 自己也要走 NVIDIA runtime</h2><p>很多人只关注业务 Pod 的 <code>runtimeClassName</code>，但这次真正卡住的是 device plugin 自己。</p><p>device plugin 的职责是发现 GPU、监控 GPU 健康状态、向 kubelet 注册资源。</p><p>如果 device plugin 这个容器没有通过 NVIDIA runtime 启动，它可能连 NVML 都加载不到。于是 kubelet 就收不到 GPU 注册信息。</p><p>下面是一个脱敏后的 DaemonSet 示例。</p><p>生产环境建议通过 Helm 或 GitOps 固定版本。示例中的镜像请按你的环境替换为官方镜像、企业镜像仓库或可信镜像代理。</p><pre><code class="language-yaml">apiVersion: apps/v1kind: DaemonSetmetadata:  name: nvidia-device-plugin-daemonset  namespace: kube-systemspec:  selector:    matchLabels:      app: nvidia-device-plugin-ds  template:    metadata:      labels:        app: nvidia-device-plugin-ds    spec:      runtimeClassName: nvidia      priorityClassName: system-node-critical      nodeSelector:        nvidia.com/gpu.present: &quot;true&quot;      tolerations:        - key: CriticalAddonsOnly          operator: Exists        - key: nvidia.com/gpu          operator: Exists          effect: NoSchedule      containers:        - name: nvidia-device-plugin-ctr          image: nvcr.io/nvidia/k8s-device-plugin:&lt;固定版本&gt;          imagePullPolicy: IfNotPresent          securityContext:            allowPrivilegeEscalation: false            capabilities:              drop: [&quot;ALL&quot;]          volumeMounts:            - name: device-plugin              mountPath: /var/lib/kubelet/device-plugins      volumes:        - name: device-plugin          hostPath:            path: /var/lib/kubelet/device-plugins</code></pre><p>几个点要讲清楚：</p><ul><li><code>runtimeClassName: nvidia</code>：让 plugin 容器能访问 NVIDIA runtime 注入的能力。</li><li><code>nodeSelector</code>：只跑在标记过的 GPU 节点上。</li><li><code>priorityClassName</code>：把它作为关键节点组件处理。</li><li><code>/var/lib/kubelet/device-plugins</code>：device plugin 和 kubelet 通过这个目录下的 socket 通信。</li></ul><p>给 GPU 节点打标签：</p><pre><code class="language-bash">kubectl label node &lt;gpu-worker-01&gt; nvidia.com/gpu.present=true</code></pre><p>应用并等待：</p><pre><code class="language-bash">kubectl apply -f nvidia-plugin.yamlkubectl -n kube-system rollout status ds/nvidia-device-plugin-daemonset --timeout=600s</code></pre><h2 id="7.-gpu-%E6%B3%A8%E5%86%8C%E9%AA%8C%E8%AF%81" tabindex="-1">7. GPU 注册验证</h2><p>先看 device plugin Pod：</p><pre><code class="language-bash">kubectl -n kube-system get pod -o wide | grep nvidia-device-pluginkubectl -n kube-system logs -l app=nvidia-device-plugin-ds --tail=200</code></pre><p>期望看到类似：</p><pre><code class="language-text">Loading NVMLRegistered device plugin with Kubelet</code></pre><p>再看节点资源：</p><pre><code class="language-bash">kubectl get node &lt;gpu-worker-01&gt; \  -o jsonpath=&#39;capacity={.status.capacity.nvidia\.com/gpu} allocatable={.status.allocatable.nvidia\.com/gpu}{&quot;\n&quot;}&#39;</code></pre><p>期望：</p><pre><code class="language-text">capacity=1 allocatable=1</code></pre><p>如果这里仍为空，排查顺序建议是：</p><ol><li>宿主机 <code>nvidia-smi</code> 是否正常。</li><li>RKE2 containerd 配置里是否有 <code>nvidia</code> runtime。</li><li><code>RuntimeClass/nvidia</code> 是否存在，handler 是否为 <code>nvidia</code>。</li><li>device plugin Pod 是否真的使用了 <code>runtimeClassName: nvidia</code>。</li><li>device plugin 日志是否还有 NVML 错误。</li></ol><h2 id="8.-%E6%9C%80%E5%B0%8F-gpu-%E6%B5%8B%E8%AF%95-pod" tabindex="-1">8. 最小 GPU 测试 Pod</h2><p>节点上报 GPU 后，用一个最小 Pod 验证调度链路：</p><pre><code class="language-yaml">apiVersion: v1kind: Podmetadata:  name: gpu-smoke-testspec:  restartPolicy: Never  runtimeClassName: nvidia  nodeSelector:    kubernetes.io/hostname: &lt;gpu-worker-01&gt;  tolerations:    - key: nvidia.com/gpu      operator: Exists      effect: NoSchedule  containers:    - name: cuda      image: nvidia/cuda:12.4.1-base-ubuntu22.04      command: [&quot;bash&quot;, &quot;-lc&quot;, &quot;nvidia-smi &amp;&amp; echo GPU_OK&quot;]      resources:        limits:          nvidia.com/gpu: 1</code></pre><p>执行：</p><pre><code class="language-bash">kubectl apply -f gpu-smoke-test.yamlkubectl wait --for=jsonpath=&#39;{.status.phase}&#39;=Succeeded pod/gpu-smoke-test --timeout=300skubectl logs gpu-smoke-testkubectl delete pod gpu-smoke-test</code></pre><p>日志里看到 <code>nvidia-smi</code> 输出和 <code>GPU_OK</code>，说明 GPU 从节点注册到业务容器访问这条链路已经跑通。</p><h2 id="9.-%E5%8F%AF-ssh-%E7%99%BB%E5%BD%95%E7%9A%84-gpu-%E8%AE%AD%E7%BB%83-pod" tabindex="-1">9. 可 SSH 登录的 GPU 训练 Pod</h2><p>很多训练场景早期需要交互式调试。最直接的交付形态是一个 StatefulSet：</p><ul><li><code>runtimeClassName: nvidia</code>。</li><li>申请 <code>nvidia.com/gpu: 1</code>。</li><li>用 Headless Service 保留稳定 DNS。</li><li>用 NodePort 或后续 LoadBalancer 暴露 SSH。</li><li>用 Secret 注入 SSH 公钥或临时密码。</li><li>通过 startupProbe 避免首次安装工具时被 kubelet 误杀。</li></ul><p>正式生产不建议直接 <code>root + 密码 + NodePort</code>。下面示例使用 SSH key，并禁用密码登录。</p><p>先创建 SSH 公钥 Secret：</p><pre><code class="language-bash">kubectl create namespace gpu-trainkubectl -n gpu-train create secret generic gpu-ssh-authorized-keys \  --from-file=authorized_keys=./authorized_keys</code></pre><p>StatefulSet 示例：</p><pre><code class="language-yaml">apiVersion: v1kind: Servicemetadata:  name: gpu-train-headless  namespace: gpu-trainspec:  clusterIP: None  selector:    app: gpu-train  ports:    - name: ssh      port: 22      targetPort: 22---apiVersion: v1kind: Servicemetadata:  name: gpu-train-ssh  namespace: gpu-trainspec:  type: NodePort  selector:    app: gpu-train  ports:    - name: ssh      port: 22      targetPort: 22      nodePort: 30022---apiVersion: apps/v1kind: StatefulSetmetadata:  name: gpu-train  namespace: gpu-trainspec:  serviceName: gpu-train-headless  replicas: 1  selector:    matchLabels:      app: gpu-train  template:    metadata:      labels:        app: gpu-train    spec:      runtimeClassName: nvidia      nodeSelector:        kubernetes.io/hostname: &lt;gpu-worker-01&gt;      terminationGracePeriodSeconds: 10      containers:        - name: main          image: nvidia/cuda:12.4.1-base-ubuntu22.04          imagePullPolicy: IfNotPresent          command: [&quot;bash&quot;, &quot;-lc&quot;]          args:            - |              set -euo pipefail              export DEBIAN_FRONTEND=noninteractive              apt-get -o Acquire::ForceIPv4=true -o Acquire::Retries=5 update              apt-get -o Acquire::ForceIPv4=true -o Acquire::Retries=5 install -y --no-install-recommends \                ca-certificates \                openssh-server \                iproute2 \                net-tools \                iputils-ping \                curl \                wget \                git \                vim-tiny \                less \                procps              mkdir -p /run/sshd /root/.ssh              cp /ssh/authorized_keys /root/.ssh/authorized_keys              chmod 700 /root/.ssh              chmod 600 /root/.ssh/authorized_keys              sed -ri &#39;s/^#?PermitRootLogin[[:space:]]+.*/PermitRootLogin prohibit-password/&#39; /etc/ssh/sshd_config              sed -ri &#39;s/^#?PasswordAuthentication[[:space:]]+.*/PasswordAuthentication no/&#39; /etc/ssh/sshd_config              sed -ri &#39;s/^#?PubkeyAuthentication[[:space:]]+.*/PubkeyAuthentication yes/&#39; /etc/ssh/sshd_config              exec /usr/sbin/sshd -D -e          volumeMounts:            - name: ssh-authorized-keys              mountPath: /ssh              readOnly: true          ports:            - name: ssh              containerPort: 22          startupProbe:            tcpSocket:              port: 22            periodSeconds: 5            failureThreshold: 360          readinessProbe:            tcpSocket:              port: 22            periodSeconds: 5          livenessProbe:            tcpSocket:              port: 22            periodSeconds: 10          resources:            limits:              nvidia.com/gpu: 1      volumes:        - name: ssh-authorized-keys          secret:            secretName: gpu-ssh-authorized-keys</code></pre><p>部署并验证：</p><pre><code class="language-bash">kubectl apply -f gpu-ssh.yamlkubectl -n gpu-train rollout status statefulset/gpu-train --timeout=1200skubectl -n gpu-train get pod -o widekubectl -n gpu-train logs -f gpu-train-0</code></pre><p>登录：</p><pre><code class="language-bash">ssh root@&lt;node-ip&gt; -p 30022</code></pre><p>进入容器后验证：</p><pre><code class="language-bash">nvidia-smi -Lip -br a</code></pre><p>这个形态适合早期调试和小规模训练环境。规模化训练平台建议进一步封装为镜像、模板、队列和租户权限模型。</p><h2 id="10.-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81-multus%EF%BC%9A%E9%BB%98%E8%AE%A4-pod-ip-%E4%B8%8D%E9%80%82%E5%90%88%E6%89%80%E6%9C%89%E8%AE%AD%E7%BB%83%E6%B5%81%E9%87%8F" tabindex="-1">10. 为什么需要 Multus：默认 Pod IP 不适合所有训练流量</h2><p>Kubernetes 默认只给 Pod 创建一张网卡，通常叫 <code>eth0</code>。</p><p>这张网卡由主 CNI 负责，例如 Cilium、Calico、Flannel 等。它适合服务发现、集群内通信和普通业务访问。</p><p>但 GPU 训练场景常常还有额外需求：</p><ul><li>Pod 需要一个和物理网络同网段的固定 IP。</li><li>运维人员希望直接 SSH 到训练环境。</li><li>分布式训练希望流量走独立算力网卡。</li><li>RoCE、IB 等网络可能需要绕过默认 Overlay。</li><li>多租户场景希望管理网、数据网、训练网分离。</li></ul><p>这时可以用 Multus 给 Pod 追加第二张、第三张网卡。</p><p><img src="/upload/2026/06/multus-ipvlan-whereabouts-flow.svg" alt="multus-ipvlan-whereabouts-flow" /></p><p>本文的网络模型是：</p><pre><code class="language-text">eth0: 主 CNI 创建，保留 Kubernetes 默认 Pod 网络net1: Multus 创建，ipvlan L2，接入管理二层网络net2: 可选，Multus 创建，接入算力网络</code></pre><h2 id="11.-rke2-%E5%AE%89%E8%A3%85-multus-%2B-whereabouts" tabindex="-1">11. RKE2 安装 Multus + Whereabouts</h2><p>RKE2 官方支持通过内置 HelmChart 方式安装 Multus，并可启用 <code>rke2-whereabouts</code>。</p><p>示例：</p><pre><code class="language-yaml">apiVersion: helm.cattle.io/v1kind: HelmChartmetadata:  name: rke2-multus  namespace: kube-systemspec:  repo: https://rke2-charts.rancher.io  chart: rke2-multus  targetNamespace: kube-system  valuesContent: |    rke2-whereabouts:      enabled: true</code></pre><p>应用：</p><pre><code class="language-bash">kubectl apply -f rke2-multus.yamlkubectl -n kube-system rollout status ds/rke2-multus --timeout=600skubectl -n kube-system rollout status ds/rke2-multus-rke2-whereabouts --timeout=600s</code></pre><p>验证 NAD CRD：</p><pre><code class="language-bash">kubectl get crd network-attachment-definitions.k8s.cni.cncf.io</code></pre><p>验证 CNI 二进制：</p><pre><code class="language-bash">ls -l /opt/cni/bin | egrep &#39;multus|whereabouts|ipvlan|macvlan|static|host-local&#39; || true</code></pre><p>注意一个容易踩的点：</p><p><code>rke2-whereabouts</code> DaemonSet 起来，不代表 Whereabouts 所需的所有 CRD 一定已存在。使用 <code>ipam.type: whereabouts</code> 前，应确认：</p><pre><code class="language-bash">kubectl get crd | grep whereabouts.cni.cncf.io</code></pre><p>至少应看到类似：</p><pre><code class="language-text">ippools.whereabouts.cni.cncf.iooverlappingrangeipreservations.whereabouts.cni.cncf.io</code></pre><p>如果缺少 CRD，Pod 可能卡在 <code>ContainerCreating</code>，事件里出现找不到 <code>ippools.whereabouts.cni.cncf.io</code> 的错误。</p><h2 id="12.-%E5%88%9B%E5%BB%BA-networkattachmentdefinition" tabindex="-1">12. 创建 NetworkAttachmentDefinition</h2><p>NAD 是 Multus 读取的二网卡配置对象。</p><p>下面提供两种模式：</p><ul><li><code>ipvlan-static</code>：固定 IP。</li><li><code>ipvlan-pool</code>：Whereabouts 从地址池自动分配 IP。</li></ul><p>把 <code>&lt;master-nic&gt;</code> 替换成 GPU 节点上真实存在、已经连入对应二层网络的网卡名。</p><pre><code class="language-yaml">apiVersion: k8s.cni.cncf.io/v1kind: NetworkAttachmentDefinitionmetadata:  name: ipvlan-static  namespace: gpu-trainspec:  config: |    {      &quot;cniVersion&quot;: &quot;0.4.0&quot;,      &quot;name&quot;: &quot;ipvlan-static&quot;,      &quot;type&quot;: &quot;ipvlan&quot;,      &quot;master&quot;: &quot;&lt;master-nic&gt;&quot;,      &quot;mode&quot;: &quot;l2&quot;,      &quot;ipam&quot;: {        &quot;type&quot;: &quot;static&quot;,        &quot;addresses&quot;: [          { &quot;address&quot;: &quot;10.20.30.240/24&quot; }        ]      }    }---apiVersion: k8s.cni.cncf.io/v1kind: NetworkAttachmentDefinitionmetadata:  name: ipvlan-pool  namespace: gpu-trainspec:  config: |    {      &quot;cniVersion&quot;: &quot;0.4.0&quot;,      &quot;name&quot;: &quot;ipvlan-pool&quot;,      &quot;type&quot;: &quot;ipvlan&quot;,      &quot;master&quot;: &quot;&lt;master-nic&gt;&quot;,      &quot;mode&quot;: &quot;l2&quot;,      &quot;ipam&quot;: {        &quot;type&quot;: &quot;whereabouts&quot;,        &quot;range&quot;: &quot;10.20.30.241-10.20.30.250/24&quot;      }    }</code></pre><p>应用：</p><pre><code class="language-bash">kubectl apply -f gpu-train-nads.yamlkubectl -n gpu-train get net-attach-def</code></pre><p>让 Pod 挂载固定 IP 二网卡，在 Pod 模板 annotation 里加入：</p><pre><code class="language-yaml">metadata:  annotations:    k8s.v1.cni.cncf.io/networks: |      [        { &quot;name&quot;: &quot;ipvlan-static&quot;, &quot;namespace&quot;: &quot;gpu-train&quot;, &quot;interface&quot;: &quot;net1&quot; }      ]</code></pre><p>如果使用地址池：</p><pre><code class="language-yaml">metadata:  annotations:    k8s.v1.cni.cncf.io/networks: |      [        { &quot;name&quot;: &quot;ipvlan-pool&quot;, &quot;namespace&quot;: &quot;gpu-train&quot;, &quot;interface&quot;: &quot;net1&quot; }      ]</code></pre><p>重建 Pod：</p><pre><code class="language-bash">kubectl apply -f gpu-ssh.yamlkubectl -n gpu-train delete pod gpu-train-0kubectl -n gpu-train wait --for=condition=Ready pod/gpu-train-0 --timeout=1200s</code></pre><p>验证事件：</p><pre><code class="language-bash">kubectl -n gpu-train describe pod gpu-train-0 | grep -A3 -E &#39;AddedInterface|multus&#39;</code></pre><p>期望看到类似：</p><pre><code class="language-text">Add net1 [10.20.30.240/24] from gpu-train/ipvlan-static</code></pre><p>进入容器：</p><pre><code class="language-bash">ip -br aip route</code></pre><p>同网段机器可以尝试：</p><pre><code class="language-bash">ssh root@10.20.30.240</code></pre><p>如果你已经通过 <code>net1</code> 固定 IP 登录，NodePort 可以逐步退场，改成更清晰的二层访问模型。</p><h2 id="13.-net1-state-unknown-%E4%B8%8D%E6%98%AF%E4%B8%80%E5%AE%9A%E5%9D%8F%E4%BA%86" tabindex="-1">13. <code>net1 state UNKNOWN</code> 不是一定坏了</h2><p>使用 <code>ipvlan</code> 时，Pod 内可能看到：</p><pre><code class="language-text">net1@if2  UNKNOWN  10.20.30.240/24</code></pre><p>这里的 <code>UNKNOWN</code> 是 Linux 接口 operstate，不等价于“网络不可用”。</p><p>很多虚拟接口没有物理网卡那样明确的 carrier 状态，所以内核无法准确显示 <code>UP</code> 或 <code>DOWN</code>，会显示 <code>UNKNOWN</code>。</p><p>更重要的是看 flags：</p><pre><code class="language-bash">ip -d link show net1</code></pre><p>如果能看到 <code>UP</code>、<code>LOWER_UP</code>，并且同网段访问正常，就不需要因为 <code>UNKNOWN</code> 误判故障。</p><p><code>ipvlan</code> L2 模式下，Pod 的 <code>net1</code> 复用宿主机 master 网卡收发二层流量。外部机器访问 <code>10.20.30.240</code> 时，流量会经宿主机物理网卡进入，再转到 Pod 网络命名空间。</p><p>需要注意：这类二层旁路网卡不一定受主 CNI 的 NetworkPolicy 约束。安全策略应在节点防火墙、上游交换网络、ACL 或独立 VLAN 上补齐。</p><h2 id="14.-%E6%89%A9%E5%B1%95%EF%BC%9A%E7%AE%A1%E7%90%86%E7%BD%91%E4%B8%8E%E7%AE%97%E5%8A%9B%E7%BD%91%E5%88%86%E7%A6%BB" tabindex="-1">14. 扩展：管理网与算力网分离</h2><p>更接近生产的 GPU 训练网络通常不是一张网卡打天下，而是至少分成：</p><ul><li>管理网：SSH、运维、控制面访问。</li><li>存储网：数据集、模型权重、检查点读写。</li><li>算力网：分布式训练通信，例如 NCCL/Gloo。</li></ul><p>如果宿主机有第二张物理网卡或 bond，可继续创建一个算力网 NAD：</p><pre><code class="language-yaml">apiVersion: k8s.cni.cncf.io/v1kind: NetworkAttachmentDefinitionmetadata:  name: ipvlan-compute-pool  namespace: gpu-trainspec:  config: |    {      &quot;cniVersion&quot;: &quot;0.4.0&quot;,      &quot;name&quot;: &quot;ipvlan-compute-pool&quot;,      &quot;type&quot;: &quot;ipvlan&quot;,      &quot;master&quot;: &quot;&lt;compute-nic&gt;&quot;,      &quot;mode&quot;: &quot;l2&quot;,      &quot;ipam&quot;: {        &quot;type&quot;: &quot;whereabouts&quot;,        &quot;range&quot;: &quot;192.168.100.10-192.168.100.250/24&quot;      }    }</code></pre><p>Pod 同时挂两张二层网卡：</p><pre><code class="language-yaml">metadata:  annotations:    k8s.v1.cni.cncf.io/networks: |      [        { &quot;name&quot;: &quot;ipvlan-static&quot;, &quot;namespace&quot;: &quot;gpu-train&quot;, &quot;interface&quot;: &quot;net1&quot; },        { &quot;name&quot;: &quot;ipvlan-compute-pool&quot;, &quot;namespace&quot;: &quot;gpu-train&quot;, &quot;interface&quot;: &quot;net2&quot; }      ]</code></pre><p>训练框架里显式绑定算力网卡，比改默认路由更稳妥。</p><p>NCCL 示例：</p><pre><code class="language-bash">export NCCL_SOCKET_IFNAME=net2</code></pre><p>Gloo 示例：</p><pre><code class="language-bash">export GLOO_SOCKET_IFNAME=net2</code></pre><p>不要随意把 Pod 默认路由改到算力网，否则可能影响访问 Kubernetes Service、DNS、镜像仓库和控制面。</p><h2 id="15.-ip-%E5%86%B2%E7%AA%81%EF%BC%9A%E4%BA%8C%E5%B1%82%E7%BD%91%E7%BB%9C%E9%87%8C%E6%9C%80%E9%9A%BE%E6%8E%92%E7%9A%84%E5%9D%91" tabindex="-1">15. IP 冲突：二层网络里最难排的坑</h2><p>Multus + ipvlan/macvlan 把 Pod 接到真实二层网络后，IP 冲突会比普通 Overlay 网络更麻烦。</p><p>如果使用 <code>static</code> IPAM，两个 Pod 写了同一个 IP，常见表现是：</p><ul><li>SSH 一会能连，一会超时。</li><li>同一个 IP 的 ARP 记录来回变化。</li><li>分布式训练随机断开、卡住、吞吐抖动。</li><li>故障看起来像训练框架问题，但根因在二层地址冲突。</li></ul><p>排查：</p><pre><code class="language-bash">ip neigh show | grep &#39;&lt;pod-l2-ip&gt;&#39; || truearp -an | grep &#39;&lt;pod-l2-ip&gt;&#39; || truekubectl -n gpu-train describe pod &lt;pod-name&gt; | grep -A5 -E &#39;multus|whereabouts|Failed&#39;</code></pre><p>建议：</p><ul><li>多副本优先使用 Whereabouts 地址池。</li><li>必须固定 IP 时，用模板生成每个副本独立 NAD。</li><li>地址池避开网关、交换机、宿主机、BMC、已有服务器地址。</li><li>更严谨的生产环境应使用独立 VLAN 或独立物理网络承载训练网。</li></ul><h2 id="16.-%E6%8C%81%E4%B9%85%E5%8C%96%EF%BC%9A%E5%AE%B9%E5%99%A8%E9%87%8C%E7%9A%84%E2%80%9C%E7%B3%BB%E7%BB%9F%E7%9B%98%E2%80%9D%E5%92%8C%E2%80%9C%E6%95%B0%E6%8D%AE%E7%9B%98%E2%80%9D" tabindex="-1">16. 持久化：容器里的“系统盘”和“数据盘”</h2><p>有些用户会提出一个很像虚拟机的需求：</p><blockquote><p>我希望训练 Pod 有系统盘和数据盘。重装系统时清空系统盘，但保留数据盘。</p></blockquote><p>在容器模型里，要先把语义讲清楚。</p><p>容器的 rootfs 来自镜像，默认是临时写层。你在容器里 <code>apt-get install</code> 到 <code>/usr/bin</code> 的内容，不会因为 StatefulSet 重建而长期稳定保留。</p><p>更推荐的做法是：</p><ul><li>把基础工具、CUDA、SSH、Python、conda 等做进自定义镜像。</li><li>用 PVC 挂载 <code>/data</code> 保存数据集、输出、模型权重。</li><li>可选再用一个 PVC 挂载 <code>/workspace</code> 或 <code>/system</code> 保存代码、环境产物和用户态工具。</li></ul><p>StatefulSet 增加两块盘：</p><pre><code class="language-yaml">spec:  volumeClaimTemplates:    - metadata:        name: workspace      spec:        accessModes: [&quot;ReadWriteOnce&quot;]        resources:          requests:            storage: 50Gi    - metadata:        name: data      spec:        accessModes: [&quot;ReadWriteOnce&quot;]        resources:          requests:            storage: 500Gi  template:    spec:      containers:        - name: main          volumeMounts:            - name: workspace              mountPath: /workspace            - name: data              mountPath: /data</code></pre><p>StatefulSet 会自动创建类似：</p><pre><code class="language-text">workspace-gpu-train-0data-gpu-train-0</code></pre><p>“重装系统但保留数据盘”的容器语义可以这样做：</p><pre><code class="language-bash">kubectl -n gpu-train delete pod gpu-train-0kubectl -n gpu-train delete pvc workspace-gpu-train-0# 不删除 data-gpu-train-0kubectl -n gpu-train get pvc data-gpu-train-0</code></pre><p>但如果你真正需要 VM 级别的系统盘、快照、回滚和重装语义，应考虑 KubeVirt、Harvester 或传统虚拟化，而不是把容器强行当 VM 使用。</p><h2 id="17.-%E5%AE%89%E5%85%A8%E5%9F%BA%E7%BA%BF" tabindex="-1">17. 安全基线</h2><p>GPU 训练环境常常被临时开放成“能登录就行”，但公开或半公开网络中这样风险很高。</p><p>建议至少做到：</p><ul><li>不把 SSH 密码写进 YAML 。</li><li>优先使用 SSH key，禁用密码登录。</li><li>NodePort 只允许内网、堡垒机或 VPN 访问。</li><li>二层 <code>net1/net2</code> 所在 VLAN 做 ACL。</li><li>不把 GPU Pod 暴露到办公网或公网。</li><li>镜像使用固定 tag 或 digest。</li><li>用独立 namespace、ResourceQuota 和 LimitRange 控制租户资源。</li><li>训练数据盘和模型盘配置权限、审计和备份。</li><li>对可登录 Pod 做生命周期管理，避免长期无人维护。</li></ul><p>如果只是临时调试，可以接受 NodePort。</p><p>如果是生产平台，应优先走：</p><pre><code class="language-text">用户 -&gt; VPN/堡垒机 -&gt; 平台审计 -&gt; Kubernetes API/Job -&gt; 受控训练环境</code></pre>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第三章-H20智算集群NCCL大规模集合通信压测与慢节点定位]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-san-zhang--h20-zhi-suan-ji-qun-nccl-da-gui-mo-ji-he-tong-xin-ya-ce-yu-man-jie-di-ding-wei" />
                <id>tag:https://blog.yongjie.top,2026-01-31:di-san-zhang--h20-zhi-suan-ji-qun-nccl-da-gui-mo-ji-he-tong-xin-ya-ce-yu-man-jie-di-ding-wei</id>
                <published>2026-01-31T13:17:02+08:00</published>
                <updated>2026-06-29T17:48:06+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="h20-%E9%9B%86%E7%BE%A4-nccl-%E5%A4%A7%E8%A7%84%E6%A8%A1%E9%9B%86%E5%90%88%E9%80%9A%E4%BF%A1%E5%8E%8B%E6%B5%8B%E4%B8%8E%E6%85%A2%E8%8A%82%E7%82%B9%E5%AE%9A%E4%BD%8D" tabindex="-1">H20 集群 NCCL 大规模集合通信压测与慢节点定位</h1><p>NCCL 是大规模 GPU 训练集群验收中最核心的指标之一。它不只是跑一个 <code>all_reduce_perf</code>，而是用集合通信的方式把 GPU、NIC、RoCE、路由、交换机、拥塞控制、驱动版本和节点一致性一起压出来。</p><p><img src="/upload/2026/06/h20-nccl-validation-flow-optimized.svg" alt="h20-nccl-validation-flow-optimized" /></p><h2 id="%E4%B8%80%E3%80%81nccl-%E9%AA%8C%E6%94%B6%E8%A6%81%E5%9B%9E%E7%AD%94%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98" tabindex="-1">一、NCCL 验收要回答什么问题</h2><p>大规模 NCCL 验收至少要回答四个问题。</p><p>第一，单机 8 卡是否健康。单机测试用来排除 GPU、驱动、CUDA、NCCL、本机拓扑问题。如果单机都不稳定，跨机测试没有意义。</p><p>第二，双机 16 卡是否稳定。双机测试是跨机通信的最小闭环，可以验证 RoCE、GID、HCA、GDR、业务网控制通道、MPI 启动链路是否正常。</p><p>第三，百台级性能曲线是否符合预期。64/128/256 机测试用于观察规模放大后的带宽曲线、尾延迟和慢节点。</p><p>第四，整网是否可复跑。350 机以上验收不是为了得到一个漂亮数字，而是证明网络、GPU 和软件栈具备生产稳定性。</p><h2 id="%E4%BA%8C%E3%80%81%E6%B5%8B%E8%AF%95%E6%8C%87%E6%A0%87%E5%A6%82%E4%BD%95%E8%A7%A3%E8%AF%BB" tabindex="-1">二、测试指标如何解读</h2><p>NCCL Tests 常见字段包括：</p><table><thead><tr><th>字段</th><th>含义</th></tr></thead><tbody><tr><td><code>size</code></td><td>消息大小，单位 Byte</td></tr><tr><td><code>time</code></td><td>完成一次通信操作的时间</td></tr><tr><td><code>algbw</code></td><td>算法带宽，反映应用视角的数据处理速率</td></tr><tr><td><code>busbw</code></td><td>总线带宽，按通信模式折算后的有效链路压力</td></tr><tr><td><code>#wrong</code></td><td>校验错误数量，必须为 0</td></tr></tbody></table><p>重点展示 <code>busbw</code>、消息大小、节点规模、测试轮次和错误数量。更好的方式是给出分规模结果矩阵和结论。</p><h2 id="%E4%B8%89%E3%80%81%E6%8E%A8%E8%8D%90%E6%B5%8B%E8%AF%95%E9%98%B6%E6%A2%AF" tabindex="-1">三、推荐测试阶梯</h2><p>生产验收不要一开始就跑 350 机。建议按下面顺序推进：</p><pre><code class="language-text">单机 8 卡  -&gt; 双机 16 卡  -&gt; 8/16 机冒烟  -&gt; 64 机小规模验收  -&gt; 128 机百台级验收  -&gt; 256 机大规模验收  -&gt; 350 机以上整网验收</code></pre><p>每个阶段都要记录：</p><table><thead><tr><th>记录项</th><th>说明</th></tr></thead><tbody><tr><td>节点清单</td><td>使用脱敏编号即可，例如 <code>node-001</code></td></tr><tr><td>GPU 数量</td><td>每个节点应为 8 卡</td></tr><tr><td>HCA 列表</td><td>公开文档使用 <code>&lt;train-hca-list&gt;</code></td></tr><tr><td>NCCL 参数</td><td>保留模板，隐藏真实业务细节</td></tr><tr><td>消息大小</td><td>例如 120M 到 34G</td></tr><tr><td>迭代次数</td><td>生产验收不要只跑少量 iteration</td></tr><tr><td>错误计数</td><td><code>#wrong</code> 必须为 0</td></tr><tr><td>结果曲线</td><td>关注稳定区间，不只看最大值</td></tr></tbody></table><h2 id="%E5%9B%9B%E3%80%81%E5%8F%82%E8%80%83%E5%91%BD%E4%BB%A4" tabindex="-1">四、参考命令</h2><p>单机测试模板：</p><pre><code class="language-bash">NCCL_ALGO=ring ./all_reduce_perf \  -g 8 \  -b 8M \  -e 12G \  -f 2</code></pre><p>双机和多机测试模板：</p><pre><code class="language-bash">mpirun -np &lt;total-ranks&gt; \  -H &lt;node-a&gt;:8,&lt;node-b&gt;:8,... \  --allow-run-as-root \  --bind-to none \  --map-by slot \  --mca btl_tcp_if_include &lt;business-bond&gt; \  --mca oob_tcp_if_include &lt;business-bond&gt; \  -x NCCL_SOCKET_IFNAME=&lt;business-bond&gt; \  -x UCX_NET_DEVICES=&lt;business-bond&gt; \  -x NCCL_IB_DISABLE=0 \  -x NCCL_IB_GID_INDEX=&lt;gid-index&gt; \  -x NCCL_IB_HCA=&lt;train-hca-list&gt; \  -x NCCL_IB_TC=&lt;traffic-class&gt; \  -x NCCL_NET_GDR_LEVEL=&lt;gdr-level&gt; \  ./all_reduce_perf \  -g 1 \  -b 120M \  -e 34G \  -f 2 \  -n 100</code></pre><p>AllToAll 测试模板：</p><pre><code class="language-bash">mpirun -np &lt;total-ranks&gt; \  -H &lt;node-list&gt; \  &lt;mpi-and-nccl-env&gt; \  ./alltoall_perf \  -b 1M \  -e 2048M \  -f 2 \  -g 1</code></pre><h2 id="%E4%BA%94%E3%80%81%E6%85%A2%E8%8A%82%E7%82%B9%E5%AE%9A%E4%BD%8D%E6%96%B9%E6%B3%95" tabindex="-1">五、慢节点定位方法</h2><p>慢节点定位要避免“看到带宽低就换卡”。建议按下面顺序收敛。</p><p>第一步，确认是否所有节点都慢。如果所有节点一起下降，优先看全局网络、交换机队列、PFC/ECN、DCQCN、NCCL 参数和业务网控制通道。如果只有少数节点慢，优先看节点本地。</p><p>第二步，做排除式测试。把可疑节点从集群里拿掉，复跑同规模或小一档规模测试。如果带宽恢复，说明可疑节点或其上联路径需要深入排查。</p><p>第三步，做单机 8 卡测试。确认本机 GPU、驱动、NVSwitch 或 PCIe 拓扑是否正常。</p><p>第四步，做单卡隔离。轮流排除某一张 GPU 或某一路 HCA，观察结果是否恢复：</p><pre><code class="language-bash"># 示例：排除最后一张 GPU，仅使用 GPU 0-6CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6 ./all_reduce_perf \  -g 7 \  -b 8M \  -e 12G \  -f 2</code></pre><p>第五步，检查网卡与 RoCE 参数。包括 GID、PFC、ECN、DSCP、CNP、DCQCN、MTU、错误包、丢包、交换机端口统计。</p><p>第六步，检查 GPU 和 NVSwitch。包括 <code>nvidia-smi</code>、ECC、row remapper、Fabric Manager、<code>dmesg</code>、<code>lspci</code>。</p><h2 id="%E5%85%AD%E3%80%81%E6%85%A2%E8%8A%82%E7%82%B9%E5%B8%B8%E8%A7%81%E5%8E%9F%E5%9B%A0%E7%9F%A9%E9%98%B5" tabindex="-1">六、慢节点常见原因矩阵</h2><table><thead><tr><th>现象</th><th>可能原因</th><th>建议动作</th></tr></thead><tbody><tr><td>单机 NCCL 异常</td><td>GPU、NVSwitch、驱动、CUDA/NCCL 版本</td><td>单机复测，查 <code>nvidia-smi</code> 和 Fabric</td></tr><tr><td>双机异常，单机正常</td><td>RoCE、GID、HCA、路由、GDR</td><td>查 <code>show_gids</code>、HCA、<code>nvidia_peermem</code></td></tr><tr><td>小规模正常，大规模掉速</td><td>PFC/ECN/DCQCN、交换机拥塞、ECMP 不均</td><td>查队列、CNP、交换机端口统计</td></tr><tr><td>某个节点加入就掉速</td><td>节点本地配置漂移或硬件异常</td><td>节点隔离，单卡/单 HCA 排查</td></tr><tr><td><code>#wrong</code> 不为 0</td><td>数据正确性问题，风险高</td><td>停止验收，查软件栈和硬件错误</td></tr><tr><td>Fabric Manager 异常</td><td>NVSwitch 未识别或服务启动失败</td><td>查 <code>dmesg</code>、<code>journalctl</code>、<code>lspci</code></td></tr></tbody></table><h2 id="%E4%B8%83%E3%80%81%E9%AA%8C%E6%94%B6%E6%8A%A5%E5%91%8A%E5%BA%94%E8%AF%A5%E6%80%8E%E4%B9%88%E5%86%99" tabindex="-1">七、验收报告应该怎么写</h2><p>一份可用的 NCCL 验收报告，建议至少包含：</p><ol><li>集群规模和脱敏节点范围。</li><li>GPU、驱动、CUDA、NCCL、OFED 版本矩阵。</li><li>RoCE 关键配置摘要。</li><li>单机、双机、小规模、百台级和整网测试结果。</li><li>AllReduce 和 AllToAll 指标说明。</li><li>慢节点处理记录和复测结果。</li></ol><h2 id="%E5%85%AB%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">八、总结</h2><p>智算集群 NCCL 验收的关键，不是一次跑出高带宽，而是形成从单机到整网的可复跑阶梯。AllReduce 能验证梯度同步类场景，AllToAll 能补充验证更复杂的多对多通信压力。慢节点定位要从 GPU、NIC、RoCE、Fabric、路由和参数一致性逐层排除。</p><p>NCCL 是验收工具，也是巡检工具。把 NCCL 测试做成标准化、脱敏化、可复跑的流程，后续扩容、替换、升级和故障恢复都会轻松很多。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第二章-H20集群RoCEv2算力网络调优最佳实践]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-er-zhang--h20-ji-qun-rocev2-suan-li-wang-luo-diao-you-zui-jia-shi-jian" />
                <id>tag:https://blog.yongjie.top,2026-01-11:di-er-zhang--h20-ji-qun-rocev2-suan-li-wang-luo-diao-you-zui-jia-shi-jian</id>
                <published>2026-01-11T20:44:58+08:00</published>
                <updated>2026-06-29T17:48:02+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="h20-%E9%9B%86%E7%BE%A4-rocev2-%E7%AE%97%E5%8A%9B%E7%BD%91%E7%BB%9C%E8%B0%83%E4%BC%98%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5" tabindex="-1">H20 集群 RoCEv2 算力网络调优最佳实践</h1><p>在 H20 训练集群里，GPU 通信性能的上限很大程度取决于 RoCEv2 网络工程质量。400G 网卡、Leaf-Spine、ECMP 这些硬件条件只是基础，真正决定稳定性的，是端到端参数是否一致、拥塞控制是否生效、GPU Direct RDMA 是否打通，以及所有配置是否能批量验证。</p><p><img src="/upload/2026/06/h20-rocev2-tuning-flow-optimized.svg" alt="h20-rocev2-tuning-flow-optimized" /></p><h2 id="%E4%B8%80%E3%80%81rocev2-%E8%B0%83%E4%BC%98%E7%9B%AE%E6%A0%87" tabindex="-1">一、RoCEv2 调优目标</h2><p>RoCEv2 调优不是追求某个单独参数“看起来正确”，而是让训练流量在大规模集合通信中稳定、低抖动、可定位。</p><p>生产环境建议同时关注这些目标：</p><table><thead><tr><th>目标</th><th>说明</th></tr></thead><tbody><tr><td>可达性</td><td>所有训练网卡 IP、GID、路由可达</td></tr><tr><td>一致性</td><td>BIOS、固件、OFED、驱动、NCCL、CUDA 版本一致</td></tr><tr><td>无损队列</td><td>PFC、ECN、DSCP、CNP 标记端到端匹配</td></tr><tr><td>拥塞控制</td><td>DCQCN 参数在每张训练网卡上固化</td></tr><tr><td>GPU Direct</td><td><code>nvidia_peermem</code> 加载，PCI ACS 不阻断 P2P</td></tr><tr><td>可回滚</td><td>每个调优动作有变更记录和回滚策略</td></tr><tr><td>可验收</td><td>单机、双机、百台级 NCCL 可复跑</td></tr></tbody></table><p>很多集群的问题不是“完全不能跑”，而是“小规模正常，大规模掉速”。这类问题通常与队列、路由、局部拥塞、少数节点配置漂移有关。</p><h2 id="%E4%BA%8C%E3%80%81rdma-%E4%B8%8E-gpu-direct-%E5%9F%BA%E7%BA%BF" tabindex="-1">二、RDMA 与 GPU Direct 基线</h2><p>第一步是让 RDMA 与 NVIDIA GPU Direct RDMA 的基础模块稳定加载。</p><pre><code class="language-bash">cat &gt;/etc/modules-load.d/rdma.conf &lt;&lt;&#39;EOF&#39;ib_coreib_umadib_uverbsib_cmrdma_cmiw_cmrdma_ucmmlx5_ibib_ipoibnvidia_peermemnvidia_uvmnvidia_modesetEOFfor module in ib_core ib_umad ib_uverbs ib_cm rdma_cm iw_cm rdma_ucm mlx5_ib ib_ipoib nvidia_peermem nvidia_uvm nvidia_modeset; do  modprobe &quot;${module}&quot;done</code></pre><p>验证时不要只看命令返回值，至少要检查：</p><pre><code class="language-bash">lsmod | egrep &#39;mlx5_ib|rdma_cm|nvidia_peermem&#39;ibv_devinfoshow_gidsnvidia-smi topo -m</code></pre><p><code>nvidia_peermem</code> 是 GPU 与 NIC 之间走 GDR 的关键模块。如果这个模块未加载或加载异常，NCCL 仍可能跑通，但跨机带宽和延迟会明显不符合预期。</p><h2 id="%E4%B8%89%E3%80%81%E7%BD%91%E5%8D%A1%E5%91%BD%E5%90%8D%E3%80%81mtu-%E4%B8%8E%E8%B7%AF%E7%94%B1" tabindex="-1">三、网卡命名、MTU 与路由</h2><p>8 卡 H20 服务器通常会有多张训练网卡。生产环境不建议依赖系统自动生成的 <code>enp*</code> 名称，因为 BIOS、固件、系统版本变化都可能导致接口名漂移。</p><p>建议做法是根据 PCI 地址生成稳定命名，并把训练网卡统一映射到约定名称，例如：</p><pre><code class="language-text">&lt;train-iface-1&gt; -&gt; bond2&lt;train-iface-2&gt; -&gt; bond3...&lt;train-iface-8&gt; -&gt; bond9</code></pre><p>这里的 <code>bond2-bond9</code> 更像是项目中的训练网卡命名约定，不一定代表传统意义上的双口链路聚合。这是“训练网卡稳定命名”，原始 udev 规则里的真实 PCI、MAC 和业务命名。</p><p>MTU 要和交换机端保持一致。但必须满足三个要求：</p><ol><li>服务器端、交换机端、对端服务器一致。</li><li>GID、路由、策略路由与 MTU 配置一并纳入自动化。</li><li>每次调整后通过 ping、RDMA 工具、NCCL 三层验证。</li></ol><h2 id="%E5%9B%9B%E3%80%81roce-%E6%A8%A1%E5%BC%8F%E4%B8%8E-gid-%E6%A3%80%E6%9F%A5" tabindex="-1">四、RoCE 模式与 GID 检查</h2><p>ConnectX 网卡需要确认链路类型和 RoCE 控制参数。模板如下：</p><pre><code class="language-bash">mst startfor dev in &lt;mst-device-list&gt;; do  mlxconfig -y -d &quot;${dev}&quot; set LINK_TYPE_P1=2  mlxconfig -y -d &quot;${dev}&quot; set ROCE_CONTROL=2donereboot</code></pre><p>重启后检查：</p><pre><code class="language-bash">for dev in &lt;mst-device-list&gt;; do  mlxconfig -d &quot;${dev}&quot; query | egrep &#39;LINK_TYPE|ROCE_CONTROL&#39;doneshow_gidscma_roce_mode -d &lt;mlx5-device&gt;</code></pre><p>如果 NCCL 使用了固定 <code>NCCL_IB_GID_INDEX</code>，就必须确认所有节点同一张训练网卡上的 GID index 语义一致。否则会出现部分节点通信正常、部分节点连接异常或性能异常的情况。</p><h2 id="%E4%BA%94%E3%80%81pfc%E3%80%81ecn%E3%80%81dscp-%E4%B8%8E-cnp" tabindex="-1">五、PFC、ECN、DSCP 与 CNP</h2><p>RoCEv2 建立在以太网上，训练网络需要对拥塞和丢包特别敏感。项目资料中采用了 DSCP 信任、PFC 队列、ECN 开关和 CNP DSCP 48 的组合思路。</p><pre><code class="language-bash">for iface in &lt;train-iface-list&gt;; do  mlnx_qos -i &quot;${iface}&quot; --trust dscp --pfc 0,0,0,0,0,1,1,0  echo 48 &gt; &quot;/sys/class/net/${iface}/ecn/roce_np/cnp_dscp&quot;  echo 1 &gt; &quot;/sys/class/net/${iface}/ecn/roce_np/enable/5&quot;  echo 1 &gt; &quot;/sys/class/net/${iface}/ecn/roce_rp/enable/5&quot;done</code></pre><p>检查：</p><pre><code class="language-bash">for iface in &lt;train-iface-list&gt;; do  echo &quot;### ${iface}&quot;  mlnx_qos -i &quot;${iface}&quot;  cat &quot;/sys/class/net/${iface}/ecn/roce_np/cnp_dscp&quot;done</code></pre><p>这里最容易出问题的是“只配服务器，不配交换机”或“服务器与交换机 DSCP/队列不一致”。RoCE 的 PFC 和 ECN 是端到端工程，服务器端正确不代表整网正确。</p><h2 id="%E5%85%AD%E3%80%81dcqcn-%E6%8B%A5%E5%A1%9E%E6%8E%A7%E5%88%B6" tabindex="-1">六、DCQCN 拥塞控制</h2><p>DCQCN 是 RoCEv2 生产网络里常用的拥塞控制机制。项目资料中启用了 DCQCN 兼容模式和 legacy DCQCN 软件模式：</p><pre><code class="language-bash">mst startfor dev in &lt;mst-device-list&gt;; do  mlxconfig -d &quot;${dev}&quot; -y set ROCE_CC_DCQCN_COMPATIBILITY_MODE=1  mlxconfig -d &quot;${dev}&quot; -y set ROCE_CC_LEGACY_DCQCN_SW=1donereboot</code></pre><p>检查：</p><pre><code class="language-bash">for dev in &lt;mst-device-list&gt;; do  mlxconfig -d &quot;${dev}&quot; query | grep ROCE_CCdone</code></pre><p>DCQCN 的价值通常在规模放大后才明显。双机测试可能看不出差异，但到 128 机、256 机甚至整网验收时，拥塞控制不一致会造成带宽波动、尾延迟升高和慢节点。</p><h2 id="%E4%B8%83%E3%80%81pci-acs-%E4%B8%8E-gpu-direct-rdma" tabindex="-1">七、PCI ACS 与 GPU Direct RDMA</h2><p>PCI ACS 可能把 PCIe 点对点访问重定向到 Root Complex，影响 GPU 与 NIC 之间的直接通信。训练集群中如果需要 GPU Direct RDMA，就要确认 ACS 不阻断关键路径。</p><pre><code class="language-bash">lspci -d &quot;*:*:*&quot; | awk &#39;{print $1}&#39; | while read bdf; do  if setpci -s &quot;${bdf}&quot; ECAP_ACS+0x06.w &gt;/dev/null 2&gt;&amp;1; then    setpci -s &quot;${bdf}&quot; ECAP_ACS+0x06.w=0000  fidone</code></pre><p>生产上要注意两点：</p><ol><li>ACS 调整要纳入变更审批，并结合服务器型号、BIOS 和安全策略确认。</li><li>调整后必须复跑单机和双机 NCCL，不能只看配置值。</li></ol><h2 id="%E5%85%AB%E3%80%81nccl-%E4%BE%A7%E9%85%8D%E5%90%88%E9%A1%B9" tabindex="-1">八、NCCL 侧配合项</h2><p>RoCE 网络调好后，NCCL 侧也要显式绑定正确路径：</p><pre><code class="language-bash">mpirun -np &lt;total-ranks&gt; \  -H &lt;node-a&gt;:8,&lt;node-b&gt;:8,... \  --allow-run-as-root \  --bind-to none \  --map-by slot \  --mca btl_tcp_if_include &lt;business-bond&gt; \  --mca oob_tcp_if_include &lt;business-bond&gt; \  -x NCCL_SOCKET_IFNAME=&lt;business-bond&gt; \  -x UCX_NET_DEVICES=&lt;business-bond&gt; \  -x NCCL_IB_DISABLE=0 \  -x NCCL_IB_GID_INDEX=&lt;gid-index&gt; \  -x NCCL_IB_HCA=&lt;train-hca-list&gt; \  -x NCCL_IB_TC=&lt;traffic-class&gt; \  -x NCCL_NET_GDR_LEVEL=&lt;gdr-level&gt; \  ./all_reduce_perf -g 1 -b 120M -e 34G -f 2 -n 100</code></pre><h2 id="%E4%B9%9D%E3%80%81%E7%94%9F%E4%BA%A7%E8%B0%83%E4%BC%98%E6%A3%80%E6%9F%A5%E8%A1%A8" tabindex="-1">九、生产调优检查表</h2><table><thead><tr><th>检查项</th><th>命令示例</th><th>期望</th></tr></thead><tbody><tr><td>RDMA 设备</td><td><code>ibv_devinfo</code></td><td>训练网卡全部可见</td></tr><tr><td>GID</td><td><code>show_gids</code></td><td>GID index 与 NCCL 参数一致</td></tr><tr><td>GPU Direct</td><td>`lsmod</td><td>grep nvidia_peermem`</td></tr><tr><td>RoCE 模式</td><td><code>mlxconfig query</code></td><td>Link Type 与 RoCE Control 正确</td></tr><tr><td>PFC</td><td><code>mlnx_qos -i &lt;iface&gt;</code></td><td>训练队列 PFC 与交换机一致</td></tr><tr><td>CNP DSCP</td><td><code>cat /sys/class/net/&lt;iface&gt;/ecn/roce_np/cnp_dscp</code></td><td>与网络设计一致</td></tr><tr><td>DCQCN</td><td>`mlxconfig query</td><td>grep ROCE_CC`</td></tr><tr><td>NCCL</td><td><code>all_reduce_perf</code></td><td>单机、双机、百台级曲线稳定</td></tr></tbody></table><h2 id="%E5%8D%81%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">十、总结</h2><p>H20 RoCEv2 调优的核心不是把命令背下来，而是形成闭环：先建立硬件和软件基线，再统一网卡命名和路由，然后配置 RoCE、PFC、ECN、DSCP、DCQCN，最后通过 NCCL 分层验收验证效果。</p><p>在 3000卡智算集群里，任何“只有一个节点不一致”的问题，都会在大规模通信中变成慢节点、掉速或超时。生产最佳实践是把所有调优项做成幂等自动化，并且每个配置项都要有验证命令和验收结果。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[第一章-H20智算集群架构与生产验收方法论]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/di-yi-zhang--h20-zhi-suan-ji-qun-jia-gou-yu-sheng-chan-yan-shou-fang-fa-lun" />
                <id>tag:https://blog.yongjie.top,2025-12-28:di-yi-zhang--h20-zhi-suan-ji-qun-jia-gou-yu-sheng-chan-yan-shou-fang-fa-lun</id>
                <published>2025-12-28T19:36:51+08:00</published>
                <updated>2026-06-29T17:47:57+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="390-%E5%8F%B0-h20-%E6%99%BA%E7%AE%97%E9%9B%86%E7%BE%A4%E6%9E%B6%E6%9E%84%E4%B8%8E%E7%94%9F%E4%BA%A7%E9%AA%8C%E6%94%B6%E6%96%B9%E6%B3%95%E8%AE%BA" tabindex="-1">390 台 H20 智算集群架构与生产验收方法论</h1><p>这篇文章整理的是一个 390 台H20 智算集群的公开版落地经验。项目形态是典型的大规模训练集群：单台服务器 8 张 H20 GPU，训练侧使用 8 张 400G ConnectX-7 网卡承载 RoCEv2 算力网络，业务侧使用 200G x2 bonding 形成 400G 聚合业务网络。</p><p><img src="/upload/2026/06/h20-cluster-architecture-optimized.svg" alt="h20-cluster-architecture-optimized" /></p><h2 id="%E4%B8%80%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E5%A4%A7%E8%A7%84%E6%A8%A1-gpu-%E9%9B%86%E7%BE%A4%E4%B8%8D%E6%98%AF%E2%80%9C%E8%A3%85%E9%A9%B1%E5%8A%A8-%2B-%E8%B7%91%E6%B5%8B%E8%AF%95%E2%80%9D" tabindex="-1">一、为什么大规模 GPU 集群不是“装驱动 + 跑测试”</h2><p>单机 GPU 服务器交付时，大家容易关注 <code>nvidia-smi</code> 是否正常、CUDA 是否能跑、单机 NCCL 是否有带宽。但到 390 台级规模，集群是否可用不再取决于单点成功，而取决于每一层都能被批量复制、批量验证、批量回滚。</p><p>在这个规模下，任何一个细节都会被放大。比如某一批网卡 RoCE 模式没有固化，可能导致双机测试偶发失败；某几个节点 PCI ACS 没有关，可能导致 GPU Direct RDMA 带宽异常；某个 Leaf 下 PFC 或 ECN 配置不一致，可能在 128 机以上才暴露成 NCCL 尾延迟。</p><p>因此，生产验收要按“分层基线”推进：</p><table><thead><tr><th>层级</th><th>验收重点</th><th>典型检查</th></tr></thead><tbody><tr><td>硬件层</td><td>GPU、NIC、NVSwitch、BMC、固件版本</td><td>设备数量、PCIe 拓扑、错误计数</td></tr><tr><td>OS 层</td><td>内核、驱动、OFED、CUDA、NCCL</td><td>模块加载、版本一致性、系统参数</td></tr><tr><td>网络层</td><td>RoCEv2、MTU、PFC、ECN、DCQCN、DSCP</td><td>GID、链路、队列、CNP 标记</td></tr><tr><td>通信层</td><td>单机、双机、小规模、百台级 NCCL</td><td>AllReduce、AllToAll、busbw、错误计数</td></tr><tr><td>存储层</td><td>数据集读取、Checkpoint 写入、元数据压力</td><td>fio 吞吐、IOPS、延迟分布</td></tr><tr><td>运维层</td><td>自动化、监控、故障定界、复跑能力</td><td>幂等脚本、节点清单、排障闭环</td></tr></tbody></table><p>真正的交付目标不是“某一次跑通”，而是任何节点出现异常后，都能快速定界、隔离、恢复，并在恢复后复跑同一套验收基线。</p><h2 id="%E4%BA%8C%E3%80%81%E9%9B%86%E7%BE%A4%E7%BD%91%E7%BB%9C%E5%B9%B3%E9%9D%A2%E8%AE%BE%E8%AE%A1" tabindex="-1">二、集群网络平面设计</h2><p>这个项目的网络可以按职责拆成三类平面。</p><p>第一类是业务管理网。它承载 SSH、调度控制、镜像拉取、运维平台、日志回传、少量数据分发等流量。项目口径中业务侧采用 200G x2 bonding，形成 400G 聚合能力。这里要特别注意，bond 的聚合速率不等于单 TCP 流一定能吃满 400G，实际还取决于 bonding 模式、交换机聚合算法、五元组分布和上层业务并发模型。</p><p>第二类是 RoCEv2 算力网络。每台 8 卡 H20 服务器配 8 张 400G 训练网卡，常见目标是尽量让 GPU 和 NIC 建立稳定、可预测的亲和关系，使 NCCL 在跨机通信时能充分利用多 HCA、多路径和 ECMP。这里的关键不是“网卡速率足够高”，而是 RoCE 的无损队列、拥塞控制、DSCP、PFC、ECN、GID、MTU 等参数必须端到端一致。</p><p>第三类是存储数据路径。训练数据读取和 Checkpoint 写入可以走独立存储网络，也可以复用高速数据网络。无论使用 CephFS、对象存储、并行文件系统，还是缓存层，存储验收都不能只看单次顺序读写带宽，还要同时看延迟分布、客户端 CPU、网卡速率、服务端 OSD 或磁盘利用率。</p><h2 id="%E4%B8%89%E3%80%81%E5%8D%95%E6%9C%BA-8-%E5%8D%A1%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%9F%BA%E7%BA%BF" tabindex="-1">三、单机 8 卡服务器基线</h2><p>单机基线要先回答四个问题。</p><p>第一，硬件是否完整识别。GPU 数量、NIC 数量、NVSwitch 或 PCIe 设备数量必须和 BOM 一致。对 8 卡服务器来说，不能只看 <code>nvidia-smi</code> 是否显示 8 张卡，还要看 PCIe BDF、GPU UUID、网卡 PCI 地址、NUMA 亲和、NVLink/NVSwitch 状态。</p><p>第二，软件栈是否一致。大规模集群最怕“少数节点版本漂移”。驱动、CUDA、NCCL、OFED、固件、BIOS 参数都要形成版本矩阵。本文项目资料中的单机 NCCL 证据图可以看到 H20-3e 设备和 NCCL 测试输出，适合公开证明机器类型和测试方法。</p><p><img src="/upload/2026/06/h20-nccl-local-proof-sanitized.png" alt="h20-nccl-local-proof-sanitized" /></p><p>第三，GPU Direct RDMA 是否可用。H20 跨机训练的通信路径不能只停留在 TCP 或普通 RDMA，要确保 <code>nvidia_peermem</code>、<code>mlx5_ib</code>、<code>rdma_cm</code> 等模块加载正常，PCI ACS 不阻断点对点访问，NCCL 能正确选择 IB/RoCE 路径。</p><p>第四，单机 NCCL 是否稳定。单机 8 卡测试的价值不是替代整网测试，而是尽早排除 GPU、NVLink/NVSwitch、驱动、CUDA、NCCL 本地问题。单机不稳定时，直接上百台测试只会制造噪声。</p><h2 id="%E5%9B%9B%E3%80%81%E4%BB%8E%E5%8D%95%E6%9C%BA%E5%88%B0%E6%95%B4%E7%BD%91%E7%9A%84%E9%AA%8C%E6%94%B6%E9%98%B6%E6%A2%AF" tabindex="-1">四、从单机到整网的验收阶梯</h2><p>大规模 NCCL 验收建议分阶段推进。</p><table><thead><tr><th>阶段</th><th>目标</th><th>为什么不能跳过</th></tr></thead><tbody><tr><td>单机 8 卡</td><td>验证本机 GPU、驱动、NCCL、拓扑</td><td>排除本地硬件和软件栈问题</td></tr><tr><td>双机 16 卡</td><td>验证 RoCE、HCA、路由、GDR</td><td>最小化跨机问题定位范围</td></tr><tr><td>64 机</td><td>验证小规模 ECMP 和队列一致性</td><td>能较快暴露配置批次差异</td></tr><tr><td>128 机</td><td>验证百台级稳定性和尾延迟</td><td>接近实际训练通信压力</td></tr><tr><td>256 机</td><td>验证大规模路径与拥塞控制</td><td>检查网络局部热点和慢节点</td></tr><tr><td>350 机以上</td><td>验证整网可用性</td><td>给生产上线提供验收依据</td></tr></tbody></table><p>项目资料中的脱敏 NCCL 结果显示，64 机、128 机、256 机和 350 机验收批次均达到预期。这里需要强调一点：NCCL 的 <code>busbw</code> 不是单张网卡线速，也不是所有网卡线速简单相加，它是集合通信算法下的有效总线带宽指标。解读结果时，要结合消息大小、迭代次数、算法、协议、节点规模和网络拓扑。</p><h2 id="%E4%BA%94%E3%80%81%E7%94%9F%E4%BA%A7%E4%BA%A4%E4%BB%98%E7%9A%84%E5%85%B3%E9%94%AE%E5%8E%9F%E5%88%99" tabindex="-1">五、生产交付的关键原则</h2><p>第一，所有配置必须自动化。包括 RDMA 模块持久化、网卡命名、RoCE 模式、DSCP、PFC、ECN、DCQCN、ACS、NCCL 环境变量模板。手工配置可以用于排障，但不能作为集群的生产交付方式。</p><p>第二，所有自动化必须可验证。每个配置项都要有检查命令和期望输出。例如配置了 CNP DSCP，就要能批量读取对应 sysfs 值；配置了 DCQCN，就要能批量查询网卡固件参数；加载了 <code>nvidia_peermem</code>，就要能从模块列表和 NCCL 结果确认生效。</p><p>第三，验收结果必须可复跑。生产环境里经常会有节点替换、固件升级、网络维护、驱动升级。没有可复跑的验收模板，后续任何变更都只能靠经验判断，风险会越来越高。</p><p>第四，故障定界必须闭环。GPU 掉卡、NVSwitch 异常、Fabric Manager 启动失败、NCCL 慢节点、RoCE 丢包，都不能停留在“重启好了”。每一次恢复都要记录现象、证据、判断、动作和复测结果。</p><h2 id="%E5%85%AD%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">六、总结</h2><p>智算集群的核心难点不是单个组件，而是硬件、网络、GPU 通信、存储和运维之间的系统协同。一个可靠的交付方案，应该具备清晰的网络分层、统一的软件栈、可自动化的 RoCE 调优、分阶段的 NCCL 验收、可复跑的测试模板，以及面向生产的故障定界闭环。</p><p>如果只能给一个建议，那就是：不要把大规模 GPU 集群交付做成一次性工程。它应该是一套可以持续验证、持续修复、持续扩容的生产系统。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[智算集群-全闪Ceph集群200G聚合网口性能压测]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/zhi-suan-ji-qun---quan-shan-ceph-ji-qun-200g-ju-he-wang-kou-xing-neng-ya-ce" />
                <id>tag:https://blog.yongjie.top,2025-12-13:zhi-suan-ji-qun---quan-shan-ceph-ji-qun-200g-ju-he-wang-kou-xing-neng-ya-ce</id>
                <published>2025-12-13T15:30:02+08:00</published>
                <updated>2026-06-30T15:10:41+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><img src="/upload/2026/06/ceph.jpg" alt="ceph" /></p><h1 id="%E4%B8%80%E3%80%81%E6%B5%8B%E8%AF%95%E6%A6%82%E8%BF%B0" tabindex="-1">一、测试概述</h1><p>本次压测旨在验证基于Ceph存储集群在GPU服务器高负载场景下的性能表现，涵盖<strong>顺序读写、随机读写、高并发IOPS及延迟控制</strong>等多个维度。测试采用<code>fio</code>工具，模拟多种I/O模式，评估存储系统的吞吐量、IOPS与延迟表现。</p><h1 id="%E4%BA%8C%E3%80%81%E7%A1%AC%E4%BB%B6%E9%85%8D%E7%BD%AE%E6%A6%82%E8%BF%B0" tabindex="-1">二、硬件配置概述</h1><h2 id="1.-ceph%E5%AD%98%E5%82%A8%E8%8A%82%E7%82%B9%EF%BC%883%E5%8F%B0%EF%BC%89" tabindex="-1">1. Ceph存储节点（3台）</h2><ul><li><strong>CPU</strong>：SR660V2 5318Y × 2</li><li><strong>内存</strong>：256GB</li><li><strong>数据盘组合</strong>：<ul><li>4 × 3.84TB NVMe U.2（PCIe 4.0, QLC NAND）</li></ul></li><li><strong>网络</strong>：双口100G网卡</li></ul><h2 id="2.-gpu%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%9C%8D%E5%8A%A1%E5%99%A8" tabindex="-1">2. GPU客户端服务器</h2><ul><li><strong>CPU</strong>：Intel Xeon Platinum 8558，192核</li><li><strong>内存</strong>：2TB</li><li><strong>网络</strong>：400Gbps网卡</li><li><strong>CephFS挂载点</strong>：容量14TB</li></ul><pre><code class="language-bash"># CPUCPU(s):                   192  On-line CPU(s) list:    0-191Vendor ID:                GenuineIntel  Model name:             INTEL(R) XEON(R) PLATINUM 8558    CPU family:           6    Model:                207    Thread(s) per core:   2    Core(s) per socket:   48# 内存root@GPU-Server-leaf-1-3:~# free -h               total        used        free      shared  buff/cache   availableMem:           2.0Ti        16Gi       1.9Ti        23Gi        23Gi       1.9Ti# 网卡速率Advertised pause frame use: NoAdvertised auto-negotiation: YesAdvertised FEC modes: RSSpeed: 400000Mb/sDuplex: FullAuto-negotiation: onPort: FIBREPHYAD: 0Transceiver: internalSupports Wake-on: dWake-on: dLink detected: yes</code></pre><p><img src="/upload/2026/06/image-20251212194650366.png" alt="image-20251212194650366" /><br /><img src="/upload/2026/06/image-20251213001751366.png" alt="image-20251213001751366" /></p><h1 id="%E4%B8%89%E3%80%81%E5%90%9E%E5%90%90%E9%87%8F%E5%8E%8B%E6%B5%8B" tabindex="-1">三、吞吐量压测</h1><h2 id="4m%E9%A1%BA%E5%BA%8F%E5%86%99-%E5%90%9E%E5%90%90" tabindex="-1">4M顺序写-吞吐</h2><pre><code class="language-bash">fio -direct=1 -iodepth=128 -rw=write -ioengine=libaio -bs=4M -numjobs=12 -size=50G -time_based=1 -runtime=600 -group_reporting -filename=/mnt/cephfs/write_test -name=Write_Test</code></pre><h3 id="cpu%2F%E5%86%85%E5%AD%98%E4%BD%BF%E7%94%A8%E7%8E%87" tabindex="-1">CPU/内存使用率</h3><p><img src="/upload/2026/06/image-20251212232000609.png" alt="image-20251212232000609" /></p><h3 id="%E7%BD%91%E5%8D%A1%E9%80%9F%E7%8E%87" tabindex="-1">网卡速率</h3><p><img src="/upload/2026/06/image-20251212180507590.png" alt="image-20251212180507590" /></p><h3 id="%E7%A3%81%E7%9B%98%E4%BD%BF%E7%94%A8%E7%8E%87" tabindex="-1">磁盘使用率</h3><p><img src="/upload/2026/06/image-20251212180524989.png" alt="image-20251212180524989" /></p><h3 id="%E5%90%9E%E5%90%90%E9%87%8F-11.3gib%2Fs-%E5%BB%B6%E8%BF%9F-532ms%2Fs" tabindex="-1">吞吐量 11.3GiB/s  延迟 532ms/s</h3><p><img src="/upload/2026/06/image-20251212194327637.png" alt="image-20251212194327637" /></p><h2 id="4m%E9%A1%BA%E5%BA%8F%E8%AF%BB-%E5%90%9E%E5%90%90" tabindex="-1">4M顺序读-吞吐</h2><pre><code class="language-bash">fio -direct=1 -iodepth=128 -rw=read -ioengine=libaio -bs=4M -numjobs=12 -size=50G -time_based=1 -runtime=600 -group_reporting -filename=/mnt/cephfs/Read_Test -name=Read_Test</code></pre><h3 id="%E5%90%9E%E5%90%90%E9%87%8F-15.6gib%2Fs-%E5%BB%B6%E8%BF%9F383ms%2Fs" tabindex="-1">吞吐量 15.6GiB/s 延迟383ms/s</h3><p><img src="/upload/2026/06/image-20251212230831912.png" alt="image-20251212230831912" /></p><h2 id="4m%E9%A1%BA%E5%BA%8F%E8%AF%BB%E5%86%99-%E5%90%9E%E5%90%90" tabindex="-1">4M顺序读写-吞吐</h2><pre><code class="language-bash">fio -direct=1 -iodepth=128 -rw=rw -ioengine=libaio -bs=4M -numjobs=12 -size=50G -time_based=1 -runtime=600 -group_reporting -filename=/mnt/cephfs/readwrite_test -name=Read_Write_Test</code></pre><h3 id="%E8%AF%BB%E5%86%99%E5%90%9E%E5%90%90%E9%87%8F-10.1gib%2Fs-%E5%BB%B6%E8%BF%9F290-302ms" tabindex="-1">读写吞吐量 10.1GiB/s 延迟290-302ms</h3><p><img src="/upload/2026/06/image-20251212232405030.png" alt="image-20251212232405030" /></p><h1 id="%E5%9B%9B%E3%80%81iops%E5%8E%8B%E6%B5%8B" tabindex="-1">四、IOPS压测</h1><h2 id="%3C5ms%E5%BB%B6%E8%BF%9F-4k-%E9%9A%8F%E6%9C%BA%E5%86%99-iops" tabindex="-1">&lt;5ms延迟 4K 随机写-IOPS</h2><pre><code class="language-bash"># numjobs=4fio -direct=1 -iodepth=128 -rw=randwrite -ioengine=libaio -bs=4k -numjobs=4 -size=50G -time_based=1 -runtime=300 -group_reporting -filename=/mnt/cephfs/Rand_Write_Test -name=Rand_Write_Test</code></pre><h3 id="iops-172k%2Fs-%E5%BB%B6%E8%BF%9F3ms%2Fs" tabindex="-1">IOPS 172K/s  延迟3ms/s</h3><ul><li><strong>平均延迟 (<code>lat avg</code>) 3ms</strong></li><li><strong>延迟分布 (<code>95.00th</code>) 95%请求&lt;5ms</strong></li><li><strong>平均IOPS 172K/s</strong></li></ul><p><img src="/upload/2026/06/image-20251213003015412.png" alt="image-20251213003015412" /></p><h2 id="%3C5ms%E5%BB%B6%E8%BF%9F-4k-%E9%9A%8F%E6%9C%BA%E8%AF%BB-iops" tabindex="-1">&lt;5ms延迟 4k 随机读-IOPS</h2><pre><code class="language-bash"># numjobs=6fio -direct=1 -iodepth=128 -rw=randread -ioengine=libaio -bs=4k -numjobs=6 -size=50G -time_based=1 -runtime=300 -group_reporting -filename=/mnt/cephfs/Rand_Read_Test -name=Rand_Read_Test</code></pre><h3 id="iops-326k%2Fs-%E5%BB%B6%E8%BF%9F2.4ms%2Fs" tabindex="-1">IOPS 326K/s   延迟2.4ms/s</h3><ul><li><strong>平均延迟 (<code>lat avg</code>) 2.4ms</strong></li><li><strong>延迟分布 (<code>99.90th</code>) 99.9%请求&lt;5.3ms</strong></li><li><strong>平均IOPS 326K/s</strong></li></ul><p><img src="/upload/2026/06/image-20251213004304127.png" alt="image-20251213004304127" /></p><h2 id="%3C5ms%E5%BB%B6%E8%BF%9F-4k-%E9%9A%8F%E6%9C%BA%E8%AF%BB%E5%86%99-iops" tabindex="-1">&lt;5ms延迟 4k 随机读写-IOPS</h2><pre><code class="language-bash"># numjobs=6fio -direct=1 -iodepth=128 -rw=randrw -ioengine=libaio -bs=4k -numjobs=6 -size=50G -time_based=1 -runtime=300 -group_reporting -filename=/mnt/cephfs/Rand_Read_Write_Test -name=Rand_Read_Write_Test</code></pre><h3 id="%E8%AF%BB%E5%86%99iops-122k%2Fs" tabindex="-1">读写IOPS 122K/s</h3><ul><li><strong>读平均延迟 (<code>lat avg</code>) 2.7ms</strong></li><li><strong>写平均延迟 (<code>lat avg</code>) 3.5ms</strong></li><li><strong>读延迟分布 (<code>99.00th</code>) 99%请求&lt;5ms</strong></li><li><strong>写延迟分布 (<code>95.00th</code>) 95%请求&lt;5.3ms</strong></li><li><strong>平均IOPS 122K/s</strong></li></ul><p><img src="/upload/2026/06/image-20251213004952449.png" alt="image-20251213004952449" /></p><h2 id="%3C10ms%E5%BB%B6%E8%BF%9F-4k%E9%9A%8F%E6%9C%BA%E8%AF%BBiops" tabindex="-1">&lt;10ms延迟 4k随机读IOPS</h2><pre><code class="language-bash"># numjobs需要逐步增加，找到&lt;10ms的极限值。numjobs=12fio -direct=1 -iodepth=128 -rw=randread -ioengine=libaio -bs=4k -numjobs=8 -size=50G -time_based=1 -runtime=300 -group_reporting -filename=/mnt/cephfs/Rand_Read_Test -name=Rand_Read_Test</code></pre><h3 id="iops-433k%2Fs-%E5%BB%B6%E8%BF%9F3.5ms%2Fs" tabindex="-1">IOPS 433K/s 延迟3.5ms/s</h3><ul><li><strong>平均延迟 (<code>lat avg</code>) 3.5ms</strong></li><li><strong>延迟分布 (<code>99.50th</code>) 99.5%请求&lt;10.2ms</strong></li><li><strong>平均IOPS 433K/s</strong></li></ul><p><img src="/upload/2026/06/image-20251213010410622.png" alt="image-20251213010410622" /></p><h2 id="%3C10ms%E5%BB%B6%E8%BF%9F-4k%E9%9A%8F%E6%9C%BA%E5%86%99iops" tabindex="-1">&lt;10ms延迟 4k随机写IOPS</h2><pre><code class="language-bash"># numjobs需要逐步增加，找到&lt;10ms的极限值。numjobs=8fio -direct=1 -iodepth=128 -rw=randwrite -ioengine=libaio -bs=4k -numjobs=8 -size=50G -time_based=1 -runtime=300 -group_reporting -filename=/mnt/cephfs/Rand_Write_Test -name=Rand_Write_Test</code></pre><h3 id="iops-198k%2Fs-%E5%BB%B6%E8%BF%9F5.2ms%2Fs" tabindex="-1">IOPS 198K/s 延迟5.2ms/s</h3><ul><li><strong>平均延迟 (<code>lat avg</code>) 5.2ms</strong></li><li><strong>延迟分布 (<code>95.00th</code>) 95%请求&lt;9.4ms</strong></li><li><strong>平均IOPS 198K/s</strong></li></ul><p><img src="/upload/2026/06/image-20251213011155320.png" alt="image-20251213011155320" /></p><h2 id="%3C10ms%E5%BB%B6%E8%BF%9F-4k%E9%9A%8F%E6%9C%BA%E8%AF%BB%E5%86%99iops" tabindex="-1">&lt;10ms延迟 4k随机读写IOPS</h2><pre><code class="language-bash"># numjobs需要逐步增加，找到&lt;10ms的极限值。numjobs=8fio -direct=1 -iodepth=128 -rw=randrw -ioengine=libaio -bs=4k -numjobs=8 -size=50G -time_based=1 -runtime=300 -group_reporting -filename=/mnt/cephfs/Rand_Read_Write_Test -name=Rand_Read_Write_Test</code></pre><h3 id="%E8%AF%BB%E5%86%99iops-137k%2Fs" tabindex="-1">读写IOPS 137K/s</h3><ul><li><strong>读平均延迟 (<code>lat avg</code>) 3.2ms</strong></li><li><strong>写平均延迟 (<code>lat avg</code>) 4.3ms</strong></li><li><strong>读延迟分布 (<code>99.90th</code>) 99.9%请求&lt;10.3ms</strong></li><li><strong>写延迟分布 (<code>99.50th</code>) 99.5%请求&lt;10.6ms</strong></li><li><strong>平均IOPS 137K/s</strong></li></ul><p><img src="/upload/2026/06/image-20251213012029287.png" alt="image-20251213012029287" /></p><h2 id="%E4%BA%94%E3%80%81%E7%B3%BB%E7%BB%9F%E7%A8%B3%E5%AE%9A%E6%80%A7%E8%AF%84%E4%BC%B0" tabindex="-1">五、系统稳定性评估</h2><ul><li>测试过程中未出现性能波动或异常中断，Ceph集群与客户端系统运行稳定。</li><li>各节点硬件资源（CPU、内存、网络、磁盘）利用率均衡，无单点瓶颈。</li></ul><h1 id="%E5%85%AD%E3%80%81%E7%BB%93%E8%AE%BA" tabindex="-1">六、结论</h1><p>本次压测表明，<strong>当前Ceph存储集群在搭配高性能GPU服务器时，能够充分发挥NVMe硬盘的性能潜力，在顺序读写、随机读写及高并发IOPS场景下均表现出色，且系统资源无瓶颈，满足高性能计算与AI训练等场景的存储需求。</strong></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[多机房监控-VictoriaMetrics/N9e-中心机房+边缘机房]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/duo-ji-fang-jian-kong" />
                <id>tag:https://blog.yongjie.top,2025-10-18:duo-ji-fang-jian-kong</id>
                <published>2025-10-18T18:47:36+08:00</published>
                <updated>2025-12-24T18:56:08+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="1.-%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84" tabindex="-1">1. 整体架构</h1><h2 id="1.1-%E4%B8%AD%E5%BF%83%E6%9C%BA%E6%88%BF%2B%E8%BE%B9%E7%BC%98%E6%9C%BA%E6%88%BF" tabindex="-1">1.1 中心机房+边缘机房</h2><p><img src="/upload/2025/12/image-20250813162614392.png" alt="image-20250813162614392" /></p><p><img src="/upload/2025/12/image-20251224181623213.png" alt="image-20251224181623213" /></p><h2 id="1.1-%E7%BB%84%E4%BB%B6%E9%AB%98%E5%8F%AF%E7%94%A8" tabindex="-1">1.1 组件高可用</h2><table><thead><tr><th style="text-align:left">组件</th><th style="text-align:left">中心机房</th><th style="text-align:left">边缘机房</th></tr></thead><tbody><tr><td style="text-align:left"><strong>夜莺服务</strong></td><td style="text-align:left">3节点K8S集群+负载均衡</td><td style="text-align:left">edge+vmagent * 2</td></tr><tr><td style="text-align:left"><strong>时序数据库</strong></td><td style="text-align:left">VictoriaMetrics集群</td><td style="text-align:left">VictoriaMetrics集群 * 3</td></tr><tr><td style="text-align:left"><strong>缓存</strong></td><td style="text-align:left">云Redis</td><td style="text-align:left">Redis集群 * 3</td></tr><tr><td style="text-align:left"><strong>关系数据库</strong></td><td style="text-align:left">云MySQL</td><td style="text-align:left">不部署</td></tr><tr><td style="text-align:left"><strong>采集器</strong></td><td style="text-align:left">k8s metrics-server</td><td style="text-align:left">Categraf+Telegraf+Node_exporter</td></tr></tbody></table><h2 id="1.2-%E8%BE%B9%E7%BC%98%E8%87%AA%E6%B2%BB%E6%9C%BA%E5%88%B6" tabindex="-1">1.2 边缘自治机制</h2><ul><li>中心机房故障后，由边缘机房的n9e-edge发出告警消息</li></ul><p><img src="/upload/2025/12/deepseek_mermaid_20250813_b99816.png" alt="deepseek_mermaid_20250813_b99816" /></p><h2 id="1.3-%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E8%AE%A1%E5%88%92" tabindex="-1">1.3 数据存储计划</h2><table><thead><tr><th style="text-align:left">数据类型</th><th style="text-align:left">中心机房</th><th style="text-align:left">边缘机房</th></tr></thead><tbody><tr><td style="text-align:left"><strong>监控指标</strong></td><td style="text-align:left">长期存储(180天)</td><td style="text-align:left">长期存储(180天)</td></tr><tr><td style="text-align:left"><strong>告警事件</strong></td><td style="text-align:left">完整存储</td><td style="text-align:left">仅未同步事件</td></tr><tr><td style="text-align:left"><strong>告警规则</strong></td><td style="text-align:left">主存储</td><td style="text-align:left">副本</td></tr><tr><td style="text-align:left"><strong>用户数据</strong></td><td style="text-align:left">主存储</td><td style="text-align:left">缓存</td></tr></tbody></table><h1 id="2.-%E5%8A%9F%E8%83%BD%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E" tabindex="-1">2. 功能配置说明</h1><h2 id="2.1-%E9%85%8D%E7%BD%AE%E6%95%B0%E6%8D%AE%E6%BA%90" tabindex="-1">2.1 配置数据源</h2><pre><code class="language-bash">http://192.168.137.20:8481/select/0/prometheus/http://192.168.137.20:8480/insert/0/prometheus/api/v1/write# 时序库选择victoriametrics</code></pre><p><img src="/upload/2025/12/image-20250808171220158.png" alt="image-20250808171220158" /></p><p><img src="/upload/2025/12/image-20250808171340753.png" alt="image-20250808171340753" /></p><h2 id="2.2-%E5%88%9B%E5%BB%BA%E5%9B%A2%E9%98%9F%2F%E7%94%A8%E6%88%B7%EF%BC%8C%E5%9B%A2%E9%98%9F%E5%8F%AF%E4%BB%A5%E6%A0%B9%E6%8D%AE%E4%B8%8D%E5%90%8C%E9%83%A8%E9%97%A8%E6%88%96%E4%B8%9A%E5%8A%A1%E7%BB%84%E5%8C%BA%E5%88%86%EF%BC%8C%E6%96%B9%E4%BE%BF%E5%90%8E%E7%BB%AD%E5%8F%91%E9%80%81%E5%91%8A%E8%AD%A6" tabindex="-1">2.2 创建团队/用户，团队可以根据不同部门或业务组区分，方便后续发送告警</h2><p><img src="/upload/2025/12/image-20250808171459010.png" alt="image-20250808171459010" /></p><h2 id="2.3-%E5%88%9B%E5%BB%BA%E7%94%A8%E6%88%B7%E5%B9%B6%E5%A1%AB%E5%86%99%E6%89%8B%E6%9C%BA%E5%8F%B7%EF%BC%8C%E5%90%8E%E7%BB%AD%E5%91%8A%E8%AD%A6%E4%BC%9A%E9%80%9A%E8%BF%87%E6%89%8B%E6%9C%BA%E5%8F%B7at%E5%AF%B9%E5%BA%94%E7%9A%84%E4%BA%BA%E5%91%98%EF%BC%8C%E5%B0%86%E7%94%A8%E6%88%B7%E5%88%86%E5%88%AB%E5%8A%A0%E5%85%A5%E5%AF%B9%E5%BA%94%E7%9A%84%E5%9B%A2%E9%98%9F%E7%BB%84%E9%87%8C" tabindex="-1">2.3 创建用户并填写手机号，后续告警会通过手机号AT对应的人员，将用户分别加入对应的团队组里</h2><p><img src="/upload/2025/12/image-20250808171606000.png" alt="image-20250808171606000" /></p><h2 id="2.4-%E7%BA%A2%E6%A9%99%E7%BB%BF%E5%AD%97%E6%AE%B5%E6%A8%A1%E6%9D%BF" tabindex="-1">2.4 红橙绿字段模板</h2><pre><code class="language-bash">{{if $event.IsRecovered}}{{else}}{{end}}{{$event.RuleName}}  - - -  {{if $event.IsRecovered}}&lt;font color=&#39;#008800&#39;&gt;**告警名称**:&lt;/font&gt; {{$event.AnnotationsJSON.summary}}  &lt;font color=&#39;#008800&#39;&gt;**告警区域**:&lt;/font&gt; {{if $event.TagsMap.business}}{{$event.TagsMap.business}}{{else}}N/A{{end}}  &lt;font color=&#39;#008800&#39;&gt;**告警主机**:&lt;/font&gt; {{if $event.TagsMap.instance}}{{$event.TagsMap.instance}}{{else}}N/A{{end}}  &lt;font color=&#39;#008800&#39;&gt;**告警项目**:&lt;/font&gt; {{if $event.TagsMap.project}}{{$event.TagsMap.project}}{{else}}N/A{{end}}  &lt;font color=&#39;#008800&#39;&gt;**告警服务**:&lt;/font&gt; {{if $event.TagsMap.service}}{{$event.TagsMap.service}}{{else}}N/A{{end}}  &lt;font color=&#39;#008800&#39;&gt;**告警级别**:&lt;/font&gt; {{if eq $event.Severity 1}}critical{{else if eq $event.Severity 2}}warning{{else}}info{{end}}  &lt;font color=&#39;#008800&#39;&gt;**告警信息**:&lt;/font&gt; {{if $event.AnnotationsJSON.description}}{{$event.AnnotationsJSON.description}}{{else}}{{$event.AnnotationsJSON.summary}}{{end}}  &lt;font color=&#39;#008800&#39;&gt;**告警时间**:&lt;/font&gt; {{timeformat $event.TriggerTime &quot;2006.01.02 15:04:05&quot;}}  &lt;font color=&#39;#008800&#39;&gt;**恢复时间**:&lt;/font&gt; {{timeformat $event.LastEvalTime &quot;2006.01.02 15:04:05&quot;}}  {{else}}{{if eq $event.Severity 1}} &lt;!-- Critical --&gt;&lt;font color=&#39;#FF0000&#39;&gt;**告警名称**:&lt;/font&gt; {{$event.AnnotationsJSON.summary}}  &lt;font color=&#39;#FF0000&#39;&gt;**告警区域**:&lt;/font&gt; {{if $event.TagsMap.business}}{{$event.TagsMap.business}}{{else}}N/A{{end}}  &lt;font color=&#39;#FF0000&#39;&gt;**告警主机**:&lt;/font&gt; {{if $event.TagsMap.instance}}{{$event.TagsMap.instance}}{{else}}N/A{{end}}  &lt;font color=&#39;#FF0000&#39;&gt;**告警项目**:&lt;/font&gt; {{if $event.TagsMap.project}}{{$event.TagsMap.project}}{{else}}N/A{{end}}  &lt;font color=&#39;#FF0000&#39;&gt;**告警服务**:&lt;/font&gt; {{if $event.TagsMap.service}}{{$event.TagsMap.service}}{{else}}N/A{{end}}  &lt;font color=&#39;#FF0000&#39;&gt;**告警级别**:&lt;/font&gt; critical  &lt;font color=&#39;#FF0000&#39;&gt;**告警信息**:&lt;/font&gt; {{if $event.AnnotationsJSON.description}}{{$event.AnnotationsJSON.description}}{{else}}{{$event.AnnotationsJSON.summary}}{{end}}  &lt;font color=&#39;#FF0000&#39;&gt;**告警时间**:&lt;/font&gt; {{timeformat $event.TriggerTime &quot;2006.01.02 15:04:05&quot;}}  {{else if eq $event.Severity 2}} &lt;!-- Warning --&gt;&lt;font color=&#39;#FFA500&#39;&gt;**告警名称**:&lt;/font&gt; {{$event.AnnotationsJSON.summary}}  &lt;font color=&#39;#FFA500&#39;&gt;**告警区域**:&lt;/font&gt; {{if $event.TagsMap.business}}{{$event.TagsMap.business}}{{else}}N/A{{end}}  &lt;font color=&#39;#FFA500&#39;&gt;**告警主机**:&lt;/font&gt; {{if $event.TagsMap.instance}}{{$event.TagsMap.instance}}{{else}}N/A{{end}}  &lt;font color=&#39;#FFA500&#39;&gt;**告警项目**:&lt;/font&gt; {{if $event.TagsMap.project}}{{$event.TagsMap.project}}{{else}}N/A{{end}}  &lt;font color=&#39;#FFA500&#39;&gt;**告警服务**:&lt;/font&gt; {{if $event.TagsMap.service}}{{$event.TagsMap.service}}{{else}}N/A{{end}}  &lt;font color=&#39;#FFA500&#39;&gt;**告警级别**:&lt;/font&gt; warning  &lt;font color=&#39;#FFA500&#39;&gt;**告警信息**:&lt;/font&gt; {{if $event.AnnotationsJSON.description}}{{$event.AnnotationsJSON.description}}{{else}}{{$event.AnnotationsJSON.summary}}{{end}}  &lt;font color=&#39;#FFA500&#39;&gt;**告警时间**:&lt;/font&gt; {{timeformat $event.TriggerTime &quot;2006.01.02 15:04:05&quot;}}  {{else}} &lt;!-- Info --&gt;&lt;font color=&#39;#0000FF&#39;&gt;**告警名称**:&lt;/font&gt; {{$event.AnnotationsJSON.summary}}  &lt;font color=&#39;#0000FF&#39;&gt;**告警区域**:&lt;/font&gt; {{if $event.TagsMap.business}}{{$event.TagsMap.business}}{{else}}N/A{{end}}  &lt;font color=&#39;#0000FF&#39;&gt;**告警主机**:&lt;/font&gt; {{if $event.TagsMap.instance}}{{$event.TagsMap.instance}}{{else}}N/A{{end}}  &lt;font color=&#39;#0000FF&#39;&gt;**告警项目**:&lt;/font&gt; {{if $event.TagsMap.project}}{{$event.TagsMap.project}}{{else}}N/A{{end}}  &lt;font color=&#39;#0000FF&#39;&gt;**告警服务**:&lt;/font&gt; {{if $event.TagsMap.service}}{{$event.TagsMap.service}}{{else}}N/A{{end}}  &lt;font color=&#39;#0000FF&#39;&gt;**告警级别**:&lt;/font&gt; info  &lt;font color=&#39;#0000FF&#39;&gt;**告警信息**:&lt;/font&gt; {{if $event.AnnotationsJSON.description}}{{$event.AnnotationsJSON.description}}{{else}}{{$event.AnnotationsJSON.summary}}{{end}}  &lt;font color=&#39;#0000FF&#39;&gt;**告警时间**:&lt;/font&gt; {{timeformat $event.TriggerTime &quot;2006.01.02 15:04:05&quot;}}  {{end}}{{end}}</code></pre><h3 id="2.4.1-%E5%91%8A%E8%AD%A6%E4%B8%8E%E6%81%A2%E5%A4%8D%E6%95%88%E6%9E%9C" tabindex="-1">2.4.1 告警与恢复效果</h3><p><img src="/upload/2025/12/image-20250808201211278.png" alt="image-20250808201211278" /></p><p><img src="/upload/2025/12/image-20250808201316888.png" alt="image-20250808201316888" /></p><h2 id="2.5-%E9%83%A8%E7%BD%B2categraf%E9%87%87%E9%9B%86%E5%99%A8%EF%BC%8C%E4%BD%BF%E7%94%A8%E8%87%AA%E6%84%88%E5%8A%9F%E8%83%BD%E9%9C%80%E9%83%A8%E7%BD%B2%E6%AD%A4%E9%87%87%E9%9B%86%E5%99%A8" tabindex="-1">2.5 部署categraf采集器，使用自愈功能需部署此采集器</h2><pre><code class="language-bash"># 下载安装包，里面集成了所有插件https://github.com/flashcatcloud/categraf/releasestar zxf categraf-v0.4.14-linux-amd64.tar.gz -C /data/mv /data/categraf-v0.4.14-linux-amd64 /data/categraf# 创建主配置文件cd /data/categrafvim conf/config.toml[global]# 启动的时候是否在stdout中打印配置内容print_configs = false# 机器名，作为本机的唯一标识，会为时序数据自动附加一个 agent_hostname=$hostname 的标签# hostname 配置如果为空，自动取本机的机器名# hostname 配置如果不为空，就使用用户配置的内容作为hostname# 用户配置的hostname字符串中，可以包含变量，目前支持两个变量，# $hostname 和 $ip，如果字符串中出现这两个变量，就会自动替换# $hostname 自动替换为本机机器名，$ip 自动替换为本机IP# 建议大家使用 --test 做一下测试，看看输出的内容是否符合预期# 这里配置的内容，在--test模式下，会显示为 agent_hostname=xxx 的标签hostname = &quot;&quot;# 是否忽略主机名的标签，如果设置为true，时序数据中就不会自动附加agent_hostname=$hostname 的标签omit_hostname = false# 时序数据的时间戳使用ms还是s，默认是ms，是因为remote write协议使用ms作为时间戳的单位precision = &quot;ms&quot;# 全局采集频率，15秒采集一次interval = 15# 全局附加标签，一行一个，这些写的标签会自动附到时序数据上[global.labels]service=&quot;pvenode&quot;business=&quot;SZ&quot;project=&quot;yw&quot;[log]# 默认的log输出，到标准输出(stdout) # 如果指定为文件, 则写入到指定的文件中file_name = &quot;stdout&quot;# options below will not be work when file_name is stdout or stderr# 如果是写入文件，最大写入大小，单位是MBmax_size = 100# max_age is the maximum number of days to retain old log files based on the timestamp encoded in their filename.# 保留多少天的日志文件max_age = 30# max_backups is the maximum number of old log files to retain.# 保留多少个日志文件max_backups = 7# local_time determines if the time used for formatting the timestamps in backup files is the computer&#39;s local time.# 是否使用本地时间local_time = true# Compress determines if the rotated log files should be compressed using gzip.# 是否将老文件压缩（gzip格式)compress = true# 发给后端的时序数据，会先被扔到 categraf 内存队列里，每个采集插件一个队列# chan_size 定义了队列最大长度# batch 是每次从队列中取多少条，发送给后端backend[writer_opt]# default: 2000batch = 2000# channel(as queue) sizechan_size = 10000# 后端backend配置，在toml中 [[]] 表示数组，所以可以配置多个writer# 每个writer可以有不同的url，不同的basic auth信息[[writers]]# 注意端口号# v5版本端口是19000# v6+版本端口是17000# 写入vmagent，由vmagent聚合重写标签再push到vminseturl = &quot;http://127.0.0.1:8429/api/v1/write&quot;# Basic auth usernamebasic_auth_user = &quot;&quot;# Basic auth passwordbasic_auth_pass = &quot;&quot;# timeout settings, unit: mstimeout = 10000dial_timeout = 3000max_idle_conns_per_host = 100# 是否开启push gateway[http]enable = falseaddress = &quot;:9100&quot;print_access = falserun_mode = &quot;release&quot;# 是否启用告警自愈agent[ibex]enable = true## ibex flush intervalinterval = &quot;2000ms&quot;## n9e ibex server rpc addressservers = [&quot;192.168.137.20:20090&quot;]## temp script dirmeta_dir = &quot;./meta&quot;# 心跳上报（附带资源信息,对象列表中使用），适用于夜莺v6+# 如果是v5版本，这里不需要保留[heartbeat]enable = true# 如果心跳携带参数 gid=&lt;group_id&gt; 可以实现自动归属于某个业务组效果# report os version cpu.util mem.util metadataurl = &quot;http://192.168.137.20:17000/v1/n9e/heartbeat&quot;# interval, unit: sinterval = 10# Basic auth usernamebasic_auth_user = &quot;&quot;# Basic auth passwordbasic_auth_pass = &quot;&quot;## Optional headers# headers = [&quot;X-From&quot;, &quot;categraf&quot;, &quot;X-Xyz&quot;, &quot;abc&quot;]# timeout settings, unit: mstimeout = 5000dial_timeout = 3000max_idle_conns_per_host = 100# embeded prometheus agent mode[prometheus]# 是否启用 prometheus agent mode 功能enable = false# 可直接使用 prometheus 的 scrape yaml 文件，来描述抓取任务scrape_config_file = &quot;/path/to/in_cluster_scrape.yaml&quot;## log level, debug warn info errorlog_level = &quot;info&quot;## wal file storage path ,default ./data-agent# wal_storage_path = &quot;/path/to/storage&quot;## wal reserve time duration, default value is 2 hour# wal_min_duration = 2 # 安装，自动添加system_service./categraf  --install# 启动systemctl start categraf.servicesystemctl status categraf.service# 看日志是否有写入数据报错journalctl -f -u categraf.service# 如果需要使用哪个插件，就在插件的配置文件添加target和采集规则</code></pre><h2 id="2.6-%E5%91%8A%E8%AD%A6%E8%87%AA%E6%84%88%E5%8A%9F%E8%83%BD%E9%85%8D%E7%BD%AE" tabindex="-1">2.6 告警自愈功能配置</h2><h3 id="2.6.1-%E5%B7%B2%E9%83%A8%E7%BD%B2categraf%E7%9A%84%E6%9C%BA%E5%99%A8%E6%89%8D%E4%BC%9A%E8%87%AA%E5%8A%A8%E6%B3%A8%E5%86%8C%E5%88%B0%E5%85%A8%E9%83%A8%E6%9C%BA%E5%99%A8%E7%9A%84%E5%88%97%E8%A1%A8%EF%BC%8C%E7%84%B6%E5%90%8E%E6%8A%8A%E6%9C%BA%E5%99%A8%E5%8A%A0%E5%85%A5%E5%91%8A%E8%AD%A6%E8%A7%84%E5%88%99%E7%9A%84%E4%B8%9A%E5%8A%A1%E7%BB%84" tabindex="-1">2.6.1 已部署categraf的机器才会自动注册到全部机器的列表，然后把机器加入告警规则的业务组</h3><ul><li>注意，一定要把机器加入对应业务组，否则告警自愈模板不会触发</li></ul><p><img src="/upload/2025/12/image-20250811194025626.png" alt="image-20250811194025626" /></p><p><img src="/upload/2025/12/image-20250811194049317.png" alt="image-20250811194049317" /></p><h3 id="2.6.2-%E5%9C%A8%E5%AF%B9%E5%BA%94%E4%B8%9A%E5%8A%A1%E7%BB%84%E5%88%9B%E5%BB%BA%E5%91%8A%E8%AD%A6%E8%87%AA%E6%84%88%E7%9A%84%E6%A8%A1%E6%9D%BF" tabindex="-1">2.6.2 在对应业务组创建告警自愈的模板</h3><ul><li>host一定要填机器的名字，IP是不通的</li></ul><p><img src="/upload/2025/12/image-20250811194159371.png" alt="image-20250811194159371" /></p><h3 id="2.6.3-%E6%89%B9%E9%87%8F%E8%AE%BE%E7%BD%AE%E5%91%8A%E8%AD%A6%E8%A7%84%E5%88%99%E6%88%96%E8%AE%BE%E7%BD%AE%E5%8D%95%E4%B8%AA%E8%A7%84%E5%88%99%EF%BC%8C%E9%80%89%E6%8B%A9%E5%AF%B9%E5%BA%94%E7%9A%84%E8%87%AA%E6%84%88%E6%A8%A1%E6%9D%BF" tabindex="-1">2.6.3 批量设置告警规则或设置单个规则，选择对应的自愈模板</h3><p><img src="/upload/2025/12/image-20250811194257035.png" alt="image-20250811194257035" /></p><h3 id="2.6.4-%E7%84%B6%E5%90%8E%E8%A7%A6%E5%8F%91%E5%91%8A%E8%AD%A6%E6%B5%8B%E8%AF%95%EF%BC%8C%E8%A7%82%E5%AF%9F%E5%91%8A%E8%AD%A6%E8%87%AA%E6%84%88%E7%9A%84%E5%8E%86%E5%8F%B2%E4%BB%BB%E5%8A%A1%E6%98%AF%E5%90%A6%E6%9C%89%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C" tabindex="-1">2.6.4 然后触发告警测试，观察告警自愈的历史任务是否有执行结果</h3><p><img src="/upload/2025/12/image-20250811194352922.png" alt="image-20250811194352922" /></p><h3 id="2.6.5-%E9%80%89%E6%8B%A9%E5%8E%86%E5%8F%B2%E4%BB%BB%E5%8A%A1%E7%9A%84stdouts%E6%97%A5%E5%BF%97%E8%BE%93%E5%87%BA%EF%BC%8C%E6%9F%A5%E7%9C%8B%E6%B5%81%E7%A8%8B%E6%98%AF%E5%90%A6%E6%89%A7%E8%A1%8C%E6%88%90%E5%8A%9F%EF%BC%8C%E5%B9%B6%E4%B8%94%E7%8A%B6%E6%80%81%E6%98%AF%E5%90%A6success" tabindex="-1">2.6.5 选择历史任务的stdouts日志输出，查看流程是否执行成功，并且状态是否success</h3><p><img src="/upload/2025/12/image-20250811194452015.png" alt="image-20250811194452015" /></p><p><img src="/upload/2025/12/image-20250811194504713.png" alt="image-20250811194504713" /></p><h1 id="3.-%E9%83%A8%E7%BD%B2%E8%BE%B9%E7%BC%98%E6%9C%BA%E6%88%BFedge" tabindex="-1">3. 部署边缘机房edge</h1><ul><li>依赖的组件：victoriametrics集群/Redis集群</li><li>edge的pushgw.Writers的配置是本地边缘机房的victoria，存储的是告警元数据，并不是监控数据，但是与监控数据的victoria共用了同一套集群</li></ul><pre><code class="language-bash"># 必须要依赖redis，每个边缘机房都要独立的。这里方便模拟，只部署了单机cat docker-compose.yml version: &#39;3.7&#39;networks:  n9e-edge:    driver: bridgeservices:  redis:    image: &quot;redis:6.2&quot;    container_name: redis    hostname: redis    restart: always    environment:      TZ: Asia/Shanghai    networks:      - n9e-edge    ports:      - &quot;6379:6379&quot;            docker-compose up -d# 部署单机victoriametrics，方便模拟wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.122.0/victoria-metrics-linux-amd64-v1.122.0.tar.gztar zxf victoria-metrics-linux-amd64-v1.122.0.tar.gz -C /usr/bincat &gt; /etc/systemd/system/victoria-metrics.service &lt;&lt;&#39;EOF&#39;[Unit]Description=Linux VictoriaMetrics ServerDocumentation=https://docs.victoriametrics.com/After=network.target[Service]ExecStart=/usr/bin/victoria-metrics-prod -httpListenAddr=0.0.0.0:8428 -storageDataPath=/data/victoria-metrics -retentionPeriod=3[Install]WantedBy=multi-user.targetEOFsystemctl daemon-reloadsystemctl enable --now victoria-metrics.servicesystemctl status victoria-metrics.service# 部署edge组件，在官方n9e压缩包已经包括了wget https://github.com/ccfos/nightingale/releases/download/v8.2.2/n9e-v8.2.2-linux-amd64.tar.gzmkdir /data/n9e-edge/tar zxf n9e-v8.2.2-linux-amd64.tar.gz -C /data/n9e-edge/cd /data/n9e-edge# 修改basicauthpass的token鉴权，与n9e配置文件内一致，开启apiforservice，修改日志级别，开启ibex，修改redis地址# 同一个边缘机房多个edge的enginename一定要一致vim etc/edge/edge.toml[Global]RunMode = &quot;release&quot;[CenterApi]# 改成n9e中心机房的ip地址。还需要修改basicauthpass的token鉴权，与n9e配置文件内一致Addrs = [&quot;http://192.168.137.20:17000&quot;]BasicAuthUser = &quot;user001&quot;BasicAuthPass = &quot;ccc26da7b9aba533cbb263sdklf902384&quot;# unit: msTimeout = 9000[Log]# log write dirDir = &quot;logs&quot;# log level: DEBUG INFO WARNING ERROR# 生产环境改为WARNING模式Level = &quot;DEBUG&quot;# stdout, stderr, fileOutput = &quot;stdout&quot;# # rotate by time# KeepHours = 4# # rotate by size# RotateNum = 3# # unit: MB# RotateSize = 256[HTTP]# http listening addressHost = &quot;0.0.0.0&quot;# http listening portPort = 19000# https cert file pathCertFile = &quot;&quot;# https key file pathKeyFile = &quot;&quot;# whether print access logPrintAccessLog = false# whether enable pprofPProf = false# expose prometheus /metrics?ExposeMetrics = true# http graceful shutdown timeout, unit: sShutdownTimeout = 30# max content length: 64MMaxContentLength = 67108864# http server read timeout, unit: sReadTimeout = 20# http server write timeout, unit: sWriteTimeout = 40# http server idle timeout, unit: sIdleTimeout = 120[HTTP.APIForAgent]Enable = true # [HTTP.APIForAgent.BasicAuth]# user001 = &quot;ccc26da7b9aba533cbb263a36c07dcc5&quot;# 一定要改为true，否则无法启动[HTTP.APIForService]Enable = true[HTTP.APIForService.BasicAuth]# 修改token鉴权，与n9e配置文件内一致user001 = &quot;ccc26da7b9aba533cbb263sdklf902384&quot;[Alert][Alert.Heartbeat]# auto detect if blankIP = &quot;&quot;# unit msInterval = 1000# 同一个机房一定要同一个名字EngineName = &quot;shenzhen-region&quot;# [Alert.Alerting]# NotifyConcurrency = 10[Pushgw]# use target labels in database instead of in seriesLabelRewrite = true# # default busigroup key name# BusiGroupLabelKey = &quot;busigroup&quot;ForceUseServerTS = true# [Pushgw.DebugSample]# ident = &quot;xx&quot;# __name__ = &quot;xx&quot;# [Pushgw.WriterOpt]# QueueMaxSize = 1000000# QueuePopSize = 1000[[Pushgw.Writers]] # 这个是单机版的victoriametricsUrl = &quot;http://192.168.137.23:8428/api/v1/write&quot;# Basic auth usernameBasicAuthUser = &quot;&quot;# Basic auth passwordBasicAuthPass = &quot;&quot;# timeout settings, unit: msHeaders = [&quot;X-From&quot;, &quot;n9e&quot;]Timeout = 10000DialTimeout = 3000TLSHandshakeTimeout = 30000ExpectContinueTimeout = 1000IdleConnTimeout = 90000# time duration, unit: msKeepAlive = 30000MaxConnsPerHost = 0MaxIdleConns = 100MaxIdleConnsPerHost = 100## Optional TLS Config# UseTLS = false# TLSCA = &quot;/etc/n9e/ca.pem&quot;# TLSCert = &quot;/etc/n9e/cert.pem&quot;# TLSKey = &quot;/etc/n9e/key.pem&quot;# InsecureSkipVerify = false# [[Writers.WriteRelabels]]# Action = &quot;replace&quot;# SourceLabels = [&quot;__address__&quot;]# Regex = &quot;([^:]+)(?::\\d+)?&quot;# Replacement = &quot;$1:80&quot;# TargetLabel = &quot;__address__&quot;# 开启为true，categraf的配置文件可以改为edge的IP地址[Ibex]Enable = trueRPCListen = &quot;0.0.0.0:20090&quot;[Redis]# address, ip:port or ip1:port,ip2:port for cluster and sentinel(SentinelAddrs)# 修改为边缘机房本地的redis集群地址Address = &quot;127.0.0.1:6379&quot;# Username = &quot;&quot;# Password = &quot;&quot;# DB = 0# UseTLS = false# TLSMinVersion = &quot;1.2&quot;# standalone cluster sentinelRedisType = &quot;standalone&quot;# Mastername for sentinel type# MasterName = &quot;mymaster&quot;# SentinelUsername = &quot;&quot;# SentinelPassword = &quot;&quot;cat &gt; /etc/systemd/system/n9e-edge.service &lt;&lt;&#39;EOF&#39;[Unit]Description=&quot;n9e-edge&quot;After=network.target[Service]Type=simpleExecStart=/data/n9e-edge/n9e-edge -configs /data/n9e-edge/etc/edgeWorkingDirectory=/data/n9e-edge/etc/edgeRestart=on-failureSuccessExitStatus=0LimitNOFILE=65536StandardOutput=syslogStandardError=syslogSyslogIdentifier=n9e-edgeOOMScoreAdjust=-1000[Install]WantedBy=multi-user.targetEOFsystemctl daemon-reloadsystemctl enable --now n9e-edge.servicesystemctl status n9e-edge.service</code></pre><h2 id="3.1-%E9%85%8D%E7%BD%AE%E6%89%80%E6%9C%89%E8%BE%B9%E7%BC%98%E6%9C%BA%E6%88%BF%E7%9A%84%E6%95%B0%E6%8D%AE%E6%BA%90%EF%BC%8C%E5%85%B3%E8%81%94%E5%AF%B9%E5%BA%94%E7%9A%84%E5%91%8A%E8%AD%A6%E5%BC%95%E6%93%8Eenginename" tabindex="-1">3.1 配置所有边缘机房的数据源，关联对应的告警引擎enginename</h2><p><img src="/upload/2025/12/image-20250812200224255.png" alt="image-20250812200224255" /></p><h2 id="3.2-%E9%80%9A%E8%BF%87%E4%B8%9A%E5%8A%A1%E7%BB%84%E6%9D%A5%E5%8C%BA%E5%88%86%E4%B8%8D%E5%90%8C%E7%9A%84%E9%87%87%E9%9B%86%E5%99%A8%EF%BC%8C%E4%BE%8B%E5%A6%82exporter%E5%92%8Ctelegraf" tabindex="-1">3.2 通过业务组来区分不同的采集器，例如exporter和telegraf</h2><p><img src="/upload/2025/12/image-20250812200325406.png" alt="image-20250812200325406" /></p><h2 id="3.3-%E5%B0%86%E5%91%8A%E8%AD%A6%E8%A7%84%E5%88%99%E5%85%B3%E8%81%94%E6%89%80%E6%9C%89%E8%BE%B9%E7%BC%98%E6%9C%BA%E6%88%BF%E7%9A%84%E6%95%B0%E6%8D%AE%E6%BA%90%EF%BC%8C%E8%BF%99%E9%87%8C%E7%B2%BE%E7%A1%AE%E5%8C%B9%E9%85%8D%E8%BE%B9%E7%BC%98%E7%9A%84%E6%95%B0%E6%8D%AE%E6%BA%90%E6%9D%A5%E8%BF%9B%E8%A1%8C%E6%B5%8B%E8%AF%95" tabindex="-1">3.3 将告警规则关联所有边缘机房的数据源，这里精确匹配边缘的数据源来进行测试</h2><p><img src="/upload/2025/12/image-20250812200456903.png" alt="image-20250812200456903" /></p><h2 id="3.4-%E5%B0%86%E5%91%8A%E8%AD%A6%E9%98%88%E5%80%BC%E8%B0%83%E4%BD%8E%EF%BC%8C%E8%AE%A9%E5%A4%9C%E8%8E%BA%E6%9B%B4%E6%96%B0%E5%91%8A%E8%AD%A6%E8%A7%84%E5%88%99%EF%BC%8C%E5%B9%B6%E4%B8%8B%E6%B2%89%E5%88%B0%E8%BE%B9%E7%BC%98%E6%9C%BA%E6%88%BFedge" tabindex="-1">3.4 将告警阈值调低，让夜莺更新告警规则，并下沉到边缘机房edge</h2><p><img src="/upload/2025/12/image-20250812200603861.png" alt="image-20250812200603861" /></p><h2 id="3.5-%E9%A9%AC%E4%B8%8A%E5%B0%86%E4%B8%AD%E5%BF%83%E6%9C%BA%E6%88%BF%E7%9A%843%E5%8F%B0%E9%9B%86%E7%BE%A4%E5%8C%85%E6%8B%AC%E5%A4%9C%E8%8E%BA%EF%BC%8C%E7%9B%B4%E6%8E%A5%E6%96%AD%E5%BC%80%E7%94%B5%E6%BA%90%E6%A8%A1%E6%8B%9F%E6%95%85%E9%9A%9C" tabindex="-1">3.5 马上将中心机房的3台集群包括夜莺，直接断开电源模拟故障</h2><h2 id="3.6-%E7%84%B6%E5%90%8E%E8%A7%82%E5%AF%9Fedge%E7%9A%84%E6%97%A5%E5%BF%97%EF%BC%8C%E9%AA%8C%E8%AF%81%E8%BE%B9%E7%BC%98%E6%9C%BA%E6%88%BF%E5%91%8A%E8%AD%A6%E4%B8%8B%E6%B2%89%E6%98%AF%E5%90%A6%E8%83%BD%E6%AD%A3%E5%B8%B8%E5%8F%91%E5%87%BA" tabindex="-1">3.6 然后观察edge的日志，验证边缘机房告警下沉是否能正常发出</h2><ul><li>中心机房故障，只保留边缘机房。告警规则由edge去本地边缘机房victoria查询，告警消息由edge发送</li><li>中心机房恢复后，edge是不会发送恢复告警的，因为此时中心机房的victoria已经恢复，由n9e夜莺接管了，重新改回正常触发阈值。除非不修改回正常的触发阈值，再等下一次由n9e触发的告警</li></ul><p><img src="/upload/2025/12/image-20250812174844223.png" alt="image-20250812174844223" /></p><p><img src="/upload/2025/12/image-20250812174902210.png" alt="image-20250812174902210" /></p><h1 id="4.-%E6%A8%A1%E6%8B%9F2%E4%B8%AA%E8%BE%B9%E7%BC%98%E6%9C%BA%E6%88%BF%E6%95%85%E9%9A%9C%E6%83%85%E5%86%B5" tabindex="-1">4. 模拟2个边缘机房故障情况</h1><h2 id="4.1-%E5%85%88%E5%B0%86%E6%89%80%E6%9C%89%E5%91%8A%E8%AD%A6%E8%A7%84%E5%88%99%E6%89%B9%E9%87%8F%E6%94%B9%E4%B8%BA%E5%8C%B9%E9%85%8D%E6%89%80%E6%9C%89%E6%95%B0%E6%8D%AE%E6%BA%90%EF%BC%8C%E7%A1%AE%E4%BF%9D%E4%B8%AD%E5%BF%83%E6%9C%BA%E6%88%BF%2B%E6%89%80%E6%9C%89%E8%BE%B9%E7%BC%98%E6%9C%BA%E6%88%BF%E9%83%BD%E5%85%B3%E8%81%94%E8%B5%B7%E6%9D%A5" tabindex="-1">4.1 先将所有告警规则批量改为匹配所有数据源，确保中心机房+所有边缘机房都关联起来</h2><p><img src="/upload/2025/12/image-20250813102629309.png" alt="image-20250813102629309" /></p><p><img src="/upload/2025/12/image-20250813102640555.png" alt="image-20250813102640555" /></p><h2 id="4.2-%E5%B0%86%E5%85%B6%E4%B8%AD1%E4%B8%AA%E5%91%8A%E8%AD%A6%E8%A7%84%E5%88%99%E7%9A%84%E9%98%88%E5%80%BC%E8%B0%83%E4%BD%8E%EF%BC%8C%E7%84%B6%E5%90%8E%E9%A9%AC%E4%B8%8A%E5%B0%862%E4%B8%AA%E8%BE%B9%E7%BC%98%E6%9C%BA%E6%88%BF%E6%96%AD%E7%94%B5%E6%A8%A1%E6%8B%9F%E6%95%85%E9%9A%9C" tabindex="-1">4.2 将其中1个告警规则的阈值调低，然后马上将2个边缘机房断电模拟故障</h2><ul><li>告警规则阈值调低后，边缘的vmagent会推送数据到中心机房，因此告警会从中心机房的监控数据中获取并触发</li></ul><p><img src="/upload/2025/12/image-20250813103214525.png" alt="image-20250813103214525" /></p><h1 id="5.-%E9%85%8D%E7%BD%AE%E7%94%B5%E8%AF%9D%E5%91%8A%E8%AD%A6" tabindex="-1">5. 配置电话告警</h1><h2 id="5.1-%E5%85%88%E5%8E%BB%E9%98%BF%E9%87%8C%E4%BA%91%E5%88%9B%E5%BB%BA%E8%AF%AD%E9%9F%B3%E9%80%9A%E7%9F%A5%E6%A8%A1%E6%9D%BF" tabindex="-1">5.1 先去阿里云创建语音通知模板</h2><p><strong>模板内容</strong></p><pre><code class="language-bash">告警机房：${business}，告警消息为：${summary}，当前触发值为：${values}，告警级别：${severity}，请及时处理。</code></pre><p><img src="/upload/2025/12/image-20250813151127544.png" alt="image-20250813151127544" /></p><p><img src="/upload/2025/12/image-20250813151137909.png" alt="image-20250813151137909" /></p><h2 id="5.2-%E5%A4%9C%E8%8E%BA%E5%88%9B%E5%BB%BAaliyun_voice%E9%80%9A%E7%9F%A5%E5%AA%92%E4%BB%8B" tabindex="-1">5.2 夜莺创建Aliyun_Voice通知媒介</h2><p><img src="/upload/2025/12/image-20250813151320248.png" alt="image-20250813151320248" /></p><h2 id="5.3-%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0%E5%A1%AB%E5%86%99%E9%98%BF%E9%87%8C%E4%BA%91%E7%9A%84%E9%80%9A%E7%9F%A5%E6%A8%A1%E6%9D%BFttscode%E5%92%8Cnumber%E7%AD%89%E4%BF%A1%E6%81%AF" tabindex="-1">5.3 请求参数填写阿里云的通知模板TtsCode和Number等信息</h2><pre><code class="language-bash">AccessKeyId=LTAI5tQhsjxxxxxAccessKeySecret=30cFbeMS4xxxxCalledNumber={{ $sendto }}CalledShowNumber=阿里云的电话TtsCode=模板codeTtsParam={&quot;business&quot;:&quot;{{$tpl.business}}&quot;,&quot;summary&quot;:&quot;{{$tpl.summary}}&quot;,&quot;values&quot;:&quot;{{$tpl.values}}&quot;,&quot;severity&quot;:&quot;{{$tpl.severity}}&quot;}</code></pre><p><img src="/upload/2025/12/image-20250813151420346.png" alt="image-20250813151420346" /></p><h2 id="5.4-%E9%85%8D%E7%BD%AE%E6%B6%88%E6%81%AF%E6%A8%A1%E6%9D%BF%EF%BC%8C%E9%80%89%E6%8B%A9aliyun-voice%E7%9A%84%E6%A8%A1%E6%9D%BF%E8%BF%9B%E8%A1%8C%E4%BF%AE%E6%94%B9" tabindex="-1">5.4 配置消息模板，选择Aliyun Voice的模板进行修改</h2><pre><code class="language-bash"># 字段标识必须与上面的{{$tpl.business}}，模板key一致business={{$event.TagsMap.business}}severity={{if eq $event.Severity 1}}critical{{else if eq $event.Severity 2}}warning{{else}}info{{end}}summary={{$event.AnnotationsJSON.summary}}values={{$event.TriggerValue}}</code></pre><p><img src="/upload/2025/12/image-20250813152011457.png" alt="image-20250813152011457" /></p><h2 id="5.5-%E5%9B%A2%E9%98%9F%E6%B7%BB%E5%8A%A0%E5%AF%B9%E5%BA%94%E7%9A%84%E4%BA%BA%E5%91%98%EF%BC%8C%E9%80%9A%E7%9F%A5%E8%A7%84%E5%88%99%E9%80%89%E6%8B%A9%E6%8E%A5%E6%94%B6%E5%9B%A2%E9%98%9F" tabindex="-1">5.5 团队添加对应的人员，通知规则选择接收团队</h2><p><img src="/upload/2025/12/image-20250813152107972.png" alt="image-20250813152107972" /></p><h2 id="5.6-%E6%B5%8B%E8%AF%95%E7%94%B5%E8%AF%9D%E5%91%8A%E8%AD%A6%EF%BC%8C%E5%B0%86%E9%80%9A%E7%9F%A5%E8%A7%84%E5%88%99%E6%B7%BB%E5%8A%A0%E5%AF%B9%E5%BA%94%E7%9A%84%E5%91%8A%E8%AD%A6%E9%80%9A%E9%81%93" tabindex="-1">5.6 测试电话告警，将通知规则添加对应的告警通道</h2><p><img src="/upload/2025/12/image-20250813152336120.png" alt="image-20250813152336120" /></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[提问的技巧]]></title>
                <link rel="alternate" type="text/html" href="https://blog.yongjie.top/archives/ti-wen-de-ji-qie" />
                <id>tag:https://blog.yongjie.top,2025-09-27:ti-wen-de-ji-qie</id>
                <published>2025-09-27T16:10:56+08:00</published>
                <updated>2025-12-23T16:12:20+08:00</updated>
                <author>
                    <name>Deng YongJie's blog</name>
                    <uri>https://blog.yongjie.top</uri>
                </author>
                <content type="html">
                        <![CDATA[<h2 id="%E6%8E%88%E4%BA%BA%E4%BB%A5%E9%B1%BC%E4%B8%8D%E5%A6%82%E6%8E%88%E4%BA%BA%E4%BB%A5%E6%B8%94" tabindex="-1">授人以鱼不如授人以渔</h2><p>很简单的一个问题，你是想要学习解决问题的思路和方法，还是直接就想要享受成果？</p><p>授人以渔是传道解惑，在回答你的问题时可以得到正反馈、满足感，同时也意味着这个领域内增加了一些优质的新鲜血液，因此我们乐于授人以渔。</p><p><strong>以问为舟，渡运维之海</strong>，一个好的问题，如同在迷雾中点亮灯塔，不仅照亮眼前的困境，更指引思考的方向。一个宽泛的问题如同没有坐标的地图，而一个精准的问题，则是带有经纬度的导航。</p><p><img src="/upload/2025/12/3055c190ae7be1f4466395a572382b29.jpg" alt="3055c190ae7be1f4466395a572382b29" /></p><h2 id="%E4%B8%80%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E6%8F%90%E9%97%AE%E6%AF%94%E7%AD%94%E6%A1%88%E6%9B%B4%E9%87%8D%E8%A6%81%EF%BC%9F" tabindex="-1">一、为什么提问比答案更重要？</h2><h3 id="1.-%E4%BB%8E%E2%80%9C%E7%BB%8F%E9%AA%8C%E2%80%9D%E5%88%B0%E2%80%9C%E5%AE%9E%E8%B7%B5%E2%80%9D%E7%9A%84%E8%AE%A4%E7%9F%A5%E9%9D%A9%E5%91%BD" tabindex="-1">1. <strong>从“经验”到“实践”的认知革命</strong></h3><p>运维这条路它可不像搭积木，按照说明书一步步来就能搞定。运维更像是在一片未知的森林里摸索前行，到处都是隐藏的 “陷阱” 和 “谜题”。当你遇到难题时，如果能把问题问得好，那简直就像拥有了一个超级精准的导航仪，能直接带你找到解决问题的最佳路径。</p><p>对一个问题良好的界定，已经将问题解决一半了。你想啊，要是你问的问题含含糊糊，别人连你到底卡在哪儿都不知道，那怎么给你出招呢？就好比你跟朋友说 “我有点不舒服”，朋友肯定得追问你是头疼、肚子疼还是哪里难受，才能帮你决定是去买药还是去医院。提问也一样，得把症状说得明明白白，别人才能对症下药。</p><p>例如，当你在调试一段代码时，若仅停留在“为什么报错？”的层面，可能永远无法触及根本问题；但若将其拆解为“变量作用域是否冲突？” “异步回调是否未处理异常？”，问题便迎刃而解。</p><h3 id="2.-%E2%80%9C%E9%97%AE%E9%A2%98%E7%95%8C%E5%AE%9A%E2%80%9D%E7%9A%84%E7%A7%91%E5%AD%A6%E6%96%B9%E6%B3%95%E8%AE%BA" tabindex="-1">2. “问题界定”的科学方法论</h3><p>在运维领域里，那可是啥样的问题都有。有些朋友提问，简直就是让回答者一头雾水。比如说 “我的脚本跑不了，咋回事啊？” 这问题一抛出来，估计大家都得在心里默默翻个白眼。脚本跑不了的原因千千万万，可能是语法错误，可能是逻辑漏洞，还可能是环境配置问题，你这么一问，别人咋猜得到你到底是哪出了问题呢？</p><p>还有些朋友，就喜欢问一些特别宽泛的问题。比如 “怎么用 Python 做个网站？” 这问题大得都能把人给吓跑了。做网站涉及到前端、后端、数据库一大堆东西，你这么一问，别人得从何说起呢？这就好比你问 “怎么盖一栋房子？” 是先打地基、还是先砌墙、还是先装水电？</p><p>问题的界定需结合**“历史性”与“情景性”**。避免用“固定概念”束缚思维，而需以“新的眼光”重构问题。面对需求文档，若直接照搬需求描述（如“实现一个登录功能”），可能陷入技术细节的泥潭；但若将其重构为 “如何平衡用户认证的安全性与体验流畅性？”，则能快速锁定技术选型（如OAuth2.0 vs JWT）。</p><h2 id="%E4%BA%8C%E3%80%81%E6%8F%90%E9%97%AE%E7%9A%84%E6%AD%A3%E7%A1%AE%E6%96%B9%E5%BC%8F" tabindex="-1">二、提问的正确方式</h2><h3 id="1.%E7%B2%BE%E5%87%86%E5%AE%9A%E4%BD%8D%EF%BC%8C%E7%9B%B4%E5%87%BB%E8%A6%81%E5%AE%B3" tabindex="-1">1.精准定位，直击要害</h3><p>当你遇到问题时，先别急着求救。自己先好好琢磨琢磨，把问题的范围缩小缩小再缩小。比如说，你发现程序运行得很慢，那就先想想是哪一部分可能出了问题。是数据库查询太耗时？还是代码不够高效？你可以通过一些调试工具，看看程序在哪个环节卡住了。就像医生给病人做检查一样，得先找到病因，才能开药方。</p><p>在发出求助前，先成为自己问题的第一位诊断师。</p><ul><li><strong>缩小范围</strong>：是性能问题、稳定性问题还是功能异常？</li><li><strong>收集证据</strong>：日志、监控指标、错误码、时间线——数据是提问的最佳佐证。</li><li><strong>初步假设</strong>：基于证据提出一个或多个可能的原因方向。这展示了你的思考深度。</li></ul><h3 id="2.%E6%8F%90%E4%BE%9B%E8%83%8C%E6%99%AF%EF%BC%8C%E8%AE%A9%E4%BF%A1%E6%81%AF%E8%87%AA%E5%B7%B1%E8%AF%B4%E8%AF%9D" tabindex="-1">2.提供背景，让信息自己说话</h3><p>一个问题往往不是孤立存在的，它背后可能有一大堆相关的因素。所以你在提问的时候，一定要把相关的背景信息都说清楚。比如说，你在用 Python 写一个数据分析的脚本，遇到了一个库的兼容性问题。那你得说清楚你用的是哪个 Python 版本，用的是哪个数据分析库，还有你的操作系统是什么。因为不同的 Python 版本、不同的库版本、不同的操作系统，都可能会导致问题不一样。</p><p>就像你去餐厅吃饭，跟服务员说 “我要一份不辣的菜”，服务员还得知道你是对辣味完全不能接受，还是只是稍微有点忌口，这样才能给你推荐合适的菜品。信息给得越全，别人回答得就越准。</p><p>一个孤立的问题陈述是苍白的。请赋予它生命所需的上下文：</p><ul><li><strong>环境</strong>：操作系统、软件版本、硬件配置、网络拓扑。</li><li><strong>操作</strong>：导致问题发生的具体步骤或变更。</li><li><strong>预期与实际</strong>：你期望发生什么？实际发生了什么？</li><li><strong>已尝试的努力</strong>：你已做过哪些排查？结果如何？这能避免重复劳动，并显示你的主动性。</li></ul><h3 id="3.%E6%8F%90%E4%BE%9B%E4%BB%A3%E7%A0%81%E3%80%81%E6%97%A5%E5%BF%97%E4%B8%8E%E6%88%AA%E5%9B%BE" tabindex="-1">3.<strong>提供</strong>代码、日志与截图</h3><p>在技术领域，一份可复现的代码片段、一段关键的错误日志、一张清晰的监控截图，胜过千言万语的描述。</p><h3 id="4.%E5%B1%95%E7%A4%BA%E6%80%9D%E8%80%83%EF%BC%8C%E8%B5%A2%E5%BE%97%E5%B0%8A%E9%87%8D" tabindex="-1">4.展示思考，赢得尊重</h3><p>当你提问的时候，把自己思考的过程也展示出来，那绝对能让回答者对你刮目相看。这不仅能让别人更好地理解你的问题，还能让别人知道你不是个只会伸手要答案的 “小白”。比如说，你在解决一个算法问题的时候，你可以这样说 “我先尝试了用暴力枚举的方法，但是发现时间复杂度太高了。然后我又想到了用动态规划，但是不知道怎么定义状态，现在卡在这里了。” 这样一说，别人就知道你已经思考过了，只是在某个环节遇到了困难，他们也更愿意帮你出谋划策。</p><h2 id="%E4%B8%89%E3%80%81%E6%8F%90%E9%97%AE%E7%9A%84-%E2%80%9C%E9%94%A6%E4%B8%8A%E6%B7%BB%E8%8A%B1%E2%80%9D-%E6%8A%80%E5%B7%A7" tabindex="-1">三、提问的 “锦上添花” 技巧</h2><h3 id="1.%E5%96%84%E7%94%A8%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%E4%B8%8E%E6%96%87%E6%A1%A3%EF%BC%8C%E5%85%88%E8%87%AA%E5%8A%A9%E5%90%8E%E6%B1%82%E5%8A%A9" tabindex="-1">1.善用搜索引擎与文档，先自助后求助</h3><p>在提问之前，利用搜索引擎、官方文档、内部Wiki，你很可能发现答案早已存在。这个过程本身，就是极佳的学习与信息过滤训练。</p><p>你可以把问题的关键字组合一下，比如 “Python 数据分析 库 兼容性问题”，说不定就能搜到别人遇到过类似的问题，还给出了详细的解决方案。这样不仅能节省别人的时间，还能让你自己先对问题有个初步的了解。</p><h3 id="2.%E5%A4%9A%E9%80%9B%E9%80%9B%E6%8A%80%E6%9C%AF%E7%A4%BE%E5%8C%BA%EF%BC%8C%E7%A7%AF%E7%B4%AF%E7%BB%8F%E9%AA%8C" tabindex="-1">2.多逛逛技术社区，积累经验</h3><p>像 Stack Overflow、CSDN 这些技术社区，里面可是藏着无数的宝藏。你可以多逛逛，看看别人是怎么提问的，别人是怎么回答的。你还能从中学到很多知识和技巧。说不定你在逛的时候，就能看到别人遇到过和你一样的问题，还给出了超棒的解决方案。</p><h3 id="3.%E5%AD%A6%E4%BC%9A%E6%80%BB%E7%BB%93%E5%BD%92%E7%BA%B3%EF%BC%8C%E4%B8%BE%E4%B8%80%E5%8F%8D%E4%B8%89" tabindex="-1">3.学会总结归纳，举一反三</h3><p>当你解决了一个问题之后，别忘了总结一下。想想这个问题是怎么产生的，你是怎么解决的，还有没有别的解决方法。这样下次再遇到类似的问题，你就能更快地解决了。而且你还能把总结的经验分享给团队，帮助团队成长。</p><p>问题解决后，完成最后的闭环：</p><ul><li><strong>总结归纳</strong>：问题根源是什么？解决方案是什么？有无更优解？</li><li><strong>分享传承</strong>：将经验写入文档、分享给团队，或形成知识库条目。</li><li><strong>举一反三</strong>：此类问题是否有通用模式？如何通过监控、告警或流程优化避免复发？</li></ul><h2 id="%E5%9B%9B%E3%80%81%E6%A1%88%E4%BE%8B%E5%90%AF%E7%A4%BA%EF%BC%9A%E4%BB%8E%E6%B7%B7%E6%B2%8C%E5%88%B0%E6%BE%84%E6%98%8E" tabindex="-1">四、案例启示：从混沌到澄明</h2><h3 id="1.-%E7%BC%96%E8%AF%91%E9%94%99%E8%AF%AF" tabindex="-1"><strong>1. 编译错误</strong></h3><p><strong>❌ 无效提问</strong>：<br />“我的代码编译不过，怎么办？”<br /><strong>问题分析</strong>：无具体错误信息、无代码片段、无环境描述。</p><p><strong>✅ 正确提问</strong>：<br />“在Linux环境下使用GCC 13.1编译C<ins>项目时，报错<code>undefined reference to 'std::thread::_M_start_thread'</code>，代码中使用了C</ins>11的<code>std::thread</code>，已添加<code>-pthread</code>编译选项但未解决，是否遗漏了其他依赖？”<br /><strong>亮点</strong>：</p><ul><li>提供环境（Linux+GCC）、错误类型（链接错误）、已尝试的解决方案。</li><li>明确技术点（C++11多线程）。</li></ul><hr /><h3 id="2.-api%E8%B0%83%E7%94%A8%E5%A4%B1%E8%B4%A5" tabindex="-1"><strong>2. API调用失败</strong></h3><p><strong>❌ 无效提问</strong>：<br />“调用微信支付API没反应，求助！”<br /><strong>问题分析</strong>：无请求参数、无响应码、无调试日志。</p><p><strong>✅ 正确提问</strong>：<br />“调用微信支付V3接口返回<code>HTTP 401</code>，请求头已包含<code>Authorization: WECHAT-PAY-SHA256-RSA2048</code>签名，时间戳误差在5秒内，证书序列号已验证有效，是否因商户号与证书不匹配导致？”<br /><strong>亮点</strong>：</p><ul><li>提供具体错误码（401）、请求头细节、已排查的环节。</li><li>锁定可能原因（证书与商户号绑定）。</li></ul><hr /><h3 id="3.-%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%A4%B1%E6%95%88" tabindex="-1"><strong>3. 正则表达式失效</strong></h3><p><strong>❌ 无效提问</strong>：<br />“我的正则匹配不到内容，哪里错了？”<br /><strong>问题分析</strong>：无正则表达式、无测试文本、无编程语言。</p><p><strong>✅ 正确提问</strong>：<br />“在Python 3.11中，使用<code>re.findall(r'\b\d{3}-\d{2}-\d{4}\b', text)</code>匹配号码失败，测试文本含<code>123-45-6789</code>但返回空列表，是否因单词边界<code>\b</code>与连字符冲突？如何调整？”<br /><strong>亮点</strong>：</p><ul><li>提供正则表达式、测试用例、语言版本。</li><li>分析可能冲突点（<code>\b</code>与连字符）。</li></ul>]]>
                </content>
            </entry>
</feed>
