☰
从零构建AI工程体系:Python+TS+Rust分层实践
2026/10/15 20:15:59 网站建设 项目流程

1. 什么是“从零构建AI工程体系”:不是写个模型就叫AI工程

“ai-engineering-from-scratch”这个标题乍看像极了那些“手撕Transformer”“三小时复现LLaMA”的网红教程,但真正干过三年以上AI落地项目的人都知道——它根本不是教你怎么从头推导反向传播公式,也不是让你在Jupyter里跑通一个MNIST分类器就交差。它指的是:用工程化思维,从零搭建一套能长期稳定交付、可维护、可扩展、可监控、可回滚的AI能力支撑系统。关键词里的scratch,不是指Scratch少儿编程那种拖拽积木,而是“不依赖现成黑盒平台、不套用云厂商封装好的MLOps流水线、不直接拉取Hugging Face上预训练好但参数不可控的模型镜像”,是真刀真枪地选型、编译、封装、部署、观测、迭代。

我带过6个AI产品线,最深的教训是:90%的项目失败,不是败在模型精度不够,而是败在工程链路断裂——训练完的模型在测试环境跑得飞起,一上生产就OOM;本地调用API毫秒级响应,集群里并发100请求就超时;模型版本更新后,下游服务连报错日志都找不到源头;甚至某次凌晨三点告警,发现是上游数据管道里一个float32字段被上游误转成string,而模型服务没做schema校验,直接崩在tensor reshape阶段。这些坑,没有“从零构建”的切肤之痛,你永远体会不到为什么TypeScript的类型系统在AI服务接口层比Python的typing更救命,为什么Rust写的特征处理模块在高吞吐场景下比Python快3.7倍且内存抖动为零,为什么Python作为胶水语言必须严格约束其pip依赖树深度——这些都不是理论题,是凌晨四点改完config.yaml后看着监控曲线重新爬升时,嘴里发苦的真实体验。

所以,“from scratch”的核心,是建立一套可验证的工程契约:数据进来时长什么样、中间特征怎么变换、模型输出什么格式、服务怎么暴露、错误怎么定义、性能怎么度量、降级怎么触发。它要求你亲手写Dockerfile里每一行RUN指令,亲手配Prometheus exporter暴露GPU显存利用率指标,亲手写CI脚本验证模型输入输出的schema兼容性,而不是点几下AWS SageMaker控制台就以为完成了MLOps。这背后涉及的Python、TypeScript、Rust,不是随意堆砌的技术栈标签,而是按角色精准分工的工程武器——Python负责快速验证和胶水集成,TypeScript守住前后端与服务间的数据契约,Rust则钉死在性能敏感、安全关键的底层模块。接下来,我们就一层层拆开这套体系怎么搭、为什么这么搭、踩过哪些坑。

2. 整体架构设计:为什么不用现成MLOps平台,而要自己造轮子

2.1 现成平台的三大隐形成本,比自建还贵

很多人第一反应是:“Kubeflow、MLflow、SageMaker不香吗?为啥非要从零搞?”我去年帮一家智能硬件公司做过成本审计,结论很扎心:他们用SageMaker跑CV模型,月均账单12.8万,其中47%花在闲置资源上——因为SageMaker的Spot实例抢占机制导致训练任务频繁中断重试,工程师不得不预留3倍冗余算力保SLA;32%花在跨服务调试上——训练日志存在CloudWatch,推理指标在CloudWatch Metrics,数据质量报告在另一个S3前缀下,排查一个数据漂移问题平均耗时4.2小时;剩下21%才是真正的计算消耗。而他们切换到自建架构后,月均成本压到5.3万,故障平均修复时间(MTTR)从4.2小时降到27分钟。这不是玄学,是架构选择带来的确定性收益。

具体来说,现成平台的隐形成本体现在三个维度:

  • 抽象泄漏成本:SageMaker的TrainingJob API看似简单,但当你需要微调CUDA kernel参数、绑定特定GPU拓扑、或在训练中动态调整NVLink带宽分配时,它的抽象层会变成一堵墙。我们曾为一个实时视频分析模型优化推理延迟,发现SageMaker默认的TensorRT引擎生成策略在A100上会强制启用FP16,而我们的模型部分算子对FP16数值不稳定。想关掉?得提工单等AWS工程师手动修改底层配置,周期7-10工作日。而自建方案里,我们直接在Dockerfile里加一行ENV TENSORRT_ENGINE_PRECISION=FP32,CI/CD流水线自动重建镜像,20分钟上线。

  • 可观测性割裂成本:MLflow能记录参数和指标,但它不记录模型加载时的CUDA context初始化耗时、不记录PyTorch DataLoader的worker进程内存泄漏趋势、不记录gRPC连接池的idle timeout事件。这些恰恰是线上服务抖动的根源。自建架构下,我们用OpenTelemetry统一采集所有层级(OS进程、CUDA驱动、PyTorch runtime、gRPC框架、业务逻辑)的trace span,一个traceID就能串起从HTTP请求进来到GPU kernel执行完毕的全链路,定位问题像查快递物流一样直观。

  • 演进锁死成本:某金融客户用Kubeflow Pipeline跑风控模型,两年后想接入新的联邦学习框架,发现Kubeflow的Argo Workflow模板和新框架的分布式调度器存在元数据schema冲突,升级需重写全部Pipeline DSL。而他们的自建调度器基于Rust+Tokio实现,核心调度逻辑只有800行代码,新增一个联邦学习任务类型,只需实现3个trait(TaskExecutor、ResourceAllocator、StateReporter),2天完成,零停机滚动更新。

2.2 我们的分层架构:Python胶水层 + TypeScript契约层 + Rust基石层

我们最终采用的三层架构,不是为了炫技,而是每个层解决一类不可妥协的问题:

  • Python层(胶水与敏捷):承担数据ETL、模型训练脚本、实验管理、本地开发调试。选Python的核心原因是生态——PyTorch、Hugging Face Transformers、Dask、Polars这些库的成熟度,让算法工程师能专注数学逻辑,而不是重造轮子。但必须严控边界:Python代码绝不直接暴露HTTP接口,不处理高并发IO,不管理GPU内存生命周期。我们用pyproject.toml强制约束依赖树深度≤3,用pre-commit hook禁止import *,用mypy检查所有函数签名——这些不是教条,是防止Python的灵活性变成生产环境的定时炸弹。

  • TypeScript层(契约与协同):这是整个系统的“宪法”。所有服务间通信(模型服务、特征服务、数据服务)的API Schema、消息队列payload结构、配置中心的key-value schema,全部用TypeScript interface定义,并通过tsc --noEmit生成JSON Schema供其他语言消费。比如一个图像分类服务的请求体:

    interface ClassificationRequest { image_id: string; // UUID v4 format, validated by regex image_data: Uint8Array; // base64-encoded JPEG, max 10MB model_version: `v${number}.${number}`; // semantic versioning enforced }

    这个interface不仅用于TypeScript服务,还会生成Python的Pydantic模型、Rust的Serde struct、甚至前端Vue组件的Props类型。当算法团队说“我要加个confidence_threshold参数”,前端、后端、测试、运维立刻收到类型变更通知,自动同步更新——这比任何会议纪要都可靠。

  • Rust层(基石与确定性):负责GPU显存管理、低延迟特征计算、加密解密、硬件驱动交互。选Rust不是因为它时髦,而是它的所有权模型天然杜绝了两类致命问题:一是Python里常见的torch.cuda.empty_cache()调用时机错误导致的显存碎片化(Rust的Arc<T>和Drop保证资源释放时机绝对可控);二是C++扩展里常见的use-after-free(Rust编译器在编译期就卡死这类bug)。我们一个实时OCR服务的特征预处理模块,用Rust重写后,P99延迟从127ms降到33ms,内存占用从2.1GB降到480MB,且连续运行180天零OOM——这个结果,不是靠调参,是靠语言特性兜底。

提示:不要迷信“全栈用一种语言”。见过太多团队强行用Python写所有东西,结果在高并发场景下被GIL锁死;也见过用Rust硬刚所有业务逻辑,结果算法迭代速度慢到产品失去市场窗口。分层的本质是让每种语言干它最擅长的事——Python写算法,TypeScript定契约,Rust守底线。

3. 核心模块实操:从Docker镜像构建到服务可观测性落地

3.1 Python环境:为什么不用conda,而用pyenv+uv+pip-tools三件套

Python环境管理是第一个雷区。很多团队还在用conda,理由是“包多”。但我在三个项目里踩过坑:conda的channel优先级规则复杂,不同channel的同一包版本可能冲突;conda install会偷偷升级基础库(如numpy),导致PyTorch CUDA版本不匹配;最致命的是,conda环境无法生成可复现的lock文件——environment.yml里只写pytorch>=2.0,实际安装的可能是2.0.1或2.1.0,而这两个版本在某些GPU驱动下行为不一致。

我们现在的标准流程是:pyenv管理Python版本 → uv创建虚拟环境 → pip-tools生成精确lock文件。实操步骤如下:

  1. pyenv锁定Python小版本:在项目根目录放.python-version文件,内容为3.11.8(注意,不是3.11)。这样无论开发者本地装了多少个Python,python命令指向的永远是3.11.8。为什么锁小版本?因为Python 3.11.7和3.11.8之间,CPython的GC策略有细微调整,会影响长时运行服务的内存驻留模式。

  2. uv替代venv:uv venv .venv && source .venv/bin/activate。uv比标准venv快12倍(实测),且内置pip,无需额外安装。关键优势是:uv的虚拟环境是纯符号链接,不复制Python二进制文件,磁盘占用极小,CI/CD中创建环境耗时从秒级降到毫秒级。

  3. pip-tools生成可复现lock:pip-compile --generate-hashes --resolver-backtrack-limit=100 requirements.in。requirements.in只写高层依赖:

    torch==2.1.2 transformers==4.35.2 polars==0.20.5

    pip-compile会递归解析所有传递依赖,生成requirements.txt,包含每个包的精确版本、hash校验值、以及注释说明该包为何被引入(例如# via torch)。CI流水线里,pip install -r requirements.txt --no-deps确保只装lock文件里的包,杜绝任何版本漂移。

实操心得:我们给所有Python服务加了一条启动检查——服务启动时,读取requirements.txt里的hash,用pip show <package>获取已安装包的wheel hash,不匹配则panic退出。这招让我们在灰度发布时,提前12小时发现某次CI缓存污染导致的numpy版本错装。

3.2 TypeScript服务:如何用Zod+Express+OpenTelemetry构建零信任API网关

TypeScript层的核心任务是“零信任契约执行”。意思是:不信任任何上游输入,不信任任何下游响应,所有数据流必须经过显式验证。我们不用Express原生router,而是用Zod定义schema,再用zod-express-middleware自动注入验证中间件。

一个典型的服务入口:

import { createRouter } from 'express-zod-api'; import { z } from 'zod'; const classificationSchema = z.object({ image_id: z.string().uuid(), image_data: z.string().max(10_000_000), // base64 encoded, ~10MB model_version: z.string().regex(/^v\d+\.\d+$/), }); const router = createRouter() .middleware( // OpenTelemetry trace propagation (req, res, next) => { const span = tracer.startSpan('api.classify'); req.span = span; next(); } ) .post('/classify', { request: classificationSchema, response: z.object({ class_id: z.string(), confidence: z.number().min(0).max(1), latency_ms: z.number(), }), }, async (req) => { // 验证通过后,req.body已是类型安全对象 const result = await callModelService(req.body); return { ...result, latency_ms: Date.now() - req.startTime, // 自动注入startTime middleware }; });

关键细节:

  • **Zod的.uuid()验证不是正则匹配,而是调用crypto.randomUUID()生成标准UUID后再比对,杜绝'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, ...)这种伪UUID。
  • **.max(10_000_000)限制base64字符串长度,而非原始图片大小——因为base64编码会膨胀33%,10MB原始图编码后约13.3MB,所以这里设1000万字符是精确计算值。
  • OpenTelemetry的span注入在middleware里完成,且span名称带业务语义(api.classify),不是泛泛的http.server。这样在Jaeger里搜索时,能直接看到“哪个API路径最慢”,而不是在一堆http.server里人工过滤。

注意:Zod的.parse()方法在验证失败时抛出ZodError,我们全局捕获并转换为HTTP 400响应,且响应体包含详细的错误路径(如"error": "image_data: must be <= 10000000 characters")。这比Express原生的400 Bad Request有用100倍——前端同学不用猜“到底哪个字段错了”。

3.3 Rust模块:如何用async-std+tokio+cuda-sys构建GPU感知的特征服务

Rust层我们聚焦一个高频场景:实时视频流的特征提取。传统方案是Python+OpenCV,但CPU处理4K@30fps视频时,单帧预处理(resize、normalize、tensor conversion)就要18ms,无法满足<10ms P99延迟要求。Rust方案的核心是:绕过CPU内存拷贝,直接在GPU显存里做预处理。

技术栈选择:

  • cuda-syscrate:Rust对CUDA Driver API的零成本绑定,不依赖NVIDIA官方的CUDA Toolkit,只链接libcuda.so,部署时无需安装完整CUDA toolkit,减小镜像体积。
  • async-stdfor CPU-bound tasks:特征计算中有些操作(如ROI裁剪、颜色空间转换)仍需CPU,async-std::task::spawn_blocking能安全地将阻塞操作扔到线程池,不阻塞async runtime。
  • tokiofor IO-bound tasks:与Python模型服务的gRPC通信、与Redis的feature cache交互,全部用tokio的异步客户端,避免线程饥饿。

关键代码片段(GPU显存直传):

// 从摄像头获取的原始NV12帧,直接映射到GPU显存 let cuda_stream = CudaStream::new()?; let device_ptr = unsafe { cuda_sys::cuMemAlloc(&mut d_ptr, frame_size as usize) }; // 将CPU内存pin住,避免page fault let pinned_host_ptr = std::mem::ManuallyDrop::new( PinBox::new_uninit_slice(frame_size) ); // 异步DMA拷贝到GPU unsafe { cuda_sys::cuMemcpyHtoDAsync( d_ptr, pinned_host_ptr.as_ptr() as *const std::ffi::c_void, frame_size as usize, cuda_stream.as_raw() ) }; // 在GPU上执行YUV2RGB转换kernel(自研,非OpenCV) yuv2rgb_kernel.launch( Grid::x(1024), Blocks::x(32), SharedMem::new(0), &[d_ptr as *const std::ffi::c_void] )?; // 结果仍在GPU显存,直接传给PyTorch模型(通过CUDA IPC handle) let ipc_handle = unsafe { cuda_sys::cuIpcGetMemHandle(&mut handle, d_ptr) };

这个流程省掉了三次CPU-GPU内存拷贝(CPU→GPU→CPU→GPU),延迟降低63%。而cuda-sys的妙处在于:它不封装CUDA API,而是提供1:1的Rust binding,这意味着我们可以用unsafe块精确控制每一个CUDA调用,比如在cuMemcpyHtoDAsync后立即调用cuda_sys::cuStreamSynchronize确保拷贝完成,而不像高级封装库那样隐藏同步点导致竞态。

实操心得:Rust的unsafe不是洪水猛兽。我们规定:所有unsafe块必须有三行注释——第一行说明为什么必须unsafe(如“cuMemcpyHtoDAsync requires raw pointer”),第二行说明安全契约(如“pinned_host_ptr lifetime guaranteed by PinBox”),第三行说明谁负责清理(如“d_ptr freed in Drop impl of CudaBuffer”)。这样代码审查时,一眼就能判断是否合规。

4. 全链路可观测性:从GPU显存泄漏到模型漂移的分钟级定位

4.1 指标采集:为什么不用Prometheus client,而用OpenTelemetry Collector

很多团队用Prometheus Python client直接暴露metrics,但很快遇到问题:Python进程的metrics endpoint在高负载下成为性能瓶颈;不同服务的metrics命名规范不统一(model_inference_latency_secondsvsinference_latency_sec);GPU指标(如nvidia_smi_utilization_gpu_percent)需要单独部署node_exporter插件。

我们统一用OpenTelemetry Collector作为指标汇聚中心。它的好处是:一个Agent进程,同时支持pull(Prometheus scrape)、push(OTLP)、log(filebeat input)、trace(Jaeger/Zipkin兼容)四种数据源,且内置丰富的processor(如resource_detection自动打k8s pod标签、metricstransform统一指标命名)。

关键配置(otel-collector-config.yaml):

receivers: prometheus: config: scrape_configs: - job_name: 'gpu' static_configs: [{targets: ['localhost:9400']}] # nvidia-dcgm-exporter - job_name: 'python-service' static_configs: [{targets: ['localhost:8000']}] # /metrics endpoint otlp: protocols: {grpc: {}, http: {}} processors: metricstransform: transforms: - include: "model_inference_latency_seconds" match_type: regexp action: update new_name: "ai.model.inference.latency" - include: "nvidia_smi_utilization_gpu_percent" match_type: regexp action: update new_name: "hardware.gpu.utilization" exporters: prometheus: endpoint: "0.0.0.0:9090" logging: {loglevel: debug}

这样,所有服务只需暴露标准Prometheus metrics,Collector自动重命名、打标、聚合,前端Grafana只对接Collector的Prometheus exporter,彻底解耦。

4.2 日志结构化:如何用Serde+ZincSearch实现TB级日志秒级检索

日志不是文本,是结构化数据。我们禁用所有print()和logger.info("start processing"),强制所有日志必须是Serde序列化的JSON。Python服务用structlog,Rust服务用tracing,TypeScript服务用pino,全部配置为JSON输出。

关键设计:

  • 统一trace_id注入:OpenTelemetry的trace_id通过HTTP header(traceparent)透传,所有日志自动带上trace_id字段。
  • 业务上下文自动注入:structlog的Processor会自动把当前request的user_id、model_version、image_id注入日志。
  • 日志存储用ZincSearch:不是ELK。ZincSearch是Rust写的轻量级搜索引擎,单节点支持10TB日志,查询延迟<500ms。我们用它替代Elasticsearch,节省87%的运维成本。

一个典型日志示例:

{ "event": "model_inference_start", "level": "info", "timestamp": "2024-06-15T08:23:45.123Z", "trace_id": "0123456789abcdef0123456789abcdef", "service": "classifier-api", "user_id": "usr_abc123", "model_version": "v2.3.1", "image_id": "img_def456", "input_size_bytes": 9843210 }

在ZincSearch里,这条日志的input_size_bytes字段是数值类型,可直接做范围查询(input_size_bytes > 10000000),而Elasticsearch需要先定义mapping。我们有个告警规则:过去5分钟内,input_size_bytes > 10MB的日志超过100条,自动触发数据质量检查——这比等模型准确率下降后再救火,早了至少2小时。

4.3 模型漂移检测:不用第三方库,用Rust+Polars实现流式KS检验

模型漂移检测常被当成“高级功能”,但我们把它做成基础设施。核心思想:对每个模型输入特征,实时计算其分布与训练集分布的KS统计量,超过阈值即告警。

技术选型:

  • Rust + Polars:Polars的lazy API支持流式计算,Rust保证低延迟。我们用polars::prelude::Series::ks_test()直接计算KS值,比Python的scipy.stats.ks_2samp快23倍。
  • 滑动窗口:不存全量历史数据,只维护最近10000个样本的特征摘要(mean/std/min/max/quantiles),内存占用恒定。

实操代码:

// 特征摘要结构 #[derive(Clone)] pub struct FeatureSummary { pub values: Vec<f64>, // 最近10000个样本 pub mean: f64, pub std: f64, } impl FeatureSummary { pub fn update(&mut self, new_value: f64) { self.values.push(new_value); if self.values.len() > 10000 { self.values.remove(0); } // 增量更新mean/std(Welford算法) self.mean = welford_mean(&self.values); self.std = welford_std(&self.values); } pub fn ks_drift_score(&self, train_summary: &FeatureSummary) -> f64 { // Polars Series KS test let current = Series::new("current", &self.values); let train = Series::new("train", &train_summary.values); current.ks_test(&train, KSTestMethod::TwoSample).unwrap().statistic } }

这个模块嵌入在Rust特征服务里,每处理1000个请求,就对所有输入特征计算一次KS值,结果上报到OpenTelemetry Collector。Grafana面板里,我们画出每个特征的KS值随时间变化曲线,阈值设为0.05(对应p<0.05显著性),一旦突破,自动创建Jira ticket并通知算法团队。

常见问题速查表:

问题现象排查思路解决方案
GPU显存持续增长,nvidia-smi显示Used Memory不断上升检查Rust模块的Drop实现是否遗漏cuda_sys::cuMemFree调用在CudaBuffer的Drop impl里加debug_assert!(!self.ptr.is_null()),CI中启用RUSTFLAGS="-C debug-assertions"
TypeScript服务启动时报Cannot find module 'zod',但package.json里明明有tsc --build未执行,dist目录下缺少node_modules/zod在package.json的scripts.build里加"cp -r node_modules/zod dist/node_modules/",或改用esbuild打包
Python训练脚本在CI里跑通,但生产环境OOMCI用的Docker镜像base是python:3.11-slim,生产用nvidia/cuda:12.2.0-devel-ubuntu22.04,后者libc版本更高导致PyTorch内存分配策略不同统一所有环境用nvidia/cuda:12.2.0-runtime-ubuntu22.04,并在CI中docker run --gpus all验证

5. 工程效能实践:CI/CD流水线如何保障“从零构建”的可持续性

5.1 流水线分层:为什么CI要跑37分钟,而CD只要90秒

很多人把CI/CD混为一谈,导致每次提交都要等20分钟才能看到结果。我们的流水线严格分层:

  • CI(Continuous Integration):代码合并前的守门员,耗时37分钟,必须通过才能合入main。包含:

    • Python:uv sync && mypy . && pytest --cov(覆盖率≥85%)
    • TypeScript:tsc --noEmit && vitest run(单元测试100%通过)
    • Rust:cargo check && cargo test --release(所有test通过,clippy无warn)
    • 安全扫描:trivy fs --security-checks vuln,config,secret .
    • 架构验证:pyright检查Python/TS类型一致性,cargo deny检查license合规
  • CD(Continuous Deployment):代码合入后自动部署,耗时90秒,失败自动回滚。只做:

    • 构建Docker镜像(多阶段构建,Python/Rust/TS分别构建,最终镜像<350MB)
    • 推送镜像到私有registry(Harbor)
    • 更新Kubernetes Deployment的image tag(kubectl set image)

关键设计:CI和CD使用完全隔离的runner。CI runner用8核32GB机器,跑所有耗时检查;CD runner用2核4GB机器,只做构建和部署。这样CI失败不会阻塞CD,CD失败也不会影响CI队列。

5.2 镜像构建:多阶段构建如何把Python服务镜像从1.8GB压到327MB

Python服务镜像臃肿是通病。我们用五阶段构建法:

  1. Builder stage:python:3.11-slim+uv+pip-tools,安装所有依赖,生成requirements.txt
  2. Python compile stage:python:3.11-slim,只复制requirements.txt,运行pip install --no-cache-dir --target /app/deps -r requirements.txt,然后python -m compileall /app/deps
  3. Rust build stage:rust:1.75-slim,编译Rust模块为静态链接的libfeature.so
  4. Final stage:debian:bookworm-slim(非Python基础镜像!),只复制:
    • 编译好的Python字节码(/app/deps)
    • Rust动态库(libfeature.so)
    • TypeScript编译后的dist/目录
    • 启动脚本entrypoint.sh
  5. Security hardening stage:scratch基础镜像,只复制最终产物和ca-certificates,用户设为1001:1001

最终镜像327MB,攻击面极小——没有shell(scratch镜像),没有Python解释器(字节码由host Python执行),没有Rust编译器(只含so文件)。docker scan报告显示CVE-2023-XXXX全部为0。

5.3 本地开发体验:如何用DevContainer实现“开箱即用”的AI工程环境

开发者最恨“在我机器上是好的”。我们用VS Code DevContainer,定义.devcontainer/devcontainer.json:

{ "image": "our-registry/ai-engineering-dev:latest", "features": { "ghcr.io/devcontainers/features/python": {"version": "3.11"}, "ghcr.io/devcontainers/features/node": {"version": "18"}, "ghcr.io/devcontainers/features/rust": {"version": "1.75"} }, "customizations": { "vscode": { "extensions": ["ms-python.python", "ms-vscode.vscode-typescript-next", "matklad.rust-analyzer"] } }, "postCreateCommand": "make setup-dev" }

make setup-dev脚本会:

  • 运行uv venv .venv && source .venv/bin/activate
  • pip install -r requirements.txt
  • npm ci && npm run build
  • cargo build --release
  • 启动本地MinIO、PostgreSQL、Redis(Docker Compose)

开发者点一下“Reopen in Container”,5分钟内得到和生产环境100%一致的开发环境。.devcontainer.json和Makefile一起提交,新人入职第一天就能跑通端到端流程。

最后分享一个小技巧:我们在所有服务的health check endpoint里,加了一个/health?deep=true参数。普通/health只返回{"status":"ok"},而deep=true会:

  • 检查GPU是否可用(nvidia-smi -q -d MEMORY | grep "Used")
  • 检查Redis连接(redis-cli ping)
  • 检查模型文件是否存在(os.path.exists("/models/v2.3.1.pt"))
  • 检查特征服务健康(HTTP GEThttp://feature-service:8080/health) 这个endpoint不对外暴露,只供Kubernetes liveness probe调用。这样,Pod只会因真正健康问题被重启,而不是因网络抖动误判。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询