面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 05月31日 17:20

Dify 知识库是怎么检索的?如何提升召回和答案准确率?

Dify 知识库的核心不是“把文档丢给大模型”,而是先把文档切成块、转成向量、存进检索系统,再在用户提问时找出最相关的片段交给模型生成回答。真正影响效果的通常有四件事:文档清洗是否干净、分块是否合适、Embedding 模型是否稳定、召回后的重排和提示词是否把边界说清楚。一个常见流程是:上传 PDF、Markdown、网页或纯文本后,Dify 会抽取正文,按规则切分为多个 chunk;每个 chunk 通过 Embedding 模型转成向量;用户提问也会转成查询向量;系统根据相似度召回片段,再把片段作为上下文传给 LLM。这里的取舍很明显:块太大,召回内容容易夹带无关信息;块太小,上下文被拆散,模型可能看不到完整结论。配置时可以先用一个保守起点:chunk size 设在 500-800 字符,overlap 设在 50-120 字符,Top K 设为 3-6,score threshold 不要一开始调得太高。中文资料建议优先选择中文语义表现稳定的 Embedding 模型,并用同一批 FAQ 做回归测试。不要只看“能不能回答”,还要看答案是否引用了正确段落、是否把过期制度和现行制度混在一起。追问为什么知识库检索效果差,明明文档里有答案却召回不到?最常见原因是切分把问题和答案拆开了,向量库里每个片段都只保留了一半语义。另一个原因是文档里有大量页眉、目录、免责声明,Embedding 被噪声稀释,查询向量自然匹配不到关键内容。实际排查时不要先换大模型,而要先看命中的 chunk,确认它是否真的包含答案。边界是:如果用户问的是需要推理、汇总或跨文档比较的问题,单纯提高 Top K 只能缓解,不能保证稳定。chunk size、overlap 和 Top K 应该怎么取舍?chunk size 决定每个片段的信息密度,overlap 决定上下文是否连续,Top K 决定给模型看的候选范围。产品手册、政策制度适合中等 chunk 加少量 overlap;代码文档、API 文档则更依赖标题层级和较小片段。Top K 太低会漏召回,太高会把不相关片段塞进上下文,让模型开始“综合发挥”。踩坑点是只调一个参数看效果,最好固定测试集后成组调整。Dify 里要不要开启混合检索或重排模型?如果知识库里有大量专有名词、编号、版本号或短问答,混合检索通常比纯向量检索更稳,因为关键词匹配能补上向量语义的盲区。重排模型适合候选片段多、内容相似度高的场景,它会在召回后重新排序,把真正相关的片段推到前面。代价是延迟和成本会上升,尤其在高并发客服场景里要评估响应时间。比较稳的做法是先用纯向量建立基线,再对高频失败问题开启重排做 A/B 对比。如何判断是知识库问题还是提示词问题?先看检索日志,如果召回片段里没有答案,就是知识库侧的问题;如果片段正确但模型答偏了,才重点看提示词和模型能力。提示词里最好明确“只能依据知识库回答,缺少依据时说明不知道”,否则模型会用通用知识补洞。这里的边界是用户问题本身含糊时,即使知识库没问题也可能召回泛化片段。实践里可以让答案带引用来源,这样业务方能快速判断问题出在检索还是生成。知识库上线后应该怎么持续优化?不要把知识库当一次性导入任务,它更像搜索系统,需要持续看命中率、无答案率和用户追问。每次业务制度更新后,要删除旧版本或加上有效期,否则模型可能同时看到两套冲突规则。高频失败问题可以反向补充 FAQ,用用户真实问法写标题和正文。踩坑最多的是只追加新文档不清理旧文档,短期看资料更多,长期看答案反而更乱。
服务端阅读 05月31日 17:20

Dify 工作流怎么设计?复杂 AI 流程如何拆节点?

Dify 工作流解决什么问题?Dify 工作流适合处理一个 Prompt 很难讲清的 AI 流程。比如先判断用户意图,再检索知识库,再调用订单系统,最后生成回复并做安全校验。如果这些都塞进一个提示词,测试时可能能跑,上线后出错却不知道错在哪一步。工作流的价值是把复杂任务拆成可观察、可调试的节点。核心节点怎么理解?开始节点定义输入字段,比如用户问题、文件、客户 ID。LLM 节点负责分类、总结、改写和生成。知识库检索节点负责召回资料,条件判断节点负责分支,代码节点适合字段校验、JSON 解析和简单计算,HTTP 节点用来调用外部系统,结束节点定义对外输出。取舍很重要:模型适合理解语言,不适合做确定性计算。金额相加、字段判空、数组转换这类事,用代码节点更稳也更便宜。知识库检索为空时,也不要继续让 LLM 猜答案,应直接走兜底分支或转人工。复杂流程如何拆节点?拆分原则是:失败原因不同、调试方式不同、责任边界不同,就拆开。一个客服流程可以拆成“意图识别 → 参数提取 → 知识库检索 → 订单接口查询 → 回复生成 → 安全校验”。这样每个节点都有输入输出,定位问题比看一大段 Prompt 容易得多。不要一开始就画十几个节点。更稳的做法是先跑通最短链路,再根据真实失败样本加分支。节点越多,可控性越强,但延迟、配置和维护成本也会上升。工作流应该从问题里长出来,而不是为了显得高级而复杂。数据流转和异常怎么处理?节点之间通过变量传数据,命名要清楚,比如 user_question、retrieved_docs、order_status。常见坑是上游输出数组,下游当字符串用;HTTP 请求失败,下游还按成功结果生成回复;LLM 输出 JSON 字符串,下游却当对象取字段。关键位置可以加代码节点做类型校验。生产可用的工作流一定要有异常分支。检索为空、接口超时、用户缺字段、JSON 解析失败,都应该有明确处理。否则用户只会看到“系统异常”,团队也很难复盘。追问Workflow 和 Chatflow 有什么区别?Chatflow 更适合对话助手、知识库问答和轻量工具调用。Workflow 更适合多步骤、强结构、固定输入输出的业务流程。边界是 Chatflow 配置快,但复杂分支容易乱;Workflow 更可控,但节点多了会增加延迟。选择标准不是名字,而是流程是否需要状态和分支。什么时候用代码节点?确定性逻辑优先用代码节点,比如字段校验、格式转换、JSON 解析和简单计算。LLM 可以判断语义,但不应该负责可验证的计算。踩坑点是让模型输出“严格 JSON”后直接交给业务系统,边界输入下很容易多一句解释。加代码校验会多一步,但稳定性更好。知识库检索为空怎么办?不要让模型继续猜。应该用条件节点判断检索结果数量或相似度,低于阈值就返回“资料未覆盖”或转人工。取舍是兜底回答看起来没那么聪明,但比编造答案安全。很多知识库事故不是检索失败,而是失败后还让模型硬答。工作流输出怎么保证稳定?先在 LLM 节点约束字段,再用代码节点解析和校验,失败时重试或走异常分支。只靠提示词要求固定格式不够,模型偶尔会输出解释文字。对接业务系统时,输出字段最好做版本管理。否则工作流里改一个字段名,调用方可能立刻报错。
服务端阅读 05月31日 17:20

Dify API 怎么用?如何把 AI 应用集成到业务系统?

Dify API 适合怎么用?Dify API 的作用,是把控制台里搭好的 AI 应用接到自己的业务系统里。客服系统自动生成回复、运营后台总结用户反馈、内部知识库问答、工单分类和报告生成,都可以通过 API 调用 Dify。关键不是背端点,而是先判断应用类型:对话用 Chat API,结构化流程用 Workflow API,知识库同步用 Dataset 相关接口。认证和地址怎么配置?Dify API 通常使用 Bearer Token。密钥在应用的 API Access 页面生成,请求时放到 Header:Authorization: Bearer YOUR_API_KEYContent-Type: application/json生产环境不要把 API Key 放在前端、App 包或公开配置里。更稳的方式是前端请求自己的后端,后端保存密钥并代理 Dify 调用,这样才能做登录校验、限流、审计和密钥轮换。私有化部署还要确认 Base URL,云端可能是 https://api.dify.ai/v1,内网通常是自己的域名加 /v1。Chat API 怎么调用?聊天应用常用 /v1/chat-messages。最小请求通常包括 inputs、query、response_mode 和 user:import requestsresp = requests.post( "https://api.example.com/v1/chat-messages", headers={"Authorization": "Bearer YOUR_API_KEY"}, json={"inputs": {}, "query": "总结这条工单", "response_mode": "blocking", "user": "u-123"}, timeout=60)print(resp.json())blocking 开发简单,适合后台任务;streaming 体验更好,适合聊天窗口逐字输出。取舍是流式需要 SSE 支持,很多网关默认会缓冲响应,导致用户仍然最后一次性看到结果。多轮对话还要保存 Dify 返回的 conversation_id,下一轮继续传回去,否则追问会变成新会话。Workflow API 适合什么?/v1/workflows/run 更像触发一个后端任务,适合分类、抽取、审核、生成报告等固定输入输出场景。调用方传入 inputs 和 user,Dify 按节点执行后返回结果。它的好处是流程清楚、输出可控;边界是字段名依赖很强,工作流输入一改,业务系统也要同步改。生产集成还要补工程能力:超时、重试、错误分类、日志脱敏和成本控制。模型接口比普通接口慢,不能所有异常都返回“系统繁忙”。建议记录 request id、用户、耗时、状态码和错误类型,正文内容按合规要求脱敏。追问Chat API 和 Workflow API 怎么选?需要连续对话、上下文和追问,用 Chat API。输入一组字段、跑完流程、返回结构化结果,用 Workflow API。边界是 Chat API 更像助手,Workflow API 更像后端任务。踩坑点是把复杂审批或分类流程硬塞进聊天提示词,后期很难调试。API Key 能放前端吗?不建议,生产环境基本不要这样做。前端密钥会被抓包、源码或反编译拿到,别人可以绕过你的权限直接调用 Dify。后端代理虽然多一层开发,但能做鉴权、限流和审计。取舍上,只有临时 demo 可以前端直连,正式业务不要这么省事。streaming 和 blocking 有什么坑?blocking 简单稳定,但长回答会让用户等很久。streaming 体验好,不过浏览器、Nginx、网关和后端都要支持长连接和 SSE。常见坑是代理层缓冲响应,服务端明明一段段返回,前端却最后才显示。排查时要从 Dify、后端代理、网关三层分别看。知识库上传成功后为什么搜不到?上传成功通常只代表文件已提交,不代表切分、嵌入和索引完成。大文件或批量导入会异步处理,期间检索不到是正常的。业务系统应该查询处理状态,完成后再开放搜索。边界是文档质量、切分策略和嵌入模型都会影响召回,不是 API 成功就等于问答准确。
服务端阅读 05月31日 17:20

Dify 如何私有化部署?企业落地要注意哪些坑?

Dify 私有化部署适合什么场景?Dify 私有化部署解决的核心问题,是把 AI 应用、知识库、对话数据和权限控制放到企业自己可控的环境里。涉及合同、客户资料、工单、内部制度时,很多公司不能直接把数据交给外部平台,这时私有化就有价值。它还能接入本地模型、内部 SSO、审计系统和企业网络策略。但私有化不等于省事。云服务替你维护的数据库、Redis、对象存储、向量库、日志和升级,都会变成自己的责任。部署前先判断是 PoC、部门级使用,还是企业级平台,方案会完全不同。三种部署方式怎么选?Docker Compose 最适合验证和小规模使用,启动很快:git clone https://github.com/langgenius/dify.gitcd dify/dockercp .env.example .envdocker compose up -d它的优势是简单,适合内网试点;边界是高可用、扩缩容、备份恢复都弱。源码部署适合需要深度定制登录、权限或界面的团队,但改得越多,后续合并上游版本越痛苦。Kubernetes 适合生产环境,可以把 Web、API、Worker、数据库、Redis、对象存储和向量库拆开管理,但前提是团队真的有 K8s 运维能力。关键配置有哪些?Dify 依赖 PostgreSQL、Redis、对象存储、向量数据库、API、Worker 和前端服务。测试环境 4GB 内存也许能跑,生产至少建议 8GB 起,并按知识库规模和并发量扩容。如果模型调用外部 API,Dify 服务器不需要 GPU;只有本地跑大模型时,才要单独规划显卡和推理服务。.env 里最容易出错的是访问地址、数据库密码、Redis、对象存储、向量库类型和模型供应商配置。内网部署尤其要区分浏览器访问地址和容器内部访问地址。很多“页面能打开但上传失败、流式输出异常”的问题,都是 Console URL、Service API URL 或反向代理没配一致。生产环境要防哪些坑?第一是备份。PostgreSQL、对象存储和关键配置都要定期备份,并做恢复演练。第二是升级。Dify 更新快,升级前要先在测试环境验证数据库迁移和环境变量变化,不要直接拉 latest 镜像。第三是监控,不能只看容器存活,还要看 API 错误率、Worker 队列、数据库连接、Redis、向量库和模型调用失败率。安全上也别把“部署在内网”等同于安全。管理员权限、HTTPS、密钥管理、日志脱敏、对象存储权限都要配置好。私有化的价值是可控,不是天然免疫风险。追问Docker Compose 能用于生产吗?小团队、低并发、内部使用可以,但要补上 HTTPS、备份、日志和监控。它的边界是单机和组件耦合,宿主机或数据库出问题时恢复压力很大。踩坑最多的是试点跑顺后直接长期使用,却没有备份和升级预案。业务一旦变重要,就要考虑拆分数据库和对象存储。私有化部署一定要 GPU 吗?不一定。Dify 本身是应用编排平台,如果调用外部模型 API,就不需要 GPU。只有把大模型推理也部署在本地时,才需要按模型参数、并发和上下文长度规划显存。很多团队把 Dify 资源和模型资源混在一起估算,最后不是浪费机器,就是推理服务跑不动。最容易配错的是什么?最容易错的是内外网访问地址、反向代理和对象存储。控制台能打开,不代表 API、上传、回调和流式响应都正常。生产环境要用真实域名、HTTPS 和业务网络跑一遍完整链路。边界是有些问题只在浏览器、网关和容器互相访问时出现,本机 curl 测不出来。升级 Dify 怎么降低风险?先备份数据库、对象存储和 .env,再在测试环境按同版本路径演练。不要直接升级生产,也不要无脑追 latest。取舍上,稳定版本可以少升级,但遇到安全修复或关键功能时要有计划地升。踩坑点是忽略数据库迁移,回滚时才发现旧版本读不了新结构。
服务端阅读 05月31日 17:20

Dify 提示词工程怎么做?如何写出稳定可控的 Prompt?

Dify 提示词工程先抓住什么?Dify 里的提示词工程,重点不是把 Prompt 写得更玄,而是让应用在不同输入下稳定输出。一个可上线的 Prompt 至少要说清四件事:模型扮演什么角色、要完成什么任务、可以依据哪些上下文、输出必须长什么样。很多应用在测试时看着不错,上线后开始胡编,通常是边界没写清,而不是模型突然变差。模板、变量和系统提示词怎么分工?Dify 的 Prompt Template 支持 {{query}}、{{context}} 这类变量,也支持 Jinja2 风格的条件和循环。固定规则放在系统提示词里,动态内容通过变量传入,后续维护会清楚很多。比如知识库问答不要只写“请专业回答”,而要明确“只能根据资料回答,资料没有就说未提到”。你是企业知识库助手,只能依据资料回答。资料:{{context}}问题:{{query}}要求:资料未提到时回答“资料中未提到”;不要编造;三句话以内。取舍点在于,越不能被用户覆盖的规则,越应该放在系统层;越依赖本次请求的内容,越应该放进变量。环境变量适合保存模型网关、接口域名等配置,但密钥不要出现在可能被模型复述的提示词里。变量名也别写成 data1、result,用 user_question、retrieved_docs 这种名字更利于排查。如何优化 Prompt?优化时不要一次改一大段。先准备一组典型问题、边界问题和恶意输入,每次只改角色、示例、格式约束中的一项,再看失败类型是否减少。Few-shot 示例适合分类、抽取、格式转换;开放问答放太多示例,反而会让回答僵硬并增加 token 成本。如果业务要结构化结果,可以约束 JSON 字段,但后端仍要做解析失败兜底。生产环境常见坑是模型偶尔多输出一句解释,导致 JSON 解析失败。更稳的做法是在 Dify 工作流里加代码节点校验,失败时重试一次或走异常分支。什么时候不该继续堆 Prompt?当一个提示词同时承担意图判断、知识检索、业务计算和回答生成时,就该拆成工作流节点。模型适合理解和生成,不适合做确定性校验。复杂应用里,Prompt 工程不是把所有规则写进一段话,而是决定哪些交给模型,哪些交给流程、代码和业务系统。追问Dify 的 Prompt Template 和普通提示词有什么区别?普通提示词是一段固定文本,适合临时测试。Prompt Template 可以通过变量注入用户问题、知识库内容和节点输出,更适合应用化。边界是模板只提升组织方式,任务定义含糊时,变量再多也无法保证稳定。踩坑点是把所有上下文都塞进去,成本变高还会稀释重点。示例越多效果越好吗?不一定。分类、抽取任务放 2-4 个高质量示例很有帮助,开放问答示例太多会限制表达。示例还要覆盖正例、反例和边界,否则模型只学到表面格式。最常见的坑是示例允许猜测,但正式规则要求不能编造,模型会优先模仿示例。如何减少 Dify 应用胡编?先在提示词里明确“只依据资料回答”,再在检索为空时直接走兜底分支。不要把用户问题和知识库资料混在同一段里,要用变量标签区分来源。真正稳定的做法是流程控制加提示词约束,而不是只写一句“不要胡编”。边界是资料本身过旧或召回质量差时,Prompt 也救不了答案。什么时候要拆成工作流?当任务有多个步骤,且每步失败原因不同,就应该拆。比如先分类、再检索、再生成、再校验,比一个大 Prompt 更好排查。取舍是节点越多链路越长,延迟和维护成本也会上升。实际项目通常先做最短链路,再根据失败样本逐步拆分。
服务端阅读 05月31日 17:12

Dify 企业权限管理应该怎么配置?

Dify 的团队协作和权限管理,核心是用工作空间隔离资源,用角色控制操作范围,再用 API Key、日志和审计手段约束生产访问。回答这类问题时,不能只列 Owner、Admin、Editor、Viewer,还要说明企业里怎么分组、怎么管知识库、怎么防止误改生产应用。比较稳的配置思路是:按业务或环境划分工作空间,按职责分配最小权限,生产应用限制编辑入口,外部系统统一使用专用 API Key,不用个人账号长期集成。追问Dify 常见角色应该怎么分配?Owner 通常只给平台负责人或少数管理员,负责工作空间生命周期、关键配置和最终兜底。Admin 可以管理成员、模型、应用和资源,但不建议给太多人,否则权限边界会失控。Editor 适合应用构建者、提示词工程师和知识库维护人员,Viewer 适合业务评审、测试和只需要查看效果的人。取舍是协作效率和安全之间的平衡:权限给少了会拖慢迭代,给多了容易误删应用、泄露日志或改坏生产配置。企业里应该按团队还是按环境划分工作空间?如果公司业务线很多,优先按业务线或部门划分工作空间,能让知识库、应用和成员关系更清楚。如果生产安全要求高,可以再把测试和生产拆开,至少保证生产应用只有少数人能编辑。边界是工作空间拆得太细会带来重复配置,比如模型供应商、通用知识库和成员管理都要维护多份。比较实用的做法是核心生产应用单独隔离,普通实验应用放在团队空间里,并用命名规范标明 dev、test、prod。API Key 权限管理有哪些坑?最大的坑是用个人账号生成的 Key 接入生产系统,员工离职、换岗或误删时会影响线上服务。更好的方式是为应用或系统单独创建集成用 Key,记录用途、负责人、创建时间和轮换周期。还要避免把 Key 写死在前端、文档截图或日志里,必要时通过环境变量、密钥管理服务或 CI/CD Secret 注入。取舍在于轮换越频繁越安全,但也会增加运维成本,所以至少要对生产 Key 建立定期检查和泄露应急流程。知识库和应用权限应该怎么管?知识库通常比应用更敏感,因为里面可能有合同、客户资料、内部制度或产品路线图。配置时要明确谁能上传、删除、重新索引和绑定知识库,避免普通编辑者把测试资料接到生产应用。应用权限则要区分查看、调试、编辑、发布和调用,尤其是带外部工具的工作流,错误配置可能触发真实业务操作。踩坑是只管页面编辑权限,不管知识库数据来源和工具调用权限,结果模型回答泄露了不该看的内容。如何做审计和日常权限复查?企业级配置不能只在上线当天做一次,至少要按月或按季度复查成员、角色、API Key 和应用访问日志。操作上可以导出成员清单,与组织架构或 IAM 系统比对,清理离职、转岗和临时项目成员。对高风险应用,要关注谁修改了提示词、知识库、模型参数和工具配置,并保留变更记录。Dify 自带能力能覆盖一部分协作和日志需求,但如果要满足合规审计,最好接入公司统一身份认证、网关日志和集中审计平台。
服务端阅读 05月31日 17:12

Dify、LangChain 和 Flowise 该怎么选?

Dify、LangChain、Flowise 不是简单的谁更强,而是抽象层级不同。Dify 更像面向团队交付的 AI 应用平台,提供可视化编排、知识库、模型接入、API 发布、日志和权限能力;LangChain 更像开发框架,适合写代码做深度定制;Flowise 更偏可视化链路编排,适合把 LangChain 类能力拖拽出来快速验证。面试回答时,关键不是背功能清单,而是能根据团队技术能力、上线速度、可控性和运维成本做取舍。追问Dify 相比 LangChain 的核心优势是什么?Dify 的优势是开箱即用和团队协作成本低,产品、运营或解决方案同学也能参与调提示词、建知识库、看日志。LangChain 的优势是代码可控性强,复杂 Agent、私有工具协议、特殊记忆策略或深度业务逻辑更容易定制。取舍很直接:如果目标是快速上线一个客服、知识库问答或内部助手,Dify 更省时间;如果目标是把 LLM 深度嵌入核心业务系统,LangChain 或自研框架更灵活。踩坑是把 Dify 当成万能低代码平台,遇到复杂状态机、强事务或多系统编排时,仍然需要后端工程兜底。Dify 和 Flowise 的区别在哪里?Flowise 更突出流程编排体验,适合快速把提示词、模型、向量库、工具节点连起来验证想法。Dify 除了编排,还把应用发布、会话日志、知识库管理、模型供应商配置、权限和 API 调用这些上线后必须处理的东西做进了平台。边界是,Flowise 在轻量实验里更直接,Dify 在多人维护和生产交付里更完整。常见选择是:个人或小团队先用 Flowise 验证链路,确定要给业务部门长期使用时,再考虑 Dify 或自研平台来承接治理。什么时候不应该选 Dify?如果项目需要非常细的底层控制,例如自定义推理调度、复杂 Agent 记忆、跨多个内部系统的事务一致性,Dify 可能会显得不够自由。如果团队已经有成熟的 AI 中台、观测体系和发布流程,直接接入 LangChain、LlamaIndex 或内部框架可能更贴合现有架构。另一个边界是性能和成本,Dify 能提升开发效率,但不自动保证低延迟,模型、检索、外部工具调用仍要单独优化。面试里可以补一句:选 Dify 是买平台效率,不是放弃工程治理。企业落地时怎么做平台选型?先看应用类型:知识库问答、客服机器人、销售助手这类标准应用,Dify 通常更合适;研究型 Agent、强业务逻辑自动化、复杂多工具规划,则更适合代码框架。再看团队结构,如果业务方需要频繁调流程,低代码平台能减少研发排队;如果只有工程团队维护,代码方式更容易纳入 CI/CD 和代码审查。还要看部署要求,Dify 支持私有化和多模型接入,对有数据隔离要求的企业更友好。踩坑是只看 Demo 效果,不看权限、日志、灰度、回滚、模型成本和知识库维护,这些才决定能不能长期跑。从 LangChain 迁到 Dify 需要注意什么?不要直接把代码里的每个链路节点一比一搬进 Dify,应该先拆清楚哪些是提示词流程,哪些是业务逻辑,哪些是系统集成。提示词、检索和简单工具调用可以放到 Dify 工作流里,强校验、订单状态变更、支付等关键逻辑应保留在后端服务,通过 API 工具让 Dify 调用。配置上要补模型供应商、知识库分段策略、环境变量、API Key、日志查看权限和测试数据集。最大的坑是迁移后缺少回归测试,建议保留一组标准问题,对比准确率、响应时间和单次成本,再决定是否切生产流量。
服务端阅读 05月31日 17:12

如何用 Dify 监控和日志定位应用性能问题?

Dify 的监控和日志主要用来回答三个问题:应用有没有被正常调用、慢在哪里、钱花在了哪里。面试里不要只背“有调用统计、对话日志、Token 统计”,更要说清楚怎么用这些数据定位问题。一般先看应用层监控里的请求量、成功率、平均响应时间和 Token 消耗,再回到具体会话日志检查用户输入、模型输出、上下文长度、工作流节点耗时和错误信息。真正做优化时,监控看趋势,日志看现场,成本统计看取舍,三者要一起看。追问Dify 里哪些指标最值得优先看?优先看请求量、错误率、响应时间和 Token 用量,因为它们分别对应稳定性、体验和成本。平均响应时间只能看大概,排查体验问题时更建议看 P95 或 P99,慢请求往往藏在长尾里。Token 用量不能只看总数,还要拆成输入和输出,输入过大通常说明知识库召回、历史上下文或提示词模板太臃肿。这里的取舍是,监控指标越细越利于定位,但也会增加解释成本,团队初期先固定 4 到 6 个核心指标更稳。发现 Dify 应用变慢时应该怎么排查?先确认是不是所有请求都慢,如果只有部分用户或部分问题慢,就从对话日志里找对应会话。然后看工作流节点耗时,区分是模型响应慢、知识库检索慢、HTTP 工具调用慢,还是提示词和上下文太长。常见踩坑是只盯模型供应商,结果真正耗时在外部 API 超时或知识库召回过多。边界也要说清:Dify 能帮你看到应用链路和日志,但底层模型排队、网络抖动、数据库慢查询仍需要结合部署环境的 Prometheus、容器日志或云监控一起看。如何通过日志优化 Token 成本?先抽样查看高 Token 会话,判断输入长是因为系统提示词过长、历史消息保留太多,还是知识库片段召回过宽。优化时可以缩短提示词模板、限制上下文轮数、调整知识库 top_k、提高相似度阈值,并控制输出长度。取舍在于,压缩上下文能省钱也能变快,但过度压缩会让模型丢失关键信息,回答质量会下降。比较稳的做法是先在测试应用里做 A/B 对比,看成功率、人工满意度和单次成本是否同时改善。Dify 日志能不能直接当审计日志使用?不建议完全等同。对话日志适合分析输入输出、排查异常和复现问题,但企业审计还需要记录谁在什么时间修改了应用、模型配置、知识库和 API Key。涉及隐私数据时,还要考虑日志脱敏、保存周期和访问权限,不能让所有编辑者随便查看用户原始输入。一个常见坑是把生产用户问题直接导出给外部人员分析,里面可能包含手机号、合同内容或内部系统字段。企业里通常要把 Dify 日志和网关日志、身份系统、SIEM 或集中日志平台对接,才能满足完整审计要求。私有化部署时需要补哪些监控配置?私有化部署不能只依赖 Dify 页面里的应用统计,还要补容器、队列、数据库、Redis、对象存储和模型网关的监控。操作上至少要采集服务存活、CPU、内存、磁盘、接口耗时、错误日志,并设置告警阈值,例如错误率连续 5 分钟升高、队列积压、数据库连接耗尽。日志最好集中到 ELK、Loki 或云日志服务,方便按 requestid、appid、user_id 串联一次请求。边界是 Dify 自身能暴露应用视角,但平台级容量规划仍要靠基础设施监控,否则页面看起来只是“应用慢”,实际可能是宿主机磁盘打满。
服务端阅读 05月31日 17:12

Dify 插件系统如何工作?开发插件时要注意哪些边界?

Dify 插件系统的作用,是把模型、工具、数据源和外部服务封装成可安装、可配置、可复用的能力,而不是把所有逻辑写死在某个工作流节点里。对开发者来说,插件至少包含三件事:声明自己提供什么能力,定义用户需要填写哪些参数,在运行时代码里执行调用并返回结构化结果。这样同一个搜索、工单、数据库或消息推送能力,可以被多个应用复用。插件由清单、定义和运行时代码组成插件清单描述名称、版本、作者、图标、权限和配置项;工具定义描述参数类型、是否必填、前端如何展示;运行时代码负责调用外部 API、处理文件或返回模型结果。以工具插件为例,如果要接入内部工单系统,可以把“查询工单”“创建工单”“追加评论”拆成不同工具,而不是让每个工作流都手写 HTTP 请求。identity: name: ticket_query author: platform-team label: zh_Hans: 查询工单parameters: - name: ticket_id type: string required: true label: zh_Hans: 工单 ID运行时代码要处理异常和敏感字段插件调用外部服务时,不能把所有失败都包装成“系统错误”。参数缺失、鉴权失败、限流、超时、业务数据不存在,对用户的提示和重试策略都不一样。返回结果也要裁剪,模型只需要完成任务所需字段,不应该拿到 token、手机号、内部备注等敏感信息。from dify_plugin import Toolclass TicketQueryTool(Tool): def _invoke(self, tool_parameters): ticket_id = tool_parameters.get("ticket_id") if not ticket_id: yield self.create_text_message("ticket_id 不能为空") return data = self.session.get(f"/tickets/{ticket_id}", timeout=8).json() yield self.create_json_message({ "id": data["id"], "status": data["status"], "summary": data.get("summary", "") })使用插件要控制权限和耗时能查客户资料、发消息、执行 SQL 的插件,本质上都是可被模型间接触发的操作入口。凭证要放在平台安全配置里,scope 尽量小,高风险动作最好增加人工确认。性能上也要设置超时、缓存和输入大小限制;几分钟的离线任务不适合同步插件调用,否则用户体验和排错都会很差。插件发布前还要准备最小可用示例和失败样例。前者帮助使用者快速验证凭证和参数,后者能说明哪些错误需要用户处理、哪些应该找平台排查。没有这些样例,插件一旦进入多个工作流,维护者很难判断问题来自配置、外部 API 还是插件代码。插件版本升级时也要说明兼容性,尤其是参数名、返回字段和权限 scope 的变化。否则旧工作流可能还能运行,但模型收到的数据结构已经变了。追问Dify 插件和普通工作流节点有什么区别?普通节点更像一次具体编排,插件是可复用能力封装。插件能统一鉴权、参数校验、错误处理和返回格式,适合多个应用复用。取舍是一次性小需求用 HTTP 节点更快,长期能力或敏感凭证更适合做插件。工具插件、模型插件、数据源插件怎么选?调用外部 API 或执行动作,通常做工具插件。接入新的 LLM、Embedding 或重排序服务,应做模型插件;接入外部知识库、文档系统或数据库检索,更适合数据源插件。边界是不要用工具插件硬模拟模型,也不要让数据源插件承担复杂写操作。插件凭证应该怎么管理?凭证不要写进代码、提示词或工作流变量,应放在插件配置或平台安全存储中。多租户环境还要区分 workspace 级和用户级凭证。踩坑最多的是把测试 token 打进插件包,发布后所有环境都误用同一份凭证。插件返回给模型的数据要裁剪吗?要裁剪。返回太多会浪费 token,也可能暴露敏感信息;模型通常只需要状态、摘要、原因和可追踪 ID。边界是不能裁剪到不可解释,否则用户和开发者都不知道插件为什么给出这个结果。
前端阅读 05月31日 17:12

Garfish 沙箱隔离如何实现?快照沙箱和 Proxy 沙箱怎么选?

Garfish 沙箱隔离主要解决子应用污染全局环境的问题。微前端里,每个子应用都可能写 window、注册事件、启动定时器、插入样式或改写全局对象;如果没有隔离,A 应用卸载后的副作用会影响 B 应用。Garfish 的思路是运行时拦截全局访问,卸载时清理可追踪副作用,但它不是安全容器,不能替代鉴权、CSP 和代码审查。快照沙箱:成本低,但更像事后恢复快照沙箱会在子应用挂载前记录一份全局环境,运行后对比变化,卸载时恢复。它的好处是理解简单,对旧浏览器更友好,也适合一次只激活一个子应用的老系统改造。边界是它不阻止运行期间的污染;如果多个子应用同时活跃,某个应用写入的全局变量可能已经被另一个应用读到了。Proxy 沙箱:隔离更强,适合多实例Proxy 沙箱给子应用提供代理后的全局对象。写入 window.foo 时,数据优先落在子应用自己的记录里;读取时,再按规则从沙箱或真实 window 中取。它更适合现代浏览器和多个子应用同时存在的场景。function createSandbox(rawWindow) { const fakeWindow = Object.create(null) return new Proxy(fakeWindow, { get(target, key) { return key in target ? target[key] : rawWindow[key] }, set(target, key, value) { target[key] = value return true } })}真实实现还要处理 this 指向、不可配置属性、白名单变量和动态脚本执行上下文。踩坑点是某些第三方库强依赖真实 window,Proxy 下会出现边界行为,需要白名单透传或改为主应用共享依赖。严格隔离还要清理副作用只隔离变量不够。事件监听、定时器、WebSocket、全局弹层和样式标签都可能留下副作用。子应用应在生命周期中主动释放资源,不要完全依赖框架兜底。let timerexport function mount() { timer = window.setInterval(refresh, 5000) window.addEventListener('resize', handleResize)}export function unmount() { clearInterval(timer) window.removeEventListener('resize', handleResize)}还有一个容易忽略的边界是异步任务。子应用卸载后,之前发出的请求可能仍会回调并修改 DOM 或状态,因此要用 AbortController、请求序号或 mounted 标记做保护。否则用户快速切换路由时,会出现旧页面数据覆盖新页面的诡异问题。这类问题不一定被沙箱捕获,因为请求回调属于业务逻辑。框架能帮你隔离全局副作用,但无法判断哪个接口结果已经过期。追问快照沙箱和 Proxy 沙箱怎么选?只运行一个子应用、还要兼容旧环境时,快照沙箱更容易落地。多个子应用同时运行,或团队希望更强隔离时,应优先考虑 Proxy 沙箱。取舍点是兼容性、隔离强度和第三方库适配成本。沙箱能保证子应用安全吗?不能。Garfish 沙箱主要防止应用间副作用污染,不是浏览器级安全边界。恶意代码如果能执行,仍可能发请求、读可访问数据或操作 DOM,所以敏感能力必须靠权限、CSP 和后端校验控制。为什么卸载后还有内存泄漏?因为泄漏常来自业务自己创建的定时器、订阅、闭包引用和未关闭连接。沙箱能记录一部分全局副作用,但无法理解每个业务异步任务的意图。排查时可以反复进入和离开子应用,观察 listener、timer、DOM 节点数量是否持续上涨。第三方库在沙箱里异常怎么办?先确认它是否依赖真实 window、全局单例或不可配置属性。能改配置就让它使用子应用容器,不能改时再考虑白名单透传。不要因为一个库异常就整体关闭沙箱,否则隔离边界会被打穿。
前端阅读 05月31日 17:12

Garfish 主子应用如何通信?状态共享有哪些边界和坑?

Garfish 主子应用通信不要一开始就设计成“大而全的全局 store”。更稳的做法是按数据生命周期拆分:挂载时需要的上下文用 props,跨应用通知用事件总线,长期共享的登录态、主题、权限用平台服务封装。这样子应用仍能独立开发和部署,主应用也不会变成所有业务状态的垃圾桶。props 传稳定上下文props 适合传用户信息读取函数、API 前缀、路由 basename、权限快照和平台服务入口。它简单、可测试,子应用挂载时就能拿到,不需要等待异步事件。边界是 props 不适合高频变化数据,比如未读数、协同编辑状态或实时价格;这些数据用 props 传,会让刷新和同步逻辑很难维护。Garfish.run({ apps: [{ name: 'order', entry: 'https://cdn.example.com/order/', activeWhen: '/order', props: { basename: '/order', services: { auth: { getToken: () => auth.getToken(), logout: () => auth.logout() }, theme: { get: () => theme.current } } } }]})这里传函数比传固定 token 更安全。登录刷新后,子应用再次调用 getToken 能拿到新值,不会继续使用旧凭证。事件总线传“发生了什么”事件适合通知主题切换、登录过期、订单创建成功、侧边栏收起这类动作。它的优点是解耦,一对多传播很方便。踩坑点是事件不是状态存储,晚挂载的子应用可能错过历史事件,所以关键状态仍要能主动读取。export const platformBus = { emitTheme(theme) { Garfish.channel.emit('platform:theme-change', { theme }) }, onTheme(handler) { Garfish.channel.on('platform:theme-change', handler) return () => Garfish.channel.off('platform:theme-change', handler) }}每次订阅都要在卸载时取消,否则重复进入子应用后会出现多次触发,看起来像接口重复请求,实际是监听器泄漏。共享状态要少而稳定真正值得共享的状态通常只有登录、权限、主题、语言、租户和少量平台配置。表格筛选、弹窗开关、详情页临时数据应留在子应用内部。共享越多,子应用越难独立运行,灰度发布和回滚也越麻烦。通信契约最好放在单独包里维护,至少包含事件名、payload 类型和服务方法签名。这样主应用升级时,旧子应用能通过 TypeScript 或测试提前发现不兼容,而不是上线后才暴露。对于跨团队协作,契约比口头约定更可靠。如果担心类型包升级影响发布,可以让契约保持向后兼容:新增字段可以,删除字段要经过灰度期。事件名也不要随业务文案变化而变化,最好用稳定的领域语义。追问props、事件总线和共享服务怎么取舍?props 用来给子应用启动参数,事件总线用来通知变化,共享服务用来读取长期状态。判断标准是数据是否需要被晚挂载应用读取;如果需要,就不能只靠事件。实践里常见组合是 props 传服务入口,事件做变更通知,服务保存当前值。子应用之间能直接通信吗?技术上可以,但不建议作为主链路。A 应用直接依赖 B 应用的事件名,会让两个团队发布节奏绑在一起。更好的边界是由主应用定义平台事件或领域服务,子应用只依赖稳定契约。token 应该作为 props 直接传吗?短期可以,但长期更推荐传 getToken 函数或认证服务。直接传字符串容易在刷新、退出登录、切换账号后变成旧值。踩坑最多的是子应用自己缓存 token,主应用已经退出,它还在继续发请求。如何避免通信导致内存泄漏?订阅事件、WebSocket、轮询都必须在 unmount 或组件卸载钩子里释放。不要用匿名函数订阅后无法 off,也不要在 render 中重复订阅。排查重复触发时,先看监听器数量,再看接口层。
前端阅读 05月31日 17:12

Garfish 样式隔离怎么做?Shadow DOM 和 scoped CSS 如何取舍?

Garfish 的样式隔离不是把 CSS 变成“绝对安全”,而是给每个子应用划出清晰作用域,减少全局 reset、组件库类名、运行时 style 标签互相覆盖。实际落地时,最常见的是 scoped CSS、Shadow DOM 和动态样式清理三类方案。它们解决的问题不同,取舍也不同:scoped CSS 兼容性好,Shadow DOM 隔离更强,动态清理负责避免卸载后的残留污染。scoped CSS 适合多数业务系统scoped CSS 通常通过构建工具给选择器加容器前缀,例如把 .button 变成 [data-garfish-app="order"] .button。它不改变 DOM 结构和事件模型,对 Ant Design、Element Plus 这类组件库比较友好,所以中后台系统优先选它。边界是它只能处理被构建工具改写过的样式,管不住第三方库运行时直接插到 document.head 的全局样式。module.exports = { plugins: { 'postcss-prefix-selector': { prefix: '[data-garfish-app="order"]', transform(prefix, selector) { if (/^(html|body)/.test(selector)) return selector return `${prefix} ${selector}` } } }}Shadow DOM 隔离更强,但更挑场景Shadow DOM 使用浏览器原生边界,外部样式不容易影响子应用,子应用样式也不容易泄漏出去。它适合边界很清楚的独立模块,比如嵌入式配置台、低频运营后台或独立小工具。踩坑点是弹窗、Tooltip、全局 message、主题变量和 E2E 选择器都可能要额外适配;如果组件库大量依赖 document.body 挂载弹层,Shadow DOM 的维护成本会明显上升。const host = document.querySelector('#subapp')const root = host.attachShadow({ mode: 'open' })root.appendChild(document.createElement('div'))卸载清理决定长期稳定性很多线上样式问题不是首次加载发生,而是切换几次子应用后才出现。原因通常是旧应用的 <style>、<link>、弹层 DOM 没有移除。Garfish 可以管理部分资源生命周期,但子应用仍应在 unmount 中清理自己创建的节点和组件库全局实例。const links = []export function mount() { const link = document.createElement('link') link.rel = 'stylesheet' link.href = '/order/index.css' document.head.appendChild(link) links.push(link)}export function unmount() { links.splice(0).forEach(node => node.remove())}追问scoped CSS 和 Shadow DOM 应该怎么选?多数业务系统先选 scoped CSS,因为它改造成本低,和现有组件库、埋点、自动化测试的冲突少。Shadow DOM 更适合子应用边界稳定、弹层少、主题依赖少的场景。取舍点不是谁更先进,而是隔离强度和接入成本能否平衡。为什么弹窗样式经常隔离失败?很多组件库默认把 Modal、Popover、Message 挂到 document.body,它已经脱离了子应用容器。scoped CSS 的前缀匹配不到它,Shadow DOM 内部样式也覆盖不到它。解决时要统一配置弹层容器,例如 getPopupContainer,不要只修某一个组件。主应用主题变量要不要给子应用用?如果主题是平台能力,应该用 CSS 变量透传,例如 --color-primary、--font-size-base。但主应用不要直接覆盖子应用内部类名,否则会形成隐性耦合。边界是共享稳定 token,不共享具体 DOM 结构。样式隔离会影响性能吗?scoped CSS 的主要成本在构建期,运行时影响通常很小。Shadow DOM 也不是主要瓶颈,真正要注意的是重复加载组件库样式和卸载后残留节点。不要为了省一点 CSS 体积放弃隔离,否则线上排查污染更贵。
前端阅读 05月31日 17:12

Garfish 微前端加载慢该怎么优化?

Garfish 性能优化要分两段看:子应用加载前,重点是入口、资源、缓存和预加载;子应用运行后,重点是卸载清理、渲染开销、共享依赖和监控定位。不要一上来就堆 preload,真正有效的做法是先量化首屏、子应用加载耗时、资源体积和错误率,再针对瓶颈处理。微前端的性能问题经常不是 Garfish 本身慢,而是每个子应用都带一份 React、组件库和图表库,最后门户像同时打开了几个完整系统。先把加载链路量出来主应用应该记录从路由命中到子应用 mount 完成的耗时,并区分资源下载、脚本执行和渲染时间。没有数据时,优化很容易变成猜谜:有人说缓存,有人说分包,最后谁都证明不了收益。建议在 beforeLoad、afterLoad、mount 前后打点,并把应用名、版本、网络状态一起上报。边界是埋点不能阻塞加载,失败时要静默降级。const perf = new Map<string, number>();Garfish.run({ beforeLoad(app) { perf.set(app.name, performance.now()); }, afterMount(app) { const cost = performance.now() - (perf.get(app.name) || performance.now()); report('garfish_subapp_mount_cost', { app: app.name, cost }); },});资源体积和缓存怎么控?子应用要做路由级分包,首屏只加载必要 chunk,大型图表、编辑器、地图组件延后加载。静态资源使用带 hash 的文件名,HTML 或 manifest 保持短缓存,JS/CSS 走长缓存,这样既能更新入口,又能复用稳定资源。取舍是缓存越激进,发布回滚越依赖版本管理;如果没有版本化入口,用户可能拿到新 HTML 加旧 JS。常见坑是所有子应用都打包同一套依赖,导致首屏下载重复,应该通过 externals、共享依赖或构建约束处理。// nginx 示例:入口短缓存,hash 资源长缓存location /app/index.html { add_header Cache-Control "no-cache";}location ~* \.(js|css)$ { add_header Cache-Control "public, max-age=31536000, immutable";}预加载不是越多越好Garfish 可以在空闲时间预加载高概率访问的子应用,但预加载会占网络、CPU 和内存。更稳的策略是只预加载当前用户角色最可能进入的 1-2 个应用,并在弱网、低端设备或首屏未稳定时跳过。边界是移动端和海外网络下,盲目预加载可能让当前页面更慢。踩坑最多的是把所有子应用启动时一起 preload,看似后续切换快了,首屏却被拖垮。运行时性能别忽略卸载微前端页面切换频繁,如果子应用卸载不彻底,内存会慢慢涨,定时器和事件监听也会重复执行。React root、Vue app、全局事件、WebSocket、轮询、AbortController 都要在 unmount 里处理。取舍是生命周期代码会稍微啰嗦,但比线上定位“偶发卡顿”省事。边界是有些全局资源本来就该复用,比如登录态和主题,不要为了清理把共享能力也销毁掉。追问Garfish 加载慢时先查什么?先查子应用入口是否慢、资源是否过大、是否重复下载公共依赖,再看脚本执行和渲染耗时。不要一开始就怀疑框架,因为很多慢是构建和资源策略造成的。取舍是网络层优化见效快,但如果 JS 执行太重,缓存再好也解决不了卡顿。边界是本地开发环境的耗时不能代表生产,需要看真实用户监控。预加载应该按什么规则开启?按访问概率、用户角色和设备条件来开。比如用户进入工作台后,大概率访问报表,就可以在浏览器空闲时预加载报表子应用。取舍是提前消耗资源换切换速度,适合高频路径,不适合所有路径。踩坑是忽略弱网和低端设备,导致当前页面交互被预加载拖慢。共享依赖一定能提升性能吗?不一定。共享 React、Vue、组件库可以减少重复下载,但会增加版本协调成本。取舍是稳定基础依赖适合共享,业务库或变化频繁的包不一定适合。边界是多个子应用依赖不同大版本时,强行共享可能引入兼容问题,比重复下载更危险。如何避免切换页面后越来越卡?重点检查 unmount 是否清理事件、定时器、订阅、WebSocket 和未完成请求。可以用 Performance 和 Memory 面板反复切换路由,看 DOM 节点和监听器是否持续增长。取舍是清理逻辑需要统一封装,否则每个子应用各写各的容易漏。常见坑是只卸载组件树,忘了组件外创建的全局副作用。性能预算怎么定才合理?不要拍脑袋定一个绝对值,要按业务场景、用户网络和设备分层。比如后台门户可以容忍首次进入稍慢,但应用内切换应该稳定;高频运营页面则首屏资源要更克制。取舍是预算太严会拖慢开发,太松又没有约束力。建议把入口 HTML、首屏 JS、子应用 mount 耗时和错误率纳入流水线或发布看板。
前端阅读 05月31日 17:12

Garfish、qiankun 和 single-spa 该怎么选?

Garfish、qiankun 和 single-spa 都能做微前端,但它们解决问题的层级不同。single-spa 更像底座,只管应用注册、激活和生命周期;qiankun 在它之上补齐沙箱、资源加载和工程实践;Garfish 也提供开箱能力,同时更强调运行时加载、插件化和相对轻量的接入体验。选型时别只看“谁更先进”,要看团队是否需要高度定制、是否已有历史应用、是否能承担框架生态和运维成本。三者核心差异是什么?如果团队刚开始做微前端,single-spa 的自由度最高,但很多事情要自己补,比如资源加载、样式隔离、全局变量污染处理和错误兜底。qiankun 的优势是成熟,资料多,历史项目验证多,适合需要稳定社区经验的团队。Garfish 的优势在于配置更直接,插件机制和应用调度能力比较顺手,适合希望快速把多个独立应用纳入同一门户的场景。取舍也很明显:生态越成熟,约束和历史包袱越多;框架越轻,团队自己补规范的责任越大。| 维度 | Garfish | qiankun | single-spa ||---|---|---|---|| 定位 | 开箱式微前端框架 | 成熟微前端方案 | 生命周期底座 || 接入成本 | 中低 | 中 | 高 || 自定义空间 | 较高 | 中 | 最高 || 沙箱与隔离 | 内置能力较完整 | 方案成熟 | 需自行实现 || 社区资料 | 中等 | 较多 | 国际化资料多 |Garfish 的优势在哪里?Garfish 对主应用注册、子应用加载、生命周期和沙箱做了较完整封装,配置量比裸用 single-spa 少。对于已有多个 React、Vue 或普通 SPA 的团队,它能让子应用在较少改造下接入。另一个优势是运行时灵活性,入口可以按环境、租户、版本动态切换,这对灰度发布有帮助。边界是框架不能替你解决组织问题,如果应用间契约、样式规范和发布流程没定好,换任何框架都会乱。Garfish.run({ apps: [{ name: 'crm', entry: window.__entries__.crm, activeWhen: '/crm', props: { tenantId: getTenantId() }, }], plugins: [monitorPlugin(), authFallbackPlugin()],});什么时候不适合选 Garfish?如果团队已经深度使用 qiankun,且现有沙箱、构建、监控和发布链路都稳定,迁移到 Garfish 未必划算。迁移收益必须覆盖测试、适配和培训成本。另一个边界是如果你只是想共享几个组件或工具函数,Module Federation 或普通 npm 包可能更合适,没必要上完整微前端。常见踩坑是把微前端当成性能优化手段,结果增加了运行时加载和治理成本。追问Garfish 和 qiankun 最大的选择差异是什么?差异不只是 API,而是生态成熟度和团队治理方式。qiankun 的资料、案例和踩坑经验更多,Garfish 的接入体验和扩展方式更轻。取舍是前者更稳妥,后者在新项目里更容易按自己的平台规则搭起来。边界是如果公司已有 qiankun 基建,除非有明确痛点,否则不要为了换框架而换框架。single-spa 为什么还值得了解?single-spa 是很多微前端方案背后的生命周期模型,理解它能帮助你判断框架到底替你做了什么。直接使用它适合架构团队很强、需要完全定制加载和隔离策略的项目。取舍是自由度换来了工程成本,业务团队可能会被大量底层细节拖住。踩坑点是只接了生命周期,却忘了资源隔离和错误恢复,线上问题会很难定位。Module Federation 能替代 Garfish 吗?它们关注点不同。Module Federation 更适合模块级共享,比如多个应用共用组件、工具库或业务模块;Garfish 更适合应用级集成,比如把多个独立 SPA 放到同一个门户下。取舍是 Federation 对构建体系要求更高,Garfish 的运行时边界更清楚。边界是两者可以组合使用,但要管好依赖版本,否则共享依赖会变成隐形耦合。老项目迁移到 Garfish 应该怎么做?不要一次性全迁,先挑一个边界清晰、依赖少、访问量可控的子应用试点。迁移时先保证独立运行,再接入主应用,最后补监控和灰度。取舍是分阶段会拉长周期,但能把风险压住。常见坑是只改入口和生命周期,不处理全局样式、定时器和跨应用跳转,结果测试环境正常、生产环境互相污染。面试或评审里怎么回答选型问题?先讲业务约束,再讲框架能力,不要直接说某个框架更好。比如团队是否多技术栈、是否要独立发布、是否已有基建、是否要求强隔离。边界是微前端不是默认答案,小团队单体 SPA 可能更简单。能说清这些取舍,比背一张对比表更有说服力。
前端阅读 05月31日 17:12

Garfish 实际项目怎么落地才不容易失控?

Garfish 落地微前端,关键不是把子应用跑起来,而是先把主应用边界、子应用生命周期、发布规则和故障兜底定清楚。主应用只做平台能力:注册应用、路由分发、登录态、主题、全局错误处理和监控埋点;业务逻辑尽量留在子应用里。这样做的取舍是,前期规范会多一点,但后续团队扩张、独立发布、灰度回滚都会轻很多。主应用应该管什么?主应用最好保持“薄壳”角色,不要把订单、用户、报表这类业务判断塞进去。它可以统一提供 userInfo、权限、语言、主题、接口前缀和埋点方法,再通过 props 传给子应用。边界要写清:主应用负责“能不能进入”和“挂在哪里”,子应用负责“进去以后怎么展示”。一个常见坑是主应用为了方便到处暴露全局对象,最后每个子应用都依赖它,独立运行和本地调试会很痛苦。import Garfish from 'garfish';Garfish.run({ basename: '/', apps: [{ name: 'user-center', entry: process.env.USER_CENTER_ENTRY!, activeWhen: '/user', props: { getUser: () => window.__portal_user__, track: (event: string, data?: unknown) => sendLog(event, data), }, }], sandbox: { snapshot: true, fixBaseUrl: true },});子应用生命周期怎么写才稳?子应用要保证 mount 可重复执行,unmount 能把 DOM、事件、定时器、请求订阅清干净。React、Vue、Svelte 都可以接入,但不要假设自己永远运行在完整页面里。实际项目里最容易踩坑的是全局事件和轮询任务:页面切走后还在请求接口,用户再回来就出现重复订阅。建议把清理函数集中放到一个数组里,卸载时统一执行,避免遗漏。const disposers: Array<() => void> = [];export async function mount({ dom, props }) { const onResize = () => props.track('resize'); window.addEventListener('resize', onResize); disposers.push(() => window.removeEventListener('resize', onResize)); root = createRoot(dom.querySelector('#root')!); root.render(<App user={props.getUser()} />);}export async function unmount() { root?.unmount(); disposers.splice(0).forEach(fn => fn());}发布和回滚要提前约定每个子应用独立发布,但入口地址必须可控,最好通过环境配置或版本化 manifest 管理。直接把生产 entry 写死在主应用里,看起来简单,回滚时却要重新发主应用。更稳的方式是主应用读取配置中心,子应用发布后只更新版本指针。边界是配置中心也要有缓存和降级,否则它挂了会导致所有子应用加载失败。发布前还要约定资产命名、CDN 缓存、灰度比例和回滚触发条件。比如新版本错误率超过阈值时,先切回旧 entry,再排查子应用代码,而不是让主应用临时加兼容逻辑。追问主应用为什么不能承载太多业务逻辑?因为微前端的收益来自团队和发布边界,而不是页面拆分本身。主应用一旦包含大量业务判断,子应用每次变更都要等主应用配合,独立发布就失效了。取舍是主应用要多做一些平台抽象,但这比后期拆耦成本低得多。踩坑点是“临时加一个判断”很容易变成长期依赖,所以要把例外也纳入评审。子应用是否必须能独立运行?最好能独立运行,至少本地开发和基础页面渲染不应依赖主应用。独立运行会增加一层 mock props 或本地启动配置,但可以显著降低联调成本。边界是登录、权限、埋点这类平台能力可以用模拟实现,不必在本地完整复刻。常见坑是子应用直接读取主应用全局变量,离开主应用就白屏。团队之间怎么约定通信方式?优先用 props 传稳定能力,用事件总线传一次性通知,少用共享可变状态。共享状态看起来方便,但边界不清时会让数据来源变得混乱。取舍是事件会让调用链不如直接函数清晰,所以事件名、payload 类型和取消订阅规则要写进规范。踩坑最多的是忘记 off,导致重复弹窗、重复请求或内存泄漏。样式隔离应该一开始就上 Shadow DOM 吗?不一定。后台类系统用 CSS Modules、BEM 前缀或 scoped CSS 往往够用,Shadow DOM 更适合冲突严重、第三方样式复杂的场景。它的边界是弹窗、下拉、主题变量和全局字体可能需要额外适配。取舍是隔离越强,跨应用统一视觉和调试成本也越高。接入 Garfish 后如何做质量验收?验收不只看页面能否打开,还要测路由切换、重复进入、卸载清理、异常加载和灰度回滚。建议 E2E 覆盖跨应用主流程,单测覆盖生命周期函数。边界是不要把所有子应用组合都测一遍,那会拖慢流水线;更实际的是主链路必测、风险应用加测。常见坑是只测首次加载,忽略第二次进入才暴露的重复订阅问题。
服务端阅读 05月31日 16:34

Kubernetes Pod 是什么?生命周期和重启策略怎么理解?

Pod 是 Kubernetes 里最小的可调度单元,不是容器本身。一个 Pod 可以包含一个或多个容器,这些容器共享网络命名空间、存储卷和生命周期,被调度到同一个节点上运行。大多数业务场景是一个 Pod 一个主容器;只有当多个容器必须紧密协作、共享 localhost 或共享文件时,才适合放进同一个 Pod。理解 Pod 的重点是:它是一次运行实例,天然会被替换,不应该把它当成固定机器来使用。Pod 和容器是什么关系容器负责运行进程,Pod 负责把一组容器包装成 Kubernetes 能调度和管理的对象。同一个 Pod 内的容器共享同一个 IP,可以通过 localhost 通信,也可以挂载同一个 volume 交换文件。取舍是协作方便,但扩缩容粒度也被绑在一起:一个 Sidecar 占用资源过高,会影响主容器;主容器需要扩容时,Sidecar 也会跟着复制。不要把数据库、后端、前端这类生命周期不同的组件塞进一个 Pod。apiVersion: v1kind: Podmetadata: name: web labels: app: webspec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: memory: 256MiPod 生命周期有哪些阶段Pod 常见 phase 有 Pending、Running、Succeeded、Failed 和 Unknown。Pending 表示对象已经创建,但还没成功运行,原因可能是调度失败、镜像拉取慢、PVC 没绑定或 init container 还没完成。Running 只表示 Pod 已经绑定节点并且至少一个容器在运行,不等于业务已可接流量。Succeeded 和 Failed 常见于 Job,Unknown 多和节点失联或 kubelet 状态无法上报有关。kubectl get pod web -o widekubectl describe pod webkubectl get events --sort-by=.metadata.creationTimestamp重启策略怎么选Pod 的 restartPolicy 有 Always、OnFailure、Never。Deployment、DaemonSet、StatefulSet 这类长期服务通常只能用 Always,因为控制器期望服务持续运行。Job 常用 OnFailure,失败时让容器重跑,成功退出就不再重启。Never 适合一次性调试或希望失败状态被保留下来的任务。边界是 restartPolicy 只管 Pod 内容器重启,不等于控制器是否会重新创建 Pod;Deployment 即使容器一直崩,ReplicaSet 仍会努力维持副本数。apiVersion: batch/v1kind: Jobmetadata: name: data-migratespec: template: spec: restartPolicy: OnFailure containers: - name: migrate image: busybox command: ["sh", "-c", "./migrate.sh"] backoffLimit: 3探针和生命周期钩子别混淆livenessProbe 用来判断容器是否需要重启,readinessProbe 用来判断 Pod 是否可以加入 Service 后端,startupProbe 用来保护启动慢的应用。常见踩坑是把 readiness 写得太宽,应用还没连上数据库就接流量;或者把 liveness 写得太激进,启动高峰时被反复杀掉。生命周期钩子如 preStop 更适合优雅下线,让应用先停止接新请求,再等待连接处理完。readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 10livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 30 periodSeconds: 10为什么不建议直接管理裸 Pod裸 Pod 删除后不会自动恢复,节点故障时也缺少更高层控制器帮你维持副本。生产服务通常应该用 Deployment、StatefulSet、DaemonSet 或 Job 创建 Pod。Deployment 适合无状态服务,StatefulSet 适合有稳定身份和存储的服务,DaemonSet 适合每个节点一个代理,Job 适合一次性任务。直接写 Pod YAML 可以用于学习和临时排查,但不适合作为长期交付方式。追问Pod 为什么不是直接等于容器?Kubernetes 需要一个比容器更高的抽象来表达共享网络、共享卷、调度和生命周期。Pod 内多个容器可以通过 localhost 通信,这对 Sidecar、日志代理、服务网格代理很有用。取舍是这些容器会被一起调度、一起扩缩容,不能独立水平扩展。只有强协作关系才放同一个 Pod,否则应该拆成不同工作负载。Pending 一定是镜像拉不下来吗?不一定。Pending 可能是调度阶段就失败了,比如资源 request 太高、节点污点没有容忍、亲和性规则过硬、PVC 未绑定,也可能是镜像拉取或 init container 阶段卡住。最可靠的方式是看 kubectl describe pod 里的 Events。踩坑点是只看 Pod phase 会误导判断,真正原因通常写在 Warning 事件里。Running 是否代表服务已经可用?不代表。Running 只说明容器层面已启动,业务可能还在加载配置、连接数据库或预热缓存。Service 是否转发流量主要看 readinessProbe 和 EndpointSlice,而不是只看 Pod phase。边界是没配置 readinessProbe 时,Kubernetes 可能过早把 Pod 加入后端。滚动发布中这会导致短暂 502 或请求超时。Always、OnFailure、Never 应该怎么选?长期服务用 Always,失败了就重启,配合 Deployment 等控制器维持副本。批处理任务通常用 OnFailure,让非零退出码触发重试,成功退出后保持完成状态。Never 适合调试或希望失败现场保留下来的任务。不要在 Deployment 里幻想用 OnFailure 管服务生命周期,这不符合控制器约束,也会让行为变得难以预测。Pod 被删除后数据会不会还在?要看数据放在哪里。容器文件系统和 emptyDir 通常会随 Pod 消失,PVC 挂载的持久卷则可以跨 Pod 保留。取舍是持久化能保护数据,但也引入存储绑定、访问模式、扩容和回收策略问题。生产里不要把重要数据写在容器本地路径,除非明确接受 Pod 重建后数据丢失。
服务端阅读 05月31日 16:34

Kubernetes Service 有什么作用?ClusterIP、NodePort 和 LoadBalancer 怎么选?

Kubernetes Service 的作用,是给一组会变化的 Pod 提供一个稳定访问入口。Pod IP 会随着重建、扩缩容、滚动发布而变化,客户端如果直接访问 Pod,很快就会遇到地址失效和负载不均的问题。Service 用 selector 找到后端 Pod,通过 EndpointSlice 记录真实后端,再由 kube-proxy 或 CNI 数据面把流量转发过去。理解 Service 的关键不是背类型,而是知道不同类型解决的是“集群内访问、节点端口暴露、云负载均衡、外部域名映射”这几类问题。Service 如何找到后端 Pod最常见的 Service 通过 selector 匹配 Pod 标签。只要 Pod 上有 app: web,它就会被加入 Service 对应的 EndpointSlice。Deployment 滚动更新时,新旧 Pod 会动态进出后端列表,Service 的 ClusterIP 和 DNS 名称保持不变。踩坑点是 selector 写错时 Service 仍然存在,但没有后端,访问表现通常是连接失败或超时。apiVersion: v1kind: Servicemetadata: name: webspec: type: ClusterIP selector: app: web ports: - name: http port: 80 targetPort: 8080---apiVersion: apps/v1kind: Deploymentmetadata: name: webspec: replicas: 3 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: web image: nginx ports: - containerPort: 8080kubectl get svc webkubectl get endpointslice -l kubernetes.io/service-name=webkubectl describe svc web四种 Service 类型怎么选ClusterIP 是默认类型,只能在集群内部访问,适合微服务之间互调、内部数据库代理、队列服务等。它安全、简单,也最常用。边界是集群外不能直接访问,除非配合 Ingress、Gateway、端口转发或 VPN。NodePort 会在每个节点上打开一个端口,外部可以通过 NodeIP:NodePort 访问。它适合临时测试、裸金属环境接入外部负载均衡器,默认端口范围通常是 30000-32767。取舍是暴露面更大,而且节点 IP 变化、端口冲突、安全组放行都要自己管,生产 HTTP 服务一般不直接把 NodePort 当最终入口。LoadBalancer 会请求云厂商创建外部负载均衡器,再把流量转到 Service 后端。它适合云上对外暴露 TCP/UDP 服务,使用体验最好。边界是依赖 cloud-controller-manager 和云平台权限,裸金属集群默认不会自动得到外部 LB,通常要配 MetalLB 或云厂商插件。ExternalName 不代理流量,只返回一个 CNAME,把集群内服务名映射到外部 DNS 名称。它适合把外部数据库、第三方 API 用统一的集群内域名表达出来。踩坑点是它没有 ClusterIP,也没有端口转发和健康检查能力,排障时不要去找 Endpoint。apiVersion: v1kind: Servicemetadata: name: external-dbspec: type: ExternalName externalName: db.example.comkube-proxy 在中间做什么kube-proxy 监听 Service 和 EndpointSlice 变化,在节点上维护转发规则。iptables 模式简单稳定,但规则很多时更新成本会上升;IPVS 模式适合更大规模,支持更丰富的负载均衡算法。现在不少 CNI 也会用 eBPF 接管 Service 数据面,这时 kube-proxy 可能被替代。边界要看集群实现,不能只凭“Service 不通”就断定 kube-proxy 有问题。Headless Service 适合什么场景Headless Service 设置 clusterIP: None,不会分配虚拟 IP,而是让 DNS 直接返回后端 Pod 地址。它常用于 StatefulSet,比如数据库、消息队列这类需要稳定 Pod 身份的应用。取舍是客户端要能处理多个后端地址和连接策略,不能再完全依赖 Service 做统一负载均衡。apiVersion: v1kind: Servicemetadata: name: mysqlspec: clusterIP: None selector: app: mysql ports: - port: 3306追问ClusterIP、NodePort、LoadBalancer 的关系是什么?LoadBalancer 通常会包含 NodePort,NodePort 又建立在 ClusterIP 之上,所以它们不是完全割裂的三套机制。ClusterIP 解决集群内稳定访问,NodePort 把入口扩到节点端口,LoadBalancer 再借助云厂商或外部 LB 提供公网或内网入口。取舍是越往外暴露,运维边界越大,安全组、证书、源地址保留和费用都要考虑。内部服务不要为了“方便”直接用 LoadBalancer。Service selector 写错会发生什么?Service 对象会创建成功,DNS 也可能正常解析,但 EndpointSlice 为空。客户端访问时通常表现为连接被拒绝、超时或没有可用后端。排查时先执行 kubectl get endpointslice -l kubernetes.io/service-name=<svc>,再核对 Pod 标签和 Service selector。这个坑很常见,因为 YAML 校验不会知道你的业务标签是否写对。什么时候用 Ingress,而不是直接用 LoadBalancer?如果是 HTTP/HTTPS,多服务共享域名、路径路由、TLS 证书和灰度规则,用 Ingress 或 Gateway API 通常更合适。每个 Service 都建一个 LoadBalancer 简单直接,但成本高、入口分散,也不好统一做证书和访问控制。边界是非 HTTP 协议不一定适合 Ingress,需要看控制器是否支持 TCP/UDP 转发。生产里常见做法是一个 Ingress Controller 前面挂一个 LoadBalancer。sessionAffinity 能解决所有会话保持问题吗?不能。sessionAffinity: ClientIP 只能按客户端 IP 做相对简单的粘性会话,NAT、代理和移动网络会让多个用户看起来来自同一个 IP。它也不理解应用层登录态,Pod 重启后会话仍可能丢失。更稳的做法是把会话放到 Redis、数据库或外部状态存储里。Service 层会话保持可以作为补充,不应该成为唯一依赖。Service 不通时优先排查哪几步?先查 Service 是否有 ClusterIP 和端口,再查 EndpointSlice 是否有后端地址。然后进同命名空间 Pod 里用 curl 或 nc 测 Service DNS、ClusterIP 和 Pod IP,区分是 DNS、Service 转发还是应用端口问题。NodePort 或 LoadBalancer 不通时,还要检查节点安全组、云负载均衡器健康检查和 externalTrafficPolicy。不要只看 Service YAML,真正的线索通常在 EndpointSlice、事件和后端 Pod readiness。
服务端阅读 05月31日 16:34

Kubernetes 控制平面由哪些组件组成?它们如何协同工作?

Kubernetes 控制平面是集群的决策层,负责接收请求、保存状态、做调度决策,并不断把实际状态拉回到期望状态。它通常由 kube-apiserver、etcd、kube-scheduler、kube-controller-manager 和 cloud-controller-manager 组成。简单说,API Server 是入口,etcd 是账本,Scheduler 负责把 Pod 放到合适节点,Controller Manager 负责持续纠偏,Cloud Controller Manager 负责和云厂商资源打交道。真正排障时,不要把它们看成一组静态组件,而要看一次资源变更如何流过这些组件。控制平面的核心组件kube-apiserverkube-apiserver 是所有请求的统一入口,kubectl、控制器、调度器、Webhook 和外部系统都通过它读写集群资源。它负责认证、授权、准入控制、对象校验和 API 聚合,最后再把合法状态写入 etcd。API Server 本身是无状态组件,所以可以部署多个副本放在负载均衡器后面。边界是:它不直接创建容器,也不负责调度,只负责让状态变更有统一入口。kubectl get --raw='/readyz?verbose'kubectl get pods -n kube-system -l component=kube-apiserveretcdetcd 保存 Kubernetes 的全部关键状态,包括 Pod、Deployment、Service、Secret、ConfigMap、Node 状态和 Lease。它基于 Raft 保证一致性,常见生产部署是 3 或 5 个成员。取舍很明确:etcd 强一致带来可靠状态,但对磁盘延迟、网络抖动非常敏感。踩坑最多的是只备份了业务数据,忘了备份 etcd;一旦控制平面故障,集群对象就很难恢复。ETCDCTL_API=3 etcdctl snapshot save /backup/etcd.db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.keykube-schedulerkube-scheduler 监听还没有绑定节点的 Pod,先过滤掉不满足资源、亲和性、污点容忍、端口、卷拓扑等条件的节点,再给剩余节点打分,最后把绑定结果写回 API Server。它不会启动 Pod,真正启动容器的是工作节点上的 kubelet。一个常见误区是以为 Pod Pending 一定是调度器坏了,实际更多是 CPU/内存 request 太高、PVC 未绑定、节点污点没容忍或亲和性规则写得过硬。kube-controller-managerkube-controller-manager 运行一组控制器,例如 Deployment、ReplicaSet、Node、Job、CronJob、EndpointSlice、ServiceAccount 等控制器。控制器的模式是 watch 资源变化,比较期望状态和实际状态,然后发起下一步变更。比如 Deployment 期望 3 个副本,实际只有 2 个,ReplicaSet 控制器会补一个 Pod;Node 长时间失联,Node Controller 会更新状态并触发后续驱逐。它的价值在“持续调和”,不是执行一次就结束。cloud-controller-managercloud-controller-manager 把云厂商相关逻辑从 Kubernetes 核心里拆出来,负责云节点生命周期、LoadBalancer、路由和云盘等资源。自建裸金属集群可能没有它,云上集群通常离不开它。边界要分清:Service type=LoadBalancer 创建不出外部负载均衡时,问题经常不在 kube-proxy,而在云控制器权限、配额、子网标签或安全组配置。apiVersion: v1kind: Servicemetadata: name: webspec: type: LoadBalancer selector: app: web ports: - port: 80 targetPort: 8080一次创建 Pod 会发生什么用户执行 kubectl apply 后,请求先到 API Server,经过认证、授权和准入控制,再写入 etcd。Scheduler 看到这个 Pod 没有 spec.nodeName,开始选择节点并写入绑定结果。目标节点上的 kubelet watch 到属于自己的 Pod 后,通过容器运行时拉镜像、创建容器、挂载卷并上报状态。控制器同时观察这些状态,如果副本数不足、节点异常或对象被删除,就继续发起新的调和动作。kubectl apply -f pod.yamlkubectl get pod demo -o widekubectl describe pod demokubectl get events --sort-by=.metadata.creationTimestamp追问API Server 为什么要设计成无状态?无状态的好处是可以水平扩展,多个 API Server 实例后面挂一个负载均衡器即可提升可用性。状态统一放在 etcd,避免每个 API Server 各自保存一份数据导致一致性问题。取舍是 API Server 对 etcd 的依赖非常强,etcd 慢了,整个集群的读写都会跟着慢。生产环境里经常把 API Server 扩了很多副本,却忽略了 etcd 磁盘延迟,这类扩容收益很有限。etcd 为什么通常部署奇数个节点?etcd 使用 Raft,多数派可用才能提交写入,所以 3 节点能容忍 1 个故障,5 节点能容忍 2 个故障。偶数节点并不会提高多数派容错能力,比如 4 节点仍只能容忍 1 个故障,反而增加同步成本。边界是成员越多写入延迟越高,不是越多越安全。小中型集群用 3 个 etcd 成员通常比盲目堆到 7 个更稳。Scheduler 和 Controller Manager 都在“控制状态”,区别是什么?Scheduler 只解决“这个未调度 Pod 放到哪个 Node”这个决策问题,核心输出是绑定关系。Controller Manager 负责更广的持续调和,比如副本数、节点状态、EndpointSlice、Job 完成情况等。踩坑点是排查 Pending Pod 时先看调度事件,排查副本补不齐时再看控制器和 ReplicaSet 事件。把两者混在一起,容易误判问题方向。控制平面高可用是不是只要多部署几个 master?不是。高可用至少要考虑 API Server 多副本、etcd 多成员、Controller Manager 和 Scheduler 的 leader election、前置负载均衡器以及证书和备份。只加 master 节点但 etcd 仍是单点,故障时集群状态照样不可写。另一个边界是多副本不等于无限可用,错误的准入 Webhook、过期证书或慢 etcd 仍可能让整个控制面卡住。控制平面异常时应该先看什么?先看 API Server 的 readyz、etcd 健康和 kube-system 里控制平面 Pod 日志,再看事件和证书有效期。不要一上来重启所有组件,尤其是 etcd 抖动时,频繁重启会放大选主和恢复时间。命令层面可以从 kubectl get --raw=/readyz?verbose、kubectl get componentstatuses(旧集群可用)和 journalctl -u kubelet 开始。能访问节点但访问不了 API Server 时,还要检查负载均衡器、6443 端口和控制平面证书。
服务端阅读 05月31日 16:34

Kubernetes 工作节点包含哪些组件?各自负责什么?

Kubernetes 工作节点是实际运行 Pod 的机器,核心组件通常包括 kubelet、容器运行时、kube-proxy,再加上 CNI、CSI、日志和监控代理等配套组件。控制平面负责“决定应该是什么状态”,工作节点负责“把这个状态跑出来并持续汇报”。如果一个节点 NotReady,排查也通常围绕这几件事展开:kubelet 有没有连上 API Server,容器运行时能不能创建容器,网络插件是否正常,磁盘、内存、PID 是否触发压力状态。kubelet 负责什么?kubelet 是节点上的主代理,它从 API Server 获取分配到本节点的 Pod 规范,然后通过 CRI 调用 containerd 或 CRI-O 创建容器。它还负责挂载卷、执行探针、上报 Node 和 Pod 状态,并在容器异常退出时根据 restartPolicy 做处理。可以用下面的命令看 kubelet 和节点状态:kubectl describe node <node-name>journalctl -u kubelet -n 100 --no-pagercrictl ps -acrictl logs <container-id>kubelet 的边界也要说清楚:它不负责为 Pod 选择节点,那是 scheduler 的工作;它也不直接实现 Service 负载均衡,那主要由 kube-proxy 或 eBPF 数据面完成。很多人看到 Pod 起不来就重启 kubelet,但如果根因是镜像拉取失败、PVC 挂载失败或 CNI 没准备好,重启只会掩盖现场。容器运行时和 kube-proxy 各做什么?容器运行时负责真正创建容器、拉镜像、管理容器生命周期。现在主流选择是 containerd 或 CRI-O,Docker 的 dockershim 在 Kubernetes 1.24 之后已经移除;如果还用 Docker,也通常是通过额外适配层接入。kube-proxy 负责根据 Service 和 EndpointSlice 维护节点上的转发规则,常见模式有 iptables 和 IPVS,有些集群会用 Cilium 等 eBPF 方案替代它的部分职责。apiVersion: kubeproxy.config.k8s.io/v1alpha1kind: KubeProxyConfigurationmode: "ipvs"ipvs: scheduler: "rr"iptables 模式简单、兼容性好;IPVS 在大规模 Service 场景下性能和算法选择更好,但需要内核模块支持。取舍不是“哪个更高级”,而是集群规模、内核能力、运维熟悉度和网络插件支持是否匹配。追问Node Ready 由什么决定?Node Ready 主要由 kubelet 上报,背后包含 kubelet 自身健康、容器运行时可用性、网络是否就绪以及节点压力状态等信息。你可以用 kubectl describe node 看 Conditions,包括 Ready、MemoryPressure、DiskPressure、PIDPressure、NetworkUnavailable。边界是 Ready=True 只表示节点可参与调度,不代表节点上的每个 Pod 都健康;Pod 是否对外服务还要看 readinessProbe 和 Service 后端。生产排障时要把 Node 条件、Pod 事件和容器日志放在一起看。cordon 和 drain 有什么区别?cordon 只是把节点标记为不可调度,新 Pod 不会再放到这个节点,已有 Pod 不受影响。drain 会驱逐普通 Pod,常用于节点维护或下线:kubectl cordon <node-name>kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-datakubectl uncordon <node-name>取舍点在于你是否要立刻迁走工作负载;只做内核参数检查可能 cordon 就够了,重启机器或换盘通常需要 drain。踩坑是 DaemonSet 不会被 drain 删除,带本地 emptyDir 的 Pod 可能丢临时数据,所以命令参数要看清楚。kube-proxy 出问题会表现成什么?典型表现是 Pod 本身正常,但通过 Service 访问失败,或者只有部分节点访问 Service 异常。可以检查 kube-proxy 日志、Service 后端和节点转发规则:kubectl get svc,endpointslices -Akubectl logs -n kube-system -l k8s-app=kube-proxy --tail=100iptables-save | grep KUBE-SVC | head如果集群使用 IPVS,还要看 ipvsadm -Ln。边界是 DNS 解析失败不一定是 kube-proxy,CoreDNS、NetworkPolicy、CNI 路由同样可能导致服务不可达。不要一上来就删除 kube-proxy Pod,先确认是所有节点异常还是单节点异常。节点压力状态会怎样影响 Pod?MemoryPressure、DiskPressure、PIDPressure 触发后,节点可能拒绝新 Pod 调度,严重时 kubelet 会按 QoS 和驱逐阈值清理 Pod。BestEffort Pod 最容易被驱逐,Guaranteed Pod 相对更稳,但也不是绝对免死。实际取舍是给关键服务设置合理 requests/limits,同时给系统和 kubelet 预留资源。一个常见坑是日志撑爆磁盘导致 DiskPressure,应用没改一行代码,却出现镜像拉取失败、容器创建失败和 Pod 被驱逐。工作节点需要哪些日常巡检?至少要巡检 kubelet、containerd、CNI、磁盘、内存、节点证书和系统时间。常用命令包括:kubectl get nodes -o widekubectl top nodessystemctl status kubelet containerdjournalctl -u containerd -n 50 --no-pager巡检的边界是不要只看 Kubernetes 对象,底层内核、磁盘 inode、conntrack 表、时间同步都会影响节点稳定性。对于生产集群,建议把节点 NotReady、DiskPressure、kubelet 证书过期、容器运行时重启次数纳入告警。节点问题越早在系统层发现,越不容易演变成业务层故障。
服务端阅读 05月31日 16:34

Kubernetes PV 和 PVC 有什么区别?如何管理持久化存储?

PV 和 PVC 的区别可以用一句话概括:PV 是集群里真实或即将创建的存储资源,PVC 是业务在命名空间里提出的存储申请。Pod 不直接挑磁盘,而是引用 PVC;控制器再根据容量、访问模式、StorageClass、selector 等条件,把 PVC 绑定到合适的 PV。这样做的好处是把“应用要多大空间”和“底层用 EBS、NFS 还是本地盘”解耦,但代价是排障时要同时看 PVC、PV、StorageClass、Pod 事件,不能只盯一个对象。PV、PVC 和 StorageClass 怎么配合?动态供给是最常见的方式:开发者创建 PVC,指定 StorageClass,存储插件自动创建 PV。下面是一个比较典型的写法:apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: fast-ssdprovisioner: ebs.csi.aws.comreclaimPolicy: DeletevolumeBindingMode: WaitForFirstConsumerallowVolumeExpansion: true---apiVersion: v1kind: PersistentVolumeClaimmetadata: name: data-pvcspec: accessModes: - ReadWriteOnce storageClassName: fast-ssd resources: requests: storage: 20GivolumeBindingMode: WaitForFirstConsumer 很关键,它会等 Pod 出现后再结合调度结果创建或绑定卷,避免卷先在 A 可用区创建,Pod 却被调度到 B 可用区。reclaimPolicy: Delete 适合临时环境,PVC 删除后底层卷也会被删;生产数据库通常更倾向 Retain,防止误删 PVC 直接丢数据。这个取舍没有绝对答案,关键看数据是否可再生,以及团队有没有成熟的备份和恢复流程。Pod 怎么挂载 PVC?Pod 里只引用 PVC 名称,不关心底层 PV 是谁:apiVersion: v1kind: Podmetadata: name: appspec: containers: - name: app image: nginx:1.25 volumeMounts: - name: data mountPath: /usr/share/nginx/html volumes: - name: data persistentVolumeClaim: claimName: data-pvc如果是 Deployment 多副本,要特别注意访问模式。ReadWriteOnce 通常只能被一个节点读写挂载,多个副本被调度到不同节点时可能挂载失败;需要多节点同时读写时,应选择支持 ReadWriteMany 的后端,比如 NFS、CephFS 或云厂商文件存储。别把 PVC 当成“共享目录万能解法”,有状态服务还要考虑锁、并发写入、数据一致性和备份窗口。追问静态 PV 和动态 PV 怎么选?动态 PV 适合大多数云上和标准化环境,开发者只需要申请容量,底层卷由 CSI 插件创建,运维成本低。静态 PV 适合已有存储、特殊挂载参数、本地盘或者迁移场景,管理员先创建 PV,再让 PVC 通过 storageClassName、容量和 selector 去绑定。取舍点在于灵活性和自动化:动态供给快,但受 StorageClass 能力限制;静态供给可控,但人工维护更容易出错。生产里常见做法是通用应用走动态,数据库迁移或本地高性能盘走静态。RWO、RWX、RWOP 有什么区别?RWO 是单节点读写,不等于单 Pod;同一节点上的多个 Pod 在某些场景下可能同时使用它。RWX 是多节点读写,适合共享文件,但底层必须真的支持多写,不是 YAML 写了就生效。RWOP 是单 Pod 读写,边界更严格,适合希望从 Kubernetes 层面避免两个 Pod 同时挂同一卷的场景。踩坑点是访问模式表达的是挂载能力,不保证你的应用层并发写入一定安全。为什么 PVC 一直 Pending?先看 PVC 事件和 StorageClass:kubectl describe pvc data-pvckubectl get storageclasskubectl describe storageclass fast-ssdkubectl get pv常见原因包括 storageClassName 写错、没有默认 StorageClass、CSI 插件异常、容量或访问模式没有匹配 PV。使用 WaitForFirstConsumer 时,PVC 在 Pod 创建前 Pending 是正常的,因为它要等调度器确定节点和可用区。真正的坑是 Pod 也因为其他原因 Pending,导致你误以为是存储问题;这时要同时 kubectl describe pod 看调度事件。reclaimPolicy 选 Delete 还是 Retain?Delete 的优点是干净,PVC 删除后底层存储自动释放,测试环境和可再生数据很适合。Retain 的优点是安全,误删 PVC 时底层数据还在,但需要人工清理 PV 的 claimRef、回收磁盘或重新导入。生产数据库、用户上传文件、审计数据更适合 Retain,再配合备份策略;缓存、临时索引和 CI 产物通常可以 Delete。边界是 Retain 不等于备份,它只是“不自动删”,磁盘损坏、误写入和勒索加密仍然需要快照或异地备份解决。扩容 PVC 有哪些限制?首先 StorageClass 必须设置 allowVolumeExpansion: true,底层 CSI 也要支持扩容。扩容命令很简单:kubectl patch pvc data-pvc -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'kubectl describe pvc data-pvc但缩容通常不支持,文件系统在线扩容也可能依赖节点插件和文件系统类型。踩坑最多的是只看到 PVC 容量变大,却没确认容器内文件系统是否完成扩展;可以进入容器用 df -h 验证。对数据库类应用,扩容前仍建议做快照,因为存储层操作成功不代表应用层一定能平滑消化。