1. 项目概述当模型走出Jupyter真正开始呼吸真实世界的空气“From Notebook to Production: Running ML in the Real World (Part 4)”——这个标题本身就像一句暗号专为那些在Jupyter里调通了模型、画出了漂亮ROC曲线、却在部署时被现实狠狠绊了一跤的工程师准备的。它不是讲怎么写model.fit()而是讲模型第一次被放进API里、第一次接到线上用户请求、第一次因为内存泄漏把服务器拖垮、第一次在凌晨三点被告警电话叫醒时你该抓哪根救命稻草。我带过六支AI工程团队亲手把四十多个模型从实验室推到生产环境最深的体会是模型的准确率只决定它能不能上线而它的可观测性、资源韧性、版本可追溯性才真正决定它能在线上活几天。Part 4不是收尾恰恰是实战的真正起点——它聚焦在模型服务化Model Serving这一环解决的是“模型训练完之后如何让它稳定、高效、可维护地响应每一次真实请求”这个核心命题。它适合三类人刚从数据科学岗转岗做MLOps的工程师需要快速建立生产级服务的系统认知正在被线上模型延迟飙升、OOM崩溃、AB测试结果漂移等问题困扰的算法负责人以及技术决策者想搞清楚为什么“模型准确率98%”和“业务转化率没变化”之间隔着一堵看不见的墙。这篇文章不讲抽象理论只讲我在金融风控、电商推荐、IoT设备预测三个高压力场景中用KubernetesTritonPrometheus这套组合拳踩出来的每一步实操细节、每一个参数背后的血泪教训以及为什么我们最终放弃TensorFlow Serving又为什么在Triton上硬生生加了一层自定义预处理网关。2. 整体架构设计与方案选型逻辑为什么不是Flask也不是TF Serving2.1 真实世界的服务压力远超本地Notebook的想象很多人以为把model.predict()包进一个Flask接口就完成了服务化我见过太多这样的“玩具服务”在真实流量下瞬间崩塌。去年某电商平台大促前一个用Flask封装的实时个性化排序模型在QPS刚冲到1200时平均延迟从80ms飙到2.3秒错误率突破17%。根本原因在于Flask是单线程同步框架每个请求独占一个Python线程而PyTorch/TensorFlow的GPU推理是异步计算密集型任务线程在等待GPU kernel执行时被死锁大量请求排队堆积内存持续增长直至OOM。这暴露了一个根本矛盾数据科学家习惯的交互式、单次推理范式与生产环境要求的高并发、低延迟、资源隔离范式存在天然鸿沟。因此架构设计的第一原则不是“快”而是“解耦”——把模型计算、请求路由、数据预处理、后处理、监控告警这些关注点彻底拆开各自独立演进、独立扩缩容。2.2 为什么放弃TensorFlow ServingTFS一次真实的性能压测对比我们曾将同一个BERT-based文本分类模型分别部署在TFS 2.11和NVIDIA Triton Inference Server 23.06上进行全链路压测硬件A100 80GB × 2网络25Gbps RoCE。关键数据如下指标TensorFlow ServingTriton Inference Server差距分析P95延迟ms14268Triton的动态批处理Dynamic Batching自动合并小批量请求GPU利用率提升53%TFS需手动配置batching策略且效果不稳定最大稳定QPS8902150Triton支持多模型并行加载与GPU实例切分Model Instance单卡可同时运行4个不同模型实例TFS仅支持单模型多副本资源浪费严重内存峰值GB18.411.2Triton的共享内存Shared Memory机制让输入数据零拷贝直达GPUTFS需CPU→GPU多次序列化/反序列化GPU显存占用GB32.124.7Triton的TensorRT优化器自动对ONNX模型进行FP16量化与图融合TFS对ONNX支持有限常需回退到原始TF SavedModel计算图冗余度高提示TFS并非不好它在纯TensorFlow生态、小规模部署、需要深度定制C后端的场景仍有价值。但当我们面对多框架PyTorch/ONNX/Triton、多硬件A100/L40S/边缘Jetson、多模型百级规模的混合场景时Triton的统一抽象层Inference Server Core提供了不可替代的治理能力。2.3 为什么选择Kubernetes作为底座不只是为了“上云”有人问“模型服务这么简单用Docker Compose不行吗”——可以但代价是运维复杂度指数级上升。我们管理着分布在3个Region、12个集群的模型服务每个集群承载50模型。Kubernetes的价值不在“容器编排”这个名词而在它提供的声明式治理原语HorizontalPodAutoscalerHPA基于prometheus.io/scrape指标自动扩缩容当某个风控模型的inference_latency_seconds_p95 150ms持续2分钟HPA自动增加2个Pod副本无需人工干预PodDisruptionBudgetPDB确保升级时至少有3个健康副本在线避免服务中断NetworkPolicy严格限制模型Pod只能访问指定的特征存储Feature Store和日志收集器Loki杜绝横向渗透风险。更重要的是K8s的Operator模式让我们把“模型生命周期管理”变成代码kubectl apply -f fraud-detection-v2.yaml就能完成模型版本切换、灰度发布、AB测试分流整个过程可审计、可回滚、可复现。这比任何手工脚本都更接近“基础设施即代码”IaC的本质。2.4 架构全景图四层解耦各司其职最终落地的架构是清晰的四层结构每一层都经过真实流量验证┌─────────────────────────────────────────────────────────────┐ │ Client Applications │ │ (Web App, Mobile SDK, Internal Microservices) │ └─────────────────────────────────────────────────────────────┘ ↓ HTTP/gRPC ┌─────────────────────────────────────────────────────────────┐ │ API Gateway Layer (Kong) │ │ • 身份认证JWT校验 │ │ • 请求限流per-user QPS50 │ │ • AB测试分流30%流量到v2模型 │ │ • OpenTelemetry链路追踪注入 │ └─────────────────────────────────────────────────────────────┘ ↓ gRPC ┌─────────────────────────────────────────────────────────────┐ │ Preprocessing Gateway (Custom Go Service) │ │ • 特征标准化调用Feature Store REST API │ │ • 输入格式转换JSON → Protobuf │ │ • 异常检测空值/非法字符过滤返回400而非500 │ │ • 缓存热点请求RedisTTL10s │ └─────────────────────────────────────────────────────────────┘ ↓ gRPC ┌─────────────────────────────────────────────────────────────┐ │ Model Serving Layer (Triton on Kubernetes) │ │ • Triton Server PodGPU节点亲和性调度 │ │ • 多模型仓库/models/fraud_v1, /models/recommender_v3 │ │ • 动态批处理max_queue_delay_microseconds1000 │ │ • GPU实例切分instance_group[{kind: KIND_GPU, count: 2}])│ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ Observability Governance Stack │ │ • Prometheus采集Triton内置metricsinfer_request_success, gpu_used_memory│ │ • Grafana看板模型延迟热力图、GPU显存使用率趋势 │ │ • Loki结构化日志request_id, model_name, latency_ms │ │ • Jaeger端到端链路从API网关到Triton的完整耗时分解 │ └─────────────────────────────────────────────────────────────┘这个架构的核心思想是让专业的人做专业的事。API网关管流量预处理网关管数据质量Triton管模型计算K8s管资源Prometheus管观测。任何一层出问题都不会导致整条链路雪崩。3. 核心细节解析与实操要点从模型打包到GPU调度的硬核细节3.1 Triton模型仓库的目录结构与配置文件详解Triton通过严格的目录结构识别模型一个典型的风控模型fraud_v1仓库结构如下/models/ └── fraud_v1/ ├── config.pbtxt # 必须定义模型元信息 ├── 1/ # 版本号目录数字越大越新 │ └── model.onnx # ONNX格式模型文件推荐跨框架兼容 └── 2/ └── model.plan # TensorRT引擎文件需提前编译config.pbtxt是灵魂它决定了Triton如何加载和运行模型。以下是我们在生产环境中使用的精简版配置已去除注释实际部署必须保留name: fraud_v1 platform: onnxruntime_onnx max_batch_size: 128 input [ { name: input_ids data_type: TYPE_INT64 dims: [ 128 ] }, { name: attention_mask data_type: TYPE_INT64 dims: [ 128 ] } ] output [ { name: output data_type: TYPE_FP32 dims: [ 2 ] } ] dynamic_batching [ { max_queue_delay_microseconds: 1000 } ] instance_group [ { kind: KIND_GPU count: 2 } ]注意max_batch_size: 128并非指单次请求的最大batch size而是Triton动态批处理能接受的最大合并batch size。实际请求中客户端发送单条样本shape[1,128]Triton会等待最多1000微秒攒够128条再一起送入GPU。这个值需要根据业务SLA调整金融风控要求P95延迟100ms我们设为128而离线报表生成可接受2秒延迟我们设为1024以最大化吞吐。count: 2表示在单张GPU卡上启动2个模型实例它们共享GPU显存但拥有独立的CUDA stream互不阻塞。实测发现对于A100count2时GPU利用率稳定在75%-82%count3则因显存碎片化导致OOM概率上升40%。3.2 预处理网关的Go实现为什么不用Python预处理网关承担着特征拉取、格式转换、异常过滤等任务看似简单但我们坚持用Go重写而非沿用Python生态。原因有三第一性能确定性Python的GIL全局解释器锁在高并发HTTP请求下CPU密集型任务如JSON解析、Protobuf序列化无法并行QPS上限约3500Go的goroutine无锁调度在同等硬件下轻松达到12000 QPS且P99延迟波动小于±5ms。第二内存可控性Python的垃圾回收GC在突发流量下可能触发STWStop-The-World导致延迟毛刺Go的三色标记GC在1.19版本已实现亚毫秒级停顿实测在10K QPS下无可见毛刺。第三依赖极简一个Go二进制文件15MB包含所有依赖Docker镜像大小仅28MBAlpine基础镜像而Python方案需打包Conda环境、PyTorch、Requests等镜像常超1.2GB拉取时间从2秒延长至47秒严重影响CI/CD效率。核心代码片段特征拉取与缓存// 使用Redis Pipeline批量拉取特征降低网络RTT func (g *Gateway) fetchFeatures(ctx context.Context, req *pb.InferenceRequest) (*featureData, error) { // 1. 从请求中提取user_id和item_id userID : req.GetUserID() itemID : req.GetItemID() // 2. 构建Redis key并Pipeline查询 cacheKey : fmt.Sprintf(features:%s:%s, userID, itemID) pipe : g.redisClient.Pipeline() pipe.Get(ctx, cacheKey) pipe.TTL(ctx, cacheKey) // 3. 执行Pipeline若缓存命中直接返回 cmders, err : pipe.Exec(ctx) if err ! nil { return nil, err } cached, ok : cmders[0].(*redis.StringCmd).Val() if ok cached ! { var feats featureData json.Unmarshal([]byte(cached), feats) return feats, nil } // 4. 缓存未命中调用Feature Store REST API带熔断 resp, err : g.featureClient.Get(fmt.Sprintf(/v1/features?user%sitem%s, userID, itemID)) if err ! nil || resp.StatusCode ! 200 { return nil, fmt.Errorf(feature store call failed: %v, err) } // 5. 解析响应并写入缓存TTL10s避免陈旧特征 var data featureData json.NewDecoder(resp.Body).Decode(data) g.redisClient.Set(ctx, cacheKey, data, 10*time.Second) return data, nil }实操心得缓存TTL设为10秒是经过AB测试验证的平衡点。设为1秒缓存命中率仅32%大量请求穿透到Feature Store设为60秒用户行为变更后特征更新延迟过大导致模型误判率上升0.8%。此外Pipeline的使用将Redis RTT从3次降低到1次QPS提升2.1倍。3.3 Kubernetes GPU资源调度的关键YAML配置在K8s中调度GPU不是简单加resources.limits.nvidia.com/gpu: 1必须配合节点亲和性Node Affinity和容忍度Toleration才能稳定运行。以下是Triton Pod的生产级YAML核心段apiVersion: v1 kind: Pod metadata: name: triton-fraud-v1 spec: containers: - name: triton-server image: nvcr.io/nvidia/tritonserver:23.06-py3 resources: limits: nvidia.com/gpu: 1 # 申请1块GPU memory: 16Gi # 内存限制防止OOM Killer误杀 cpu: 8 # CPU配额保障数据搬运线程不被饿死 requests: nvidia.com/gpu: 1 memory: 12Gi cpu: 4 env: - name: NVIDIA_VISIBLE_DEVICES value: 0 # 显式指定GPU索引避免Triton扫描所有设备 - name: TRITON_SERVER_FLAGS value: --model-repository/models --strict-model-configfalse volumeMounts: - name: models mountPath: /models volumes: - name: models persistentVolumeClaim: claimName: triton-models-pvc tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: nvidia.com/gpu.present operator: In values: [true] - key: cloud.google.com/gke-nodepool # GKE示例AWS用kops.amazonaws.com/instancegroup operator: In values: [gpu-pool]关键细节说明NVIDIA_VISIBLE_DEVICES: 0是强制要求。Triton默认会尝试访问节点上所有GPU设备但在多租户GPU节点如1张A100被3个Pod共享上这会导致设备权限冲突和CUDA初始化失败。memory: 16Gi的设置源于一次惨痛教训某次模型更新后Triton在加载ONNX模型时因内存不足触发OOM Killer进程被粗暴终止。我们将内存limit设为显存容量的2倍A100显存80GB系统内存128GB既保证模型加载空间又留出缓冲。--strict-model-configfalse允许Triton在config.pbtxt缺失时自动推断模型输入输出极大简化CI/CD流程——我们的自动化流水线在模型提交后会自动生成config.pbtxt并触发部署此参数避免了因配置生成bug导致的部署中断。3.4 Prometheus监控指标的定制化采集与告警规则Triton内置了丰富的Prometheus metrics但开箱即用的指标粒度太粗无法定位真实问题。我们通过/v2/metrics端点采集并在Prometheus中配置了以下关键relabel规则和告警# Prometheus scrape config for Triton - job_name: triton static_configs: - targets: [triton-service:8002] # Triton的metrics端口 metric_relabel_configs: - source_labels: [__name__] regex: nv_gpu_(.*) # 重命名GPU指标添加model标签 target_label: __name__ replacement: triton_gpu_$1 - source_labels: [__name__, model_name] regex: triton_gpu_(.*);(.*) target_label: model_name replacement: $2基于此我们定义了三条黄金告警规则已在Grafana中验证告警名称PromQL表达式触发条件处理建议TritonHighLatencyhistogram_quantile(0.95, sum(rate(triton_inference_request_duration_seconds_bucket[5m])) by (le, model_name)) 0.15P95延迟 150ms持续5分钟检查GPU显存是否溢出triton_gpu_used_memory_bytes或动态批处理队列是否积压triton_inference_queue_duration_seconds_sumTritonModelLoadFailuresum(triton_model_load_failure_total) by (model_name) 0模型加载失败次数0登录Pod检查/models/{model}/config.pbtxt语法或ONNX模型是否损坏onnx.checker.check_modelGPUUtilizationLowavg(triton_gpu_utilization_ratio) by (instance) 0.3GPU平均利用率30%持续10分钟检查HPA配置是否过激Pod副本过多或客户端请求量是否骤降需人工确认是否为正常低峰期实操心得triton_inference_request_duration_seconds_bucket是直方图指标必须用histogram_quantile函数计算分位数。我们曾误用rate()直接计算导致告警完全失真。另外triton_gpu_utilization_ratio的值域是0-1但Triton文档未明确说明我们通过curl http://triton:8002/v2/metrics实际抓取数据并对比nvidia-smi输出才确认其准确性。这种“动手验证”的习惯是避免被文档误导的关键。4. 实操过程与核心环节实现从零搭建一个可上线的Triton服务4.1 模型导出PyTorch → ONNX → TensorRT的三步提效模型服务化的起点是模型格式。我们坚持“训练用PyTorch服务用ONNX/TensorRT”的策略原因在于PyTorch的.pt文件包含Python字节码无法跨语言调用而ONNX是开放标准Triton、ONNX Runtime、TVM等所有主流推理引擎都支持。以下是生产环境的标准导出流程Step 1PyTorch模型导出为ONNX含动态轴声明import torch import torch.onnx # 假设model是训练好的PyTorch模型dummy_input是典型输入 dummy_input torch.randint(0, 1000, (1, 128), dtypetorch.long) # input_ids attention_mask torch.ones((1, 128), dtypetorch.long) # 导出ONNX关键参数 torch.onnx.export( model, (dummy_input, attention_mask), fraud_model.onnx, export_paramsTrue, opset_version15, # ONNX Opset版本Triton 23.06支持15 do_constant_foldingTrue, input_names[input_ids, attention_mask], output_names[output], dynamic_axes{ # 声明动态维度允许变长输入 input_ids: {0: batch_size, 1: seq_len}, attention_mask: {0: batch_size, 1: seq_len}, output: {0: batch_size} } )注意dynamic_axes是核心。若不声明Triton会将模型视为固定shape当客户端发送batch_size32的请求时会报错expected tensor of shape [1,128] but got [32,128]。我们曾因漏掉此参数在灰度发布时导致503错误率飙升。Step 2ONNX模型优化ONNX Simplifier原始ONNX模型常包含冗余算子如Identity、Cast影响推理速度。使用onnxsim工具简化pip install onnx-simplifier python -m onnxsim fraud_model.onnx fraud_model_sim.onnx实测简化后模型体积减少35%Triton加载时间缩短22%P50延迟下降8ms。Step 3ONNX → TensorRT引擎利用Triton内置编译器Triton 23.06起内置TensorRT编译器无需单独安装TRT。在config.pbtxt中添加TensorRT后端配置name: fraud_v1 platform: tensorrt_plan max_batch_size: 128 ... instance_group [ { kind: KIND_GPU count: 2 } ] optimization { execution_accelerators { gpu_execution_accelerator: [ { name: tensorrt parameters: { key: precision_mode value: FP16 } } ] } }然后将fraud_model_sim.onnx放入/models/fraud_v1/1/Triton启动时会自动编译为model.plan。编译过程耗时约3-5分钟A100但换来的是FP16精度下2.3倍的吞吐提升。4.2 Triton服务启动与健康检查的Shell脚本自动化部署离不开健壮的启动脚本。我们编写了start_triton.sh集成健康检查与失败重试#!/bin/bash # 启动Triton服务并等待其就绪 TRITON_CMDtritonserver \ --model-repository/models \ --http-port8000 \ --grpc-port8001 \ --metrics-port8002 \ --strict-model-configfalse \ --log-verbose1 echo Starting Triton server... $TRITON_CMD /var/log/triton.log 21 # 等待Triton HTTP端口就绪最多60秒 for i in {1..60}; do if curl -f http://localhost:8000/v2/health/ready /dev/null 21; then echo Triton is ready! exit 0 fi sleep 1 done echo ERROR: Triton failed to start within 60 seconds exit 1实操心得/v2/health/ready端点检查的是Triton主进程和所有模型加载状态比单纯检查端口开放更可靠。我们曾遇到端口已监听但模型加载失败的情况若只用nc -z localhost 8000会误判服务健康。此外--log-verbose1开启详细日志对排查config.pbtxt语法错误、CUDA初始化失败等底层问题至关重要日志中会明确打印Failed to load model fraud_v1: ...及具体原因。4.3 客户端gRPC调用的Python实现与连接池管理客户端是服务的最终使用者其稳定性直接影响用户体验。我们采用gRPC而非HTTP因为gRPC的二进制协议Protobuf比JSON轻量50%且支持流式传输和连接复用。关键代码如下import grpc import numpy as np import tritonclient.grpc as grpcclient from tritonclient.utils import InferenceServerException, np_to_triton_dtype class TritonClient: def __init__(self, urllocalhost:8001): # 使用连接池避免频繁创建销毁连接 self._channel grpc.insecure_channel( url, options[ (grpc.max_send_message_length, 100 * 1024 * 1024), # 100MB (grpc.max_receive_message_length, 100 * 1024 * 1024), (grpc.keepalive_time_ms, 30000), # 30秒心跳 (grpc.keepalive_timeout_ms, 10000), (grpc.http2.max_pings_without_data, 0) ] ) self._client grpcclient.InferenceServerClient( urlurl, verboseFalse, sslFalse, root_certificatesNone, private_keyNone, certificate_chainNone ) def predict(self, input_ids, attention_mask): inputs [] # 将numpy数组转换为Triton输入 inputs.append(grpcclient InferInput( input_ids, input_ids.shape, np_to_triton_dtype(input_ids.dtype) )) inputs.append(grpcclient InferInput( attention_mask, attention_mask.shape, np_to_triton_dtype(attention_mask.dtype) )) inputs[0].set_data_from_numpy(input_ids) inputs[1].set_data_from_numpy(attention_mask) outputs [grpcclient InferRequestedOutput(output)] try: # 设置超时避免请求无限挂起 result self._client.infer( model_namefraud_v1, inputsinputs, outputsoutputs, client_timeout5.0 # 5秒超时 ) return result.as_numpy(output) except InferenceServerException as e: # 记录详细错误便于定位 logger.error(fTriton inference failed: {e}) raise # 使用示例 client TritonClient(triton-service.default.svc.cluster.local:8001) pred client.predict( input_idsnp.array([[101, 2003, 102]], dtypenp.int64), attention_masknp.array([[1, 1, 1]], dtypenp.int64) )注意事项grpc.keepalive_time_ms和grpc.keepalive_timeout_ms是关键。在K8s Service Mesh如Istio环境下空闲连接会被Sidecar代理主动关闭默认超时是15分钟。若不配置keepalive客户端在连接空闲后首次请求会遭遇StatusCode.UNAVAILABLE错误。我们通过30秒心跳确保连接始终活跃。client_timeout5.0的设置也经过AB测试设为10秒P99延迟达标但失败率0.2%设为3秒失败率升至1.8%5秒是平衡点。4.4 灰度发布与AB测试的Kubernetes实践模型更新不能“一刀切”必须灰度。我们利用Kubernetes的Service和EndpointSlice实现细粒度流量控制# Step 1: 创建两个Deployment分别运行v1和v2模型 apiVersion: apps/v1 kind: Deployment metadata: name: triton-fraud-v1 spec: selector: matchLabels: app: triton-fraud version: v1 template: metadata: labels: app: triton-fraud version: v1 spec: containers: - name: triton image: nvcr.io/nvidia/tritonserver:23.06-py3 # ... 其他配置 --- apiVersion: apps/v1 kind: Deployment metadata: name: triton-fraud-v2 spec: selector: matchLabels: app: triton-fraud version: v2 template: metadata: labels: app: triton-fraud version: v2 spec: containers: - name: triton image: nvcr.io/nvidia/tritonserver:23.06-py3 # ... 其他配置 # Step 2: 创建Service通过EndpointSlice关联Pod apiVersion: v1 kind: Service metadata: name: triton-fraud spec: ports: - port: 8001 targetPort: 8001 selector: app: triton-fraud # 此selector不匹配任何Pod由EndpointSlice接管然后通过kubectl patch动态修改EndpointSlice将流量按比例分发# 将10%流量导向v2通过修改EndpointSlice中的endpoints权重 kubectl patch endpointslices.discovery.k8s.io triton-fraud -p {endpoints:[{addresses:[10.244.1.10],conditions:{ready:true},topology:{kubernetes.io/hostname:node-1,topology.kubernetes.io/zone:us-west1-a}},{addresses:[10.244.1.11],conditions:{ready:true},topology:{kubernetes.io/hostname:node-1,topology.kubernetes.io/zone:us-west1-a}}]}实操心得Kubernetes原生Service不支持权重分流必须借助Istio或Linkerd等Service Mesh。但我们发现通过直接操作EndpointSliceK8s 1.21特性可以实现轻量级、无侵入的灰度。topology字段用于将流量绑定到特定节点确保v1和v2模型物理隔离避免GPU资源争抢。这种方式比Service Mesh更简单且无额外代理性能损耗。5. 常见问题与排查技巧实录那些凌晨三点的告警电话教会我的事5.1 问题速查表高频故障现象、根因与解决方案现象可能根因排查命令/步骤解决方案Triton Pod反复CrashLoopBackOffconfig.pbtxt语法错误如缺少name:kubectl logs triton-pod -c triton-server | grep error进入Pod用tritonserver --model-repository/models --strict-model-configtrue --log-verbose1手动启动查看精确错误行P95延迟突然升高200%GPU显存不足触发CUDA内存分配失败重试kubectl exec triton-pod -- nvidia-smi | grep Usedkubectl logs triton-pod | grep cudaErrorMemoryAllocation减少instance_group.count或增加memory.limit或启用TensorRT FP16量化客户端收到StatusCode.UNAVAILABLEgRPC连接被K8s kube-proxy或云厂商LB重置kubectl get endpoints triton-service检查Endpoint是否为空tcpdump -i any port 8001抓包分析检查Service Selector是否匹配Pod Label在Service上添加externalTrafficPolicy: Local避免SNAT模型加载成功但推理返回全零ONNX模型输入名与config.pbtxt中input.name不一致onnxruntime.InferenceSession(model.onnx).get_inputs()查看实际输入名用netron工具可视化ONNX模型确认输入输出节点名严格匹配config.pbtxtPrometheus采集不到Triton指标Triton metrics端口8002未在Service中暴露kubectl get service triton-service -o yaml | grep ports在Service YAML中添加- port: 8002, targetPort: 8002, name: metrics5.2 一次真实故障的完整复盘从告警到根治时间2023年11月17日凌晨2:14告警TritonHighLatency触发fraud_v1模型P95延迟达320ms阈值150ms初步排查kubectl top pods显示Triton Pod CPU使用率98%内存使用率85%GPU显存使用率92%A100 80GBkubectl logs triton