ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

智能体Skills设计:可验证能力单元的工程化实践

智能体Skills设计:可验证能力单元的工程化实践 1. 项目概述当“skills”不再是个模糊标签而是一套可定义、可组合、可部署的智能体能力单元最近在好几个技术团队的内部分享会上我都被同一个词反复追问“skills”到底指什么不是简历上那行轻飘飘的“熟练掌握Python/React/Docker”也不是招聘JD里堆砌的“具备优秀的沟通能力和学习能力”——而是实实在在能被调用、能被测试、能嵌入工作流、能和Gemini或Claude这类模型深度协同的原子化能力模块。这个词突然密集出现在GKE控制台的插件市场、Genkit文档的架构图里、甚至GitHub新开源项目的README第一行。它背后站着的是智能体Agent开发范式从“写提示词硬编码逻辑”向“能力编排运行时调度”的实质性跃迁。我第一次在真实生产环境里把“skills”当核心构件来设计是在给一家做跨境SaaS服务的客户重构客服工单处理系统时。他们原来的方案是让大模型直接读取工单内容、生成回复草稿再由人工审核发布。问题出在三个地方一是模型对内部API权限、数据脱敏规则、SLA响应时限这些硬性约束理解不稳定经常越界二是不同业务线支付异常、物流延迟、发票开具需要完全不同的处理路径全靠提示词区分维护成本爆炸三是当某个环节需要调用外部系统比如触发Stripe退款、查询FedEx物流节点、生成PDF发票时模型只能“描述”动作无法真正执行。我们最后拆解出7个明确的skillsvalidate_payment_refund_eligibility、fetch_realtime_shipping_status、generate_compliance_invoice_pdf、escalate_to_human_agent_if_risk_score_gt_0.8……每个skill都封装了具体的输入校验、错误重试、日志埋点和权限检查。上线后工单自动闭环率从32%提升到68%更关键的是当法务部要求新增GDPR数据擦除流程时我们只用新增一个execute_gdpr_right_to_erasureskill并调整编排逻辑两天就完成了全链路更新——这在过去意味着重写整个提示工程模板和后端路由。所以“skills”在这里本质是面向智能体时代的函数式编程范式。它把“能力”从模型的黑盒输出中剥离出来变成开发者可以像调用REST API一样精确控制、像管理Docker镜像一样版本化、像配置Kubernetes资源一样声明式编排的独立单元。它不依赖于某个特定模型Gemini、Claude、本地Llama3而是构建在统一的运行时契约之上。你看到的“Gemini Code Assist for Individuals”报错表面是账户资格问题深层原因往往是客户端试图绕过skills的权限网关直接调用底层模型接口而“Claude Agent Skills: A First Principles Deep Dive”这类热帖其价值恰恰在于揭示了skills如何通过input_schema、output_schema、execution_context这三个契约要素把混沌的AI交互变成了可预测的软件工程实践。如果你正在前端开发中尝试集成AI能力或者正为GKE集群上的智能体服务寻找标准化扩展方式那么理解skills的设计哲学与落地细节不是锦上添花而是避免踩进“提示词沼泽”的生存必需。2. 核心设计思路为什么必须放弃“万能提示词”转向可验证的skills架构2.1 传统提示词方案的三大结构性缺陷我见过太多团队在AI项目初期陷入“提示词幻觉”以为只要写出足够精妙的system prompt就能让模型完美理解业务规则、安全边界和执行逻辑。但现实很快会给出残酷反馈。去年帮一家金融风控团队搭建反欺诈分析助手时他们最初的方案是让Gemini Pro直接解析交易流水JSON根据预设规则判断风险等级并生成处置建议。结果上线一周出现了三类典型故障规则漂移失效当监管新规要求将“同一IP下5分钟内3次失败登录”纳入高风险指标时团队修改了prompt里的文字描述但模型在实际推理中仍沿用旧逻辑因为它的知识库并未同步更新且缺乏强制校验机制权限越界执行模型在生成建议时竟输出了“请立即冻结该用户账户并通知合规部门”的指令而系统根本没有提供冻结账户的API权限这属于严重越权行为错误传播放大某次上游数据清洗脚本异常导致部分交易流水的amount字段为空字符串。模型未做空值校验直接参与计算最终输出的风险分数全部失真下游告警系统因此瘫痪4小时。这些问题的根源在于提示词本质上是一种弱契约Weak Contract它无法强制约束模型的输入格式、无法拦截非法操作、无法保证输出结构可被下游程序解析。就像你不能靠在API文档里写“请勿传入SQL注入语句”来保障数据库安全一样仅靠文字描述无法建立可靠的能力边界。2.2 skills架构的四大设计原则与工程价值当我们决定用skills重构上述风控系统时核心设计并非技术选型而是确立四条铁律。这些原则直接决定了后续所有工具链和代码结构契约先行Contract-First每个skill必须明确定义input_schemaJSON Schema、output_schemaJSON Schema和error_schema预定义错误码及含义。例如assess_transaction_riskskill的输入schema强制要求transaction_id为非空字符串、amount为正数、timestamp为ISO8601格式。任何不符合schema的请求在进入业务逻辑前就被运行时框架拒绝根本不会触达模型。这相当于在API网关层就完成了强类型校验把90%的低级错误拦截在入口。能力隔离Capability Isolation一个skill只做一件事且这件事必须有明确的、可验证的副作用。fetch_user_kyc_status只负责查询KYC认证状态并返回结构化结果initiate_manual_review只负责向审核队列发送消息并返回任务ID。它们之间绝不共享内存或状态所有数据流转必须通过明确定义的输入/输出完成。这种隔离让单个skill可以独立测试、独立部署、独立扩缩容——当KYC查询服务因第三方API限流变慢时我们只需给fetch_user_kyc_statusskill单独增加超时重试和降级策略不影响其他能力模块。上下文感知Context-Aware Executionskills不是孤立运行的函数而是运行在包含execution_context的沙箱中。这个context由运行时框架注入包含当前用户身份JWT claims、请求来源Web/App/API、SLA等级P0/P1/P2、可用资源配额CPU/Memory/Token Budget等元信息。assess_transaction_riskskill在执行时会根据context中的user_tier字段动态调整风险阈值VIP用户触发人工审核的阈值比普通用户高20%这无需修改skill代码只需更新context生成逻辑。可观测即能力Observability as Capability每个skill的调用必须自带结构化日志、性能指标p95延迟、错误率和trace ID。我们曾发现generate_compliance_reportskill在每月初的调用量激增300%但p95延迟却下降了15%。深入trace才发现该skill内部缓存了月度统计模板而缓存键未包含report_period参数导致不同月份的报告共用了同一份缓存造成数据污染。没有细粒度的可观测性这种隐蔽缺陷几乎不可能被发现。提示选择skills框架时首要考察其对这四条原则的支持深度。Genkit的defineSkillAPI天然支持schema定义和context注入而某些轻量级库仅提供简单的函数包装缺失契约校验和上下文管理强行使用会导致后期维护成本指数级上升。2.3 为什么GKE成为skills部署的事实标准平台当团队决定将skills从本地开发环境推向生产时GKEGoogle Kubernetes Engine几乎是唯一合理的选择这并非出于厂商绑定而是由skills的运行特性决定的弹性扩缩容的刚性需求客服场景的skills调用量存在明显波峰波谷如工作日9-11点、14-16点为高峰而风控分析类skills则可能在月末结算时突发海量请求。Kubernetes的HPAHorizontal Pod Autoscaler可以根据CPU、内存或自定义指标如skills调用QPS自动伸缩Pod副本数。我们为process_customer_inquiryskill配置了基于RPS的扩缩容策略当每秒请求数超过50时自动扩容至最多10个实例低于10时缩容至2个。实测表明该策略使平均响应延迟稳定在320ms以内资源利用率提升至65%远高于固定实例数的方案。多租户与权限隔离的原生支持一个GKE集群需同时承载多个业务线的skills支付、物流、售后每个业务线对数据权限、网络策略、资源配额的要求截然不同。Kubernetes的Namespace天然提供了逻辑隔离层配合RBACRole-Based Access Control和NetworkPolicy可以精确控制logistics-namespace内的skills Pod只能访问shipping-apiService且禁止访问payment-dbSecretfinance-namespace的skills则拥有更高的CPU配额和专属的审计日志采集器。这种细粒度管控是传统VM或Serverless平台难以低成本实现的。声明式运维与GitOps的无缝衔接skills的版本迭代极其频繁平均每周发布2-3个新版本手动更新部署极易出错。我们将所有skills的Deployment YAML、ConfigMap存储API密钥等配置、Service定义内部服务发现全部托管在Git仓库中。通过Argo CD这样的GitOps工具当Git分支合并时自动触发GKE集群的状态同步。一次assess_transaction_riskskill的v2.1.0发布从代码提交到全量生效耗时仅4分17秒且全程可审计、可回滚。相比之下手动SSH到服务器更新Docker镜像的方式已彻底退出我们的生产流程。与Google Cloud生态的深度协同GKE集群可原生集成Cloud Logging、Cloud Monitoring、Cloud Trace无需额外埋点即可获得skills的全链路可观测性通过Workload Identityskills Pod能以最小权限原则安全访问Cloud Storage读取训练数据、Secret Manager获取数据库密码、Vertex AI调用专用微调模型等服务彻底规避了硬编码密钥的安全风险。3. 实操细节解析从零构建一个可验证的search_product_inventoryskills3.1 技术栈选型与环境准备为什么Genkit GKE Vertex AI是黄金组合在启动search_product_inventoryskill开发前我们花了整整两天进行技术栈论证。目标很明确要一个能快速验证概念、又能平滑过渡到生产环境的方案。最终选定Genkit作为核心框架GKE作为运行平台Vertex AI作为模型后端这个组合绝非随意拼凑而是基于对各组件能力边界的深刻理解Genkit为何不可替代市面上有大量“Agent框架”但多数聚焦于对话编排如LangChain的AgentExecutor或简单函数调用如LlamaIndex的Tool。Genkit的独特价值在于其skills-first的原生设计。它的defineSkillAPI强制开发者在编写业务逻辑前先用TypeScript定义完整的input_schema和output_schema并内置了基于Zod的运行时校验。更重要的是Genkit的run方法天然支持execution_context注入且其CLI工具genkit deploy能一键将skills打包为容器镜像并推送到Artifact Registry再生成标准的Kubernetes Deployment YAML。这意味着你在本地用genkit run测试的skill和部署到GKE上运行的skill除了execution_context中的环境变量如CLOUD_ENVprod不同其余逻辑完全一致——消除了“本地能跑线上报错”的经典陷阱。GKE的不可替代性有人会问为什么不用Cloud RunCloud Run确实更轻量。但当我们规划skills矩阵时发现必须解决三个Cloud Run难以优雅处理的问题1多个skills需要共享一个Redis缓存实例用于库存状态快照Cloud Run的无状态特性要求每个实例都独立连接增加了连接池管理复杂度2某些skills如批量库存同步需要长时间运行15分钟超出Cloud Run默认超时3我们需要对skills Pod进行精细的网络策略控制如禁止inventory-skill访问公网仅允许其调用内部warehouse-api。GKE的StatefulSet、Job、NetworkPolicy等原生资源为这些需求提供了开箱即用的解决方案。Vertex AI的决策逻辑虽然Gemini API可通过REST直接调用但我们选择Vertex AI核心原因是其企业级治理能力。Vertex AI的Model Garden提供了经过Google安全审计的Gemini模型镜像其Endpoint资源支持细粒度的配额管理如限制search_product_inventoryskill每分钟最多调用Gemini 100次最关键的是Vertex AI的Request Logging功能能自动记录每次模型调用的完整输入/输出脱敏后为后续的合规审计和效果分析提供不可篡改的证据链。当法务部要求证明“模型从未输出过用户手机号”时我们直接导出Vertex AI的日志报表而非翻查应用层日志。环境准备清单已在GKE集群中完成Kubernetes v1.26 集群启用Workload IdentityArtifact Registry仓库用于存储skills容器镜像Vertex AI Endpoint已部署gemini-1.5-pro-001模型Cloud SQL PostgreSQL实例存储产品目录和库存快照Redis Memorystore实例用于高频库存状态缓存3.2search_product_inventoryskill的完整实现从契约定义到业务逻辑现在让我们动手实现这个核心skill。它接收用户自然语言查询如“帮我找北京仓还有多少台MacBook Pro M3 16GB内存的现货”解析出产品SKU、仓库位置、所需属性查询库存数据库并返回结构化结果。整个过程严格遵循前述四大设计原则。第一步定义不可变的契约Schema// skills/search-product-inventory/schema.ts import { z } from zod; export const SearchInventoryInputSchema z.object({ // 用户原始查询文本必填 query: z.string().min(1, 查询文本不能为空), // 当前用户ID用于权限校验由execution_context注入 userId: z.string().uuid(用户ID格式不正确), // 请求来源渠道影响结果排序策略 channel: z.enum([web, mobile, api]).default(web), // 可选的上下文信息如用户历史搜索偏好 context: z.record(z.any()).optional() }); export const SearchInventoryOutputSchema z.object({ // 查询是否成功 success: z.boolean(), // 结构化的产品库存信息列表 results: z.array( z.object({ sku: z.string().describe(产品唯一标识符), productName: z.string().describe(产品名称), warehouse: z.string().describe(仓库代码), availableQuantity: z.number().int().min(0).describe(可用库存数量), // 库存状态枚举供前端差异化展示 inventoryStatus: z.enum([IN_STOCK, LOW_STOCK, OUT_OF_STOCK, BACKORDERED]), // 预估发货时间仅当有货时返回 estimatedShipDate: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/).optional() }) ), // 本次查询的元信息 metadata: z.object({ queryId: z.string().uuid(), processingTimeMs: z.number().int(), modelUsed: z.string() }) }); // 预定义错误码便于客户端统一处理 export const SearchInventoryErrorSchema z.object({ code: z.enum([ INVALID_QUERY_FORMAT, USER_PERMISSION_DENIED, WAREHOUSE_NOT_FOUND, MODEL_INVOCATION_FAILED, DATABASE_UNAVAILABLE ]), message: z.string(), details: z.record(z.any()).optional() });注意这里userId字段虽在输入schema中定义为z.string()但实际值不由客户端传入而是由Genkit运行时从execution_context中提取并注入。schema定义确保了skill逻辑能安全地依赖此字段而无需在业务代码中做二次校验。第二步编写核心业务逻辑TypeScript// skills/search-product-inventory/index.ts import { defineSkill, SkillInput, SkillOutput } from genkit-ai/core; import { SearchInventoryInputSchema, SearchInventoryOutputSchema, SearchInventoryErrorSchema } from ./schema; import { getVertexAIModel } from ../lib/vertex-ai; import { queryInventoryDB } from ../lib/inventory-db; import { getInventoryCache } from ../lib/inventory-cache; // 定义skill指定输入输出schema export const searchProductInventory defineSkill({ name: search_product_inventory, inputSchema: SearchInventoryInputSchema, outputSchema: SearchInventoryOutputSchema, errorSchema: SearchInventoryErrorSchema, // 执行函数接收输入和execution_context async execute(input: SkillInputtypeof SearchInventoryInputSchema, context) { const startTime Date.now(); try { // 1. 权限校验检查用户是否有权查询库存 if (!await checkUserInventoryPermission(input.userId)) { throw new Error(USER_PERMISSION_DENIED); } // 2. 使用Vertex AI的Gemini模型解析用户查询 // 构建结构化提示强制模型输出JSON const model getVertexAIModel(gemini-1.5-pro-001); const parsePrompt 你是一个专业的库存查询解析器。请严格按以下JSON Schema输出不要任何额外文字 ${JSON.stringify(SearchInventoryQuerySchema)} 用户查询${input.query} 请提取产品SKU若未明确尝试从产品名推断、仓库位置若未明确默认为BEIJING、其他关键属性。 ; const parseResult await model.generate({ prompt: parsePrompt, // 设置严格的输出约束防止模型自由发挥 responseMimeType: application/json }); // 3. 解析模型输出转换为强类型对象 const parsedQuery SearchInventoryQuerySchema.parse(JSON.parse(parseResult.text)); // 4. 查询库存优先查Redis缓存缓存未命中则查PostgreSQL let inventoryResults; if (parsedQuery.warehouse) { inventoryResults await getInventoryCache().get(parsedQuery.sku, parsedQuery.warehouse); } if (!inventoryResults) { inventoryResults await queryInventoryDB(parsedQuery.sku, parsedQuery.warehouse); // 写入缓存设置10分钟过期库存变化通常不频繁 if (parsedQuery.warehouse) { await getInventoryCache().set(parsedQuery.sku, parsedQuery.warehouse, inventoryResults, 600); } } // 5. 构建结构化输出 const output: SkillOutputtypeof SearchInventoryOutputSchema { success: true, results: inventoryResults.map(item ({ sku: item.sku, productName: item.productName, warehouse: item.warehouse, availableQuantity: item.quantity, inventoryStatus: calculateInventoryStatus(item.quantity), estimatedShipDate: item.quantity 0 ? new Date().toISOString() : undefined })), metadata: { queryId: context.requestId || unknown, processingTimeMs: Date.now() - startTime, modelUsed: gemini-1.5-pro-001 } }; return output; } catch (error: any) { // 统一错误处理映射到预定义错误码 const errorCode mapToStandardErrorCode(error); throw { code: errorCode, message: 库存查询失败: ${error.message}, details: { originalError: error.stack } }; } } }); // 辅助函数计算库存状态 function calculateInventoryStatus(quantity: number): IN_STOCK | LOW_STOCK | OUT_OF_STOCK | BACKORDERED { if (quantity 10) return IN_STOCK; if (quantity 0 quantity 10) return LOW_STOCK; if (quantity 0) return OUT_OF_STOCK; return BACKORDERED; // 理论上不会出现负数但保留兜底 } // 辅助函数映射错误到标准码 function mapToStandardErrorCode(error: any): string { if (error.message?.includes(permission)) return USER_PERMISSION_DENIED; if (error.message?.includes(warehouse)) return WAREHOUSE_NOT_FOUND; if (error.message?.includes(model)) return MODEL_INVOCATION_FAILED; if (error.message?.includes(database)) return DATABASE_UNAVAILABLE; return INVALID_QUERY_FORMAT; }第三步本地开发与测试Genkit CLI在本地我们使用Genkit CLI进行高效迭代# 1. 启动本地开发服务器自动监听文件变更 genkit dev # 2. 在浏览器打开 http://localhost:3000进入Genkit Studio # 这里可以直观地看到所有已定义的skills点击search_product_inventory # 输入测试数据符合schema的JSON实时查看执行日志、输入输出、耗时 # 3. 编写单元测试Jest # test/search-product-inventory.test.ts import { searchProductInventory } from ../skills/search-product-inventory; import { mockVertexAIModel } from ../test/mocks/vertex-ai; describe(searchProductInventory skill, () { it(should return inventory for valid query, async () { // Mock Vertex AI模型返回预设的解析结果 mockVertexAIModel({ text: JSON.stringify({ sku: MBP-M3-16GB, warehouse: BEIJING }) }); const result await searchProductInventory.execute({ query: 北京仓MacBook Pro M3 16GB还有多少, userId: user-123e4567-e89b-12d3-a456-426614174000, channel: web }, { requestId: req-abc123 }); expect(result.success).toBe(true); expect(result.results[0].sku).toBe(MBP-M3-16GB); expect(result.metadata.processingTimeMs).toBeGreaterThan(0); }); });3.3 GKE部署全流程从Docker镜像到Kubernetes资源当本地测试通过后部署到GKE集群是标准化的流水线1. 构建容器镜像# Genkit CLI自动生成Dockerfile和构建脚本 genkit build --output-dir ./dist # 构建并推送镜像到Artifact Registry docker build -t us-central1-docker.pkg.dev/my-project/my-repo/inventory-skill:v1.0.0 . docker push us-central1-docker.pkg.dev/my-project/my-repo/inventory-skill:v1.0.02. 生成Kubernetes Deployment YAML# Genkit CLI生成标准YAML已配置好Workload Identity Service Account genkit deploy kubernetes \ --image us-central1-docker.pkg.dev/my-project/my-repo/inventory-skill:v1.0.0 \ --service-account inventory-samy-project.iam.gserviceaccount.com \ --env VERTEX_AI_ENDPOINTprojects/my-project/locations/us-central1/endpoints/gemini-15-pro-001 \ --output ./k8s/inventory-skill-deployment.yaml生成的inventory-skill-deployment.yaml关键片段apiVersion: apps/v1 kind: Deployment metadata: name: inventory-skill spec: replicas: 3 # 初始副本数 selector: matchLabels: app: inventory-skill template: metadata: labels: app: inventory-skill spec: serviceAccountName: inventory-sa # 关联Workload Identity SA containers: - name: inventory-skill image: us-central1-docker.pkg.dev/my-project/my-repo/inventory-skill:v1.0.0 env: - name: VERTEX_AI_ENDPOINT value: projects/my-project/locations/us-central1/endpoints/gemini-15-pro-001 # 资源限制防止单个Pod吃光节点资源 resources: requests: memory: 256Mi cpu: 100m limits: memory: 512Mi cpu: 200m # 就绪探针确保技能启动完成才接入流量 readinessProbe: httpGet: path: /healthz port: 3000 initialDelaySeconds: 10 periodSeconds: 5 --- # Service定义供集群内其他skills调用 apiVersion: v1 kind: Service metadata: name: inventory-skill-service spec: selector: app: inventory-skill ports: - port: 3000 targetPort: 30003. 应用部署并验证# 应用YAML到GKE集群 kubectl apply -f ./k8s/inventory-skill-deployment.yaml # 查看Pod状态 kubectl get pods -l appinventory-skill # NAME READY STATUS RESTARTS AGE # inventory-skill-7c8d9b4f5-2xq9p 1/1 Running 0 45s # 查看日志确认初始化成功 kubectl logs -l appinventory-skill --tail50 # 使用curl进行端到端测试从集群内其他Pod发起 kubectl run curl-test --imagecurlimages/curl -i --rm --restartNever \ -- sh -c curl http://inventory-skill-service:3000/healthz # {status:ok,timestamp:2024-05-20T08:30:45.123Z}4. 生产环境实操监控、扩缩容与灰度发布的实战经验4.1 构建skills的可观测性体系不只是看CPU和内存在GKE上运行skills基础的CPU/Memory监控只是入门。真正的生产级可观测性必须深入到skills的业务语义层。我们为search_product_inventoryskill构建了三层监控第一层基础设施层GKE原生指标container_cpu_usage_seconds_total识别资源瓶颈如某次发布后CPU使用率持续高于80%指向代码中未关闭的数据库连接container_memory_working_set_bytes发现内存泄漏如Pod重启前内存占用呈线性增长network_receive_bytes_total监控网络流量突增可能预示着恶意爬虫或DDoS攻击。第二层平台层Genkit Vertex AI指标Genkit暴露的genkit_skill_invocation_count_total{skillsearch_product_inventory,statussuccess}这是最核心的业务指标。我们将其与genkit_skill_invocation_duration_seconds_bucket结合计算成功率success / total和p95延迟。当成功率跌破99.5%或p95延迟超过1.2秒时触发PagerDuty告警。Vertex AI的vertex_ai_endpoint_request_count和vertex_ai_endpoint_request_latency分离模型调用本身的性能。如果Genkit skill延迟高但Vertex AI延迟正常则问题一定在skill自身的数据库查询或缓存逻辑反之则需优化模型提示或考虑切换模型版本。第三层业务语义层自定义指标与日志自定义Prometheus指标inventory_search_result_count{warehouseBEIJING,skuMBP-M3-16GB}记录每次查询返回的有效结果数。当某SKU的查询结果长期为0可能意味着该产品已下架需触发运营告警结构化日志每条日志必须包含skill_name、query_id、user_id、warehouse、sku、result_count、processing_time_ms、model_used。通过Cloud Logging的高级过滤可快速定位问题resource.typek8s_container resource.labels.cluster_nameprod-cluster jsonPayload.skill_namesearch_product_inventory jsonPayload.processing_time_ms 2000即找出所有耗时超2秒的慢查询。实操心得我们曾遇到一个诡异问题——search_product_inventoryskill的p95延迟在每天凌晨2点准时飙升至5秒以上。通过分析结构化日志发现所有慢查询都集中在warehouseSHANGHAI且sku字段为空。进一步排查发现是上游一个定时任务在凌晨2点同步上海仓数据时错误地将部分SKU字段置为空字符串导致skill在查询时触发了全表扫描。没有业务语义层的日志这个问题会演变成一个无法定位的“神秘性能抖动”。4.2 基于真实负载的HPA水平Pod自动扩缩容配置GKE的HPA默认基于CPU/Memory但对于skills这种I/O密集型服务CPU使用率可能很低但请求队列却已堆积如山。我们必须基于自定义指标进行扩缩容。以下是为search_product_inventoryskill配置的HPA YAML# k8s/inventory-skill-hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: inventory-skill-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: inventory-skill minReplicas: 2 maxReplicas: 10 metrics: - type: External external: # 使用Cloud Monitoring的自定义指标 metric: name: custom.googleapis.com/genkit/skill_invocation_qps selector: matchLabels: resource.label.cluster_name: prod-cluster resource.label.namespace_name: default resource.label.pod_name: inventory-skill target: type: AverageValue averageValue: 50 # 目标QPS为50 - type: External external: metric: name: custom.googleapis.com/genkit/skill_invocation_p95_latency_ms selector: matchLabels: resource.label.cluster_name: prod-cluster resource.label.namespace_name: default resource.label.pod_name: inventory-skill target: type: Value value: 1200 # p95延迟目标为1200ms这个配置的精妙之处在于双指标驱动当QPS超过50时HPA会增加Pod以分担负载但当p95延迟超过1200ms时即使QPS未达阈值HPA也会强制扩容因为这表明现有Pod已不堪重负。我们通过Cloud Monitoring的Metrics Explorer创建了这两个自定义指标并确保Genkit的metrics exporter正确上报。注意custom.googleapis.com/genkit/skill_invocation_qps指标并非简单计数而是通过Prometheus的rate()函数计算过去2分钟的平均每秒请求数这能平滑掉瞬时毛刺避免扩缩容震荡。4.3 灰度发布与金丝雀测试如何零 downtime 更新skillsskills的更新绝不能采用“一刀切”的滚动更新。我们采用**金丝雀发布Canary Release**策略将新版本流量逐步导入步骤1部署新版本Deploymentv1.0.1# 修改Deployment YAML中的镜像标签 # ... image: us-central1-docker.pkg.dev/my-project/my-repo/inventory-skill:v1.0.1 kubectl apply -f ./k8s/inventory-skill-deployment-v1.0.1.yaml # 此时v1.0.0和v1.0.1两个Deployment并存步骤2创建Service与Endpoint Slices# k8s/inventory-skill-canary-service.yaml apiVersion: v1 kind: Service metadata: name: inventory-skill-canary spec: # 不直接关联Pod而是通过EndpointSlice clusterIP: None --- # EndpointSlice为v1.0.0版本 apiVersion: discovery.k8s.io/v1 kind: EndpointSlice metadata: name: inventory-skill-v100 labels: kubernetes.io/service-name: inventory-skill-canary skills-version: v1.0.0 addressType: IPv4 endpoints: - conditions: ready: true hostname: inventory-skill-v100-pod-1 ip: 10.4.2.15 ports: - name: http port: 3000 protocol: TCP --- # EndpointSlice为v1.0.1版本 apiVersion: discovery.k8s.io/v1 kind: EndpointSlice metadata: name: inventory-skill-v101 labels: kubernetes.io/service-name: inventory-skill-canary skills-version: v1.0.1 addressType: IPv4 endpoints: - conditions: ready: true hostname: inventory-skill-v101-pod-1 ip: 10.4.2.16 ports: - name: http port: 3000 protocol: TCP步骤3通过Istio Ingress Gateway进行流量切分# istio-gateway/inventory-skill-canary-gateway.yaml apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: inventory-skill-canary spec: hosts: - inventory.api.mycompany.com http: - route: # 95%流量到v1.0.0 - destination: host: inventory-skill-canary subset: v100 weight: 95 # 5%流量到v1.0.1 - destination: host: inventory-skill-canary subset: v101 weight: 5 --- apiVersion: networking.istio.io/v
返回列表