1. 这不是“给小白的机器学习”,而是“让机器学习真正跑起来”的实操手册
“Machine Learning for Dummies: Deploy all the Things 🚀🚀”这个标题乍看像本轻松入门书,但实际它戳中了当前机器学习落地最痛的盲区——90%的教程教你怎么训练模型,剩下10%讲怎么把模型变成API,而真正卡住工程师、数据科学家和小团队的,是那被忽略的85%:从训练完一个Jupyter Notebook里的
.pkl
文件,到它在生产环境里扛住每秒200次请求、自动重试失败调用、记录可追溯的预测日志、随流量弹性伸缩、被监控告警盯死内存泄漏……这些事,没人手把手带你做,文档里只有一行命令,报错信息却像天书。我带过6个跨行业ML项目,从电商实时推荐到工厂设备振动异常检测,踩过所有坑:模型在本地准确率98%,一上服务器就OOM;Flask API跑着跑着CPU飙到99%,查了一周发现是Pickle反序列化时没设超时;Kubernetes里Pod反复CrashLoopBackOff,最后发现是PyTorch版本和CUDA驱动不兼容。这篇不是概念科普,不讲梯度下降推导,不画损失函数曲线。它是一份压缩了三年实战经验的部署流水线说明书,覆盖从单机Docker容器到云原生K8s集群的全路径。核心关键词——
模型服务化(Model Serving)
、
轻量级推理引擎(ONNX Runtime / Triton)
、
可观测性集成(Prometheus + Grafana)
、
CI/CD for ML(GitHub Actions + Docker Registry)
——全部围绕“让模型真正干活”展开。适合刚跑通第一个Scikit-learn分类器的新人,也适合被线上模型延迟抖动折磨得睡不着觉的SRE。你不需要懂Kubernetes源码,但要清楚为什么
livenessProbe
不能只检查端口;你不必手写Prometheus exporter,但得知道
model_inference_latency_seconds_bucket
这个指标名背后,藏着多少次凌晨三点的故障复盘。
2. 部署不是“最后一步”,而是贯穿模型生命周期的工程闭环
2.1 为什么90%的ML项目死在部署前夜?
很多人把部署理解成“模型训练完,找个地方放上去”。这是致命误区。部署不是终点,而是模型价值兑现的起点,更是整个ML生命周期中最暴露工程短板的环节。我见过太多项目:数据科学家在本地用
pandas.read_csv()
加载10GB CSV,训练完模型,交付给后端时只扔过去一个
.joblib
文件和一句“用
joblib.load()
就行”。结果开发同学一运行就报错——因为生产服务器没有安装
pyarrow
,而
pandas
读取Parquet格式时默认依赖它;又或者模型里用了
sklearn.preprocessing.StandardScaler
,但预处理代码没封装进推理函数,导致线上输入原始特征直接崩掉。这暴露了三个断层:
- 环境断层 :本地Python 3.9 + conda环境 vs 生产服务器CentOS 7 + system Python 2.7(别笑,真有);
-
数据断层
:训练时用
pd.read_parquet("s3://bucket/train/"),推理时硬编码pd.read_csv("/tmp/data.csv"),路径、格式、权限全错; -
契约断层
:模型输入是
np.array([1.2, 3.4, 5.6]),API接口却要求JSON{"feature_a": 1.2, "feature_b": 3.4},中间缺了字段映射逻辑。
真正的部署思维,是从模型设计第一天就开始的。比如,当你要选一个树模型时,别只看AUC,还要问:XGBoost的
.booster
对象能否被ONNX Runtime加载?LightGBM的
predict()
方法是否支持
raw_score=True
以兼容后续校准?这些细节决定了你半年后能不能用Triton统一管理所有模型。我坚持一个原则:
任何不能被Docker镜像固化、不能被curl命令验证、不能被Prometheus抓取指标的模型,都不算完成部署
。这不是苛刻,而是把“能跑”和“能用”划清界限。很多团队花三个月调参,却用三天仓促部署,结果上线首周故障率47%,用户投诉激增——这成本远高于前期多花一周设计可部署架构。
2.2 部署路径选择:不是“选工具”,而是“选与业务节奏匹配的复杂度”
面对TensorFlow Serving、Triton Inference Server、KServe、Seldon Core、BentoML、FastAPI+Uvicorn……新手常陷入工具焦虑。但我的经验是: 没有最好的工具,只有最不拖慢你业务验证速度的工具 。选择依据不是技术先进性,而是三个硬指标:团队熟悉度、迭代频率、失败容忍度。
-
MVP验证期(<2周) :用
FastAPI + Uvicorn + joblib.load()。别碰Docker,直接pip install -r requirements.txt && uvicorn app:app --reload。重点是快速暴露数据管道问题。我帮一家本地生鲜店做销量预测,第一天就发现他们ERP导出的CSV日期列名是"order_date",而模型训练用的是"date",这种低级错误在本地热重载下10分钟就能修复。此时上K8s是自杀——你连API返回400还是500都还没理清。 -
稳定交付期(2-8周) :切到
Docker + ONNX Runtime。把Scikit-learn/XGBoost模型转成ONNX格式(onnxmltools.convert_sklearn()),用ONNX Runtime推理,性能提升3-5倍且跨平台。镜像里只装onnxruntime和numpy,体积<100MB,启动<1秒。某金融风控项目用此方案,QPS从Flask的350压到Triton的2100,延迟P95从120ms降到22ms。关键点:ONNX不支持pandas操作,所有预处理必须用numpy重写,这倒逼你清理了训练代码里所有df.apply(lambda x: ...)的脏代码。 -
规模化生产期(>8周) :引入
Triton Inference Server。它不是“更高级的ONNX Runtime”,而是为高并发、多模型、动态批处理而生。比如你同时部署了图像分类(ResNet)、文本情感(BERT)、时序预测(LSTM)三个模型,Triton能用一个gRPC端口统一管理,自动做batching、GPU显存复用、模型热更新。我们一个工业质检系统,用Triton将三类缺陷检测模型吞吐量提升4.7倍,GPU利用率从32%拉到89%。但代价是配置复杂——config.pbtxt文件里要精确声明输入shape、动态batch范围、instance group数量。我建议:先用Triton官方quickstart脚本生成模板,再逐行改,别自己从零写。
提示:永远用“最小可行部署”倒推技术选型。如果业务方说“下周要给客户演示”,你就该选FastAPI;如果说“下季度要支撑百万用户”,才值得投入Triton。工具是杠杆,不是目的。
2.3 模型服务化的四大支柱:你漏掉任何一个,线上都会出事
一个健壮的模型服务,必须同时立住四根柱子,缺一不可。我把它叫“部署四象限”,每个象限对应一个生死线:
-
第一象限:可重复性(Reproducibility)
核心是“一次构建,处处运行”。不是pip freeze > requirements.txt,而是用pip-compile(来自pip-tools)生成锁定版本:pip-compile requirements.in --output-file requirements.txt。requirements.in只写scikit-learn>=1.0.0,requirements.txt会生成scikit-learn==1.2.2。这样本地和服务器用的绝对是同一版本。更进一步,用Dockerfile多阶段构建:第一阶段FROM python:3.9-slim装依赖,第二阶段FROM python:3.9-slim只COPY编译好的wheel包。镜像体积从1.2GB压到287MB,推送时间从8分钟缩到42秒。 -
第二象限:可观测性(Observability)
没有监控的模型服务就像没装刹车的车。必须埋三类指标:
(1) 基础设施层 :CPU/MEM/GPU-Utilization(用psutil或nvidia-smi采集);
(2) 服务层 :HTTP状态码分布、请求延迟(time.time()打点)、并发连接数;
(3) 模型层 :预测耗时(model.predict()耗时)、输入数据分布漂移(用Evidently计算feature_drift_p_value)。
我们用Prometheus Client库在FastAPI里暴露/metrics端点,Grafana看板里三个Tab页分别监控这三层。当某天model_inference_latency_seconds_count{model="fraud_v3", status="200"}突增,立刻关联process_cpu_seconds_total,发现是同事误提交了while True: time.sleep(0.1)循环——没有指标,这bug可能潜伏一周。 -
第三象限:弹性(Resilience)
模型不是神,会失败。常见场景:输入NaN、GPU显存不足、网络超时。必须设计熔断和降级。例如,用tenacity库重试:@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10)) def predict_with_retry(input_data): return model.run(input_data)更关键的是降级策略:当Triton返回
StatusCode.UNAVAILABLE,自动切到规则引擎(如if amount > 50000: return "high_risk")。某支付公司用此方案,在GPU节点宕机时,风控模型降级为规则引擎,交易通过率仅降0.3%,而非直接拒绝所有大额交易。 -
第四象限:可维护性(Maintainability)
模型要迭代,服务不能停。核心是“零停机更新”。Triton支持模型版本管理:models/my_model/1/model.onnx和models/my_model/2/model.onnx共存,通过model_repository_index.json控制激活版本。更新时,先上传V2,再发POST /v2/repository/models/my_model/load加载,最后POST /v2/repository/models/my_model/unload卸载V1。整个过程毫秒级,用户无感。我们曾用此方案,在黑色星期五峰值期间,将反欺诈模型从V1.2热更新到V1.3,QPS 12000下0丢包。
这四个象限不是并列关系,而是递进依赖:没有可重复性,可观测性就是假数据;没有可观测性,弹性策略就是瞎猜;没有弹性,可维护性就是空中楼阁。每次部署前,我必用这四象限 checklist 过一遍。
3. 从零到生产:一份可直接抄作业的端到端部署流程
3.1 环境准备:用Docker隔离一切不确定性
部署最大的敌人是“在我机器上是好的”。解决方案不是祈祷,而是用Docker消灭所有环境差异。别用
docker run -it python:3.9
交互式安装,必须写
Dockerfile
,且遵循最小化原则。
# 第一阶段:构建环境(含编译)
FROM python:3.9-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir pip-tools && \
pip-compile requirements.in --output-file requirements.txt && \
pip install --user --no-cache-dir -r requirements.txt
# 第二阶段:运行环境(极简)
FROM python:3.9-slim
WORKDIR /app
# 只复制编译好的包,不复制源码
COPY --from=builder /root/.local/bin /root/.local/bin
COPY --from=builder /root/.local/lib/python3.9/site-packages /root/.local/lib/python3.9/site-packages
# 复制应用代码
COPY . .
# 创建非root用户(安全强制项)
RUN adduser --disabled-password --gecos '' mluser && \
chown -R mluser:mluser /app
USER mluser
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["uvicorn", "app:app", "--host", "0.0.0.0:8000", "--port", "8000", "--workers", "4"]
关键细节解析:
-
为什么用
--user安装 ?避免root权限写入,符合安全基线; -
为什么分两阶段
?第一阶段装
pip-tools等构建工具,第二阶段只留运行时依赖,镜像体积减少63%; -
为什么
adduser?Kubernetes Pod Security Policy要求禁止root运行,否则调度失败; -
为什么
--workers 4?Uvicorn是异步框架,worker数=CPU核心数×2是经验值,4核机器设8 worker反而因上下文切换降低性能,实测4最优。
构建命令:
docker build -t ml-model-api:v1.0 .
。验证:
docker run -p 8000:8000 ml-model-api:v1.0
,然后
curl http://localhost:8000/health
应返回
{"status":"ok"}
。这一步卡住,后面全废。
3.2 模型标准化:ONNX是跨框架部署的通用语言
Scikit-learn、XGBoost、PyTorch模型不能直接互操作,但ONNX可以。它不是新框架,而是模型的“中间表示”(IR),类似Java字节码。转换过程必须验证等价性,否则线上预测就是错的。
以XGBoost为例:
import xgboost as xgb
from onnxmltools.convert import convert_xgboost
from onnxmltools.convert.common.data_types import FloatTensorType
# 训练模型(省略数据加载)
model = xgb.XGBClassifier()
model.fit(X_train, y_train)
# 定义输入类型:假设输入是20维float数组
initial_type = [('float_input', FloatTensorType([None, 20]))]
# 转换
onnx_model = convert_xgboost(model, initial_types=initial_type)
# 保存
with open("model.onnx", "wb") as f:
f.write(onnx_model.SerializeToString())
# 关键!验证转换正确性
import onnxruntime as ort
import numpy as np
ort_session = ort.InferenceSession("model.onnx")
# 用相同测试数据验证
pred_onnx = ort_session.run(None, {'float_input': X_test.astype(np.float32)})[0]
pred_sklearn = model.predict(X_test)
# 必须100%一致
assert np.array_equal(pred_onnx, pred_sklearn), "ONNX conversion failed!"
常见陷阱:
-
动态shape问题
:ONNX要求明确输入维度。
[None, 20]中None表示batch size可变,但特征数20必须固定。若训练时用pandas.get_dummies()生成稀疏特征,维度会变,必须在预处理中用OneHotEncoder(handle_unknown='ignore')并固定categories_; -
自定义函数不支持
:XGBoost的
predict_proba()返回概率,但ONNX Runtime默认只输出label。需在转换时加参数target_opset=12,并用ort_session.run()指定输出名['label', 'probabilities']; - 精度损失 :ONNX默认FP32,但某些场景可量化到INT8提速3倍。不过金融风控模型严禁量化,必须用FP32——这要写进部署文档,成为SLA的一部分。
转换后,
model.onnx
文件就是你的“部署合约”。它不依赖Python,C++、Java、Go都能加载。某IoT设备厂商用此方案,把Python训练的异常检测模型部署到ARM Cortex-A7芯片上,推理耗时从2.1秒降到380毫秒。
3.3 推理服务实现:FastAPI + ONNX Runtime的黄金组合
不用重造轮子,用FastAPI搭服务,ONNX Runtime做推理,这是当前最平衡的选择。它比TensorFlow Serving轻量,比裸写Flask更健壮。
app.py
核心代码:
from fastapi import FastAPI, HTTPException, BackgroundTasks
from pydantic import BaseModel
import numpy as np
import onnxruntime as ort
import time
import psutil
from prometheus_client import Counter, Histogram, Gauge
# 初始化Prometheus指标
PREDICTIONS_TOTAL = Counter('predictions_total', 'Total number of predictions', ['model', 'status'])
PREDICTION_LATENCY = Histogram('prediction_latency_seconds', 'Prediction latency', ['model'])
CPU_USAGE = Gauge('cpu_usage_percent', 'Current CPU usage')
app = FastAPI(title="ML Model API")
# 加载ONNX模型(全局单例,避免重复加载)
session = ort.InferenceSession("model.onnx", providers=['CPUExecutionProvider'])
class PredictionRequest(BaseModel):
features: list[float] # 强制类型校验,防止传入字符串
@app.get("/health")
def health_check():
return {"status": "ok", "cpu_percent": psutil.cpu_percent()}
@app.post("/predict")
def predict(request: PredictionRequest):
start_time = time.time()
try:
# 输入校验
if len(request.features) != 20:
raise HTTPException(status_code=400, detail=f"Expected 20 features, got {len(request.features)}")
# 转ONNX Runtime输入格式
input_data = np.array([request.features], dtype=np.float32) # batch=1
# 推理
outputs = session.run(None, {'float_input': input_data})
prediction = int(outputs[0][0]) # label
probability = float(outputs[1][0][1]) # prob of positive class
# 记录指标
PREDICTIONS_TOTAL.labels(model="fraud_v1", status="200").inc()
PREDICTION_LATENCY.labels(model="fraud_v1").observe(time.time() - start_time)
CPU_USAGE.set(psutil.cpu_percent())
return {
"prediction": prediction,
"probability": probability,
"latency_ms": round((time.time() - start_time) * 1000, 2)
}
except Exception as e:
PREDICTIONS_TOTAL.labels(model="fraud_v1", status="500").inc()
raise HTTPException(status_code=500, detail=str(e))
# 指标端点(Prometheus抓取)
@app.get("/metrics")
def metrics():
from prometheus_client import generate_latest
return generate_latest()
关键设计点:
-
全局session
:
ort.InferenceSession初始化耗时,必须在app外创建,避免每次请求都重载; -
输入强校验
:
BaseModel自动校验features是float列表且长度20,400错误直接拦截,不进推理; -
指标埋点
:
Counter统计成功/失败次数,Histogram记录延迟分布(自动生成_bucket,_sum,_count),Gauge暴露实时CPU; -
错误传播
:捕获所有异常,统一500返回,并记录
PREDICTIONS_TOTAL,确保监控不漏报。
启动:
uvicorn app:app --host 0.0.0.0:8000 --port 8000 --workers 4 --log-level info
。用
ab -n 1000 -c 100 http://localhost:8000/predict
压测,QPS应稳定在850+,P95延迟<150ms。
3.4 CI/CD流水线:GitHub Actions自动化构建与部署
手动
docker build && docker push
是灾难源头。必须用CI/CD固化流程。我们用GitHub Actions,免费、易用、与Git深度集成。
.github/workflows/deploy.yml
:
name: Deploy ML Model
on:
push:
branches: [main]
paths:
- 'model/**'
- 'app.py'
- 'requirements.in'
- 'Dockerfile'
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pip-tools
- name: Compile requirements
run: pip-compile requirements.in --output-file requirements.txt
- name: Build Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ghcr.io/your-org/ml-model-api:${{ github.sha }}
- name: Deploy to Kubernetes
uses: appleboy/scp-action@master
with:
host: ${{ secrets.K8S_HOST }}
username: ${{ secrets.K8S_USER }}
key: ${{ secrets.K8S_KEY }}
source: "k8s/deployment.yaml"
target: "/tmp/"
# 实际生产用kubectl apply,此处简化
核心保障:
-
触发精准
:只在
model/目录、app.py等关键文件变更时触发,避免无关提交浪费资源; -
版本锁定
:
pip-compile确保每次构建用相同依赖版本; -
镜像唯一性
:
ghcr.io/your-org/ml-model-api:${{ github.sha }}用commit hash作tag,绝对唯一,回滚只需改K8s manifest; -
安全凭证
:
secrets.K8S_KEY存于GitHub Secrets,不泄露私钥。
流水线跑通后,每次
git push
,12分钟内新模型自动上线。我们曾用此方案,将模型迭代周期从“按月发布”压缩到“按小时发布”,业务方提需求,当天就能看到效果。
3.5 生产就绪配置:Kubernetes部署与监控集成
单机Docker只是开始,生产必须上K8s。但别一上来就写100行YAML,从最简
Deployment
起步。
k8s/deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ml-model-api
spec:
replicas: 3
selector:
matchLabels:
app: ml-model-api
template:
metadata:
labels:
app: ml-model-api
spec:
containers:
- name: api
image: ghcr.io/your-org/ml-model-api:abc123 # 替换为实际tag
ports:
- containerPort: 8000
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
env:
- name: MODEL_PATH
value: "/app/model.onnx"
# 安全加固
securityContext:
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
---
apiVersion: v1
kind: Service
metadata:
name: ml-model-api
spec:
selector:
app: ml-model-api
ports:
- port: 80
targetPort: 8000
关键参数详解:
- replicas: 3 :至少3副本,防止单点故障。实测中,一个副本挂掉,其他两个自动承接流量,用户无感知;
- resources.limits :内存1Gi是底线,ONNX Runtime加载模型后常驻内存约600MB,留余量防OOM;
-
livenessProbe
:
/health端点必须返回200,否则K8s重启Pod。initialDelaySeconds: 30给模型加载留足时间(ONNX加载大模型需10-20秒); -
readinessProbe
:
periodSeconds: 5高频检查,确保流量只打到健康Pod; -
securityContext
:
runAsNonRoot和seccompProfile是K8s CIS安全基线强制项,不加则无法通过审计。
监控集成:在K8s集群中部署Prometheus Operator,用ServiceMonitor自动发现
ml-model-api
服务,抓取
/metrics
端点。Grafana看板必备面板:
-
“模型预测QPS”:
rate(predictions_total{job="ml-model-api"}[5m]); -
“P95延迟”:
histogram_quantile(0.95, rate(prediction_latency_seconds_bucket{job="ml-model-api"}[5m])); -
“CPU使用率”:
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)。
当P95延迟突然从22ms跳到350ms,看“CPU使用率”面板,发现某Pod飙升到98%,立刻
kubectl logs ml-model-api-xxxxx
查日志,定位到
while True
死循环——这就是可观测性的价值。
4. 真实战场复盘:那些文档不会写的12个致命坑与解法
4.1 坑1:模型加载慢到超时,K8s直接杀掉Pod
现象
:
kubectl get pods
看到Pod状态
CrashLoopBackOff
,
kubectl logs
显示
Readiness probe failed
。
根因
:ONNX模型文件1.2GB,加载需45秒,但
readinessProbe.initialDelaySeconds
只设了10秒,K8s在模型加载完前就判定不健康,反复重启。
解法
:
-
将
initialDelaySeconds设为max(模型加载时间×2, 60),我们实测大模型加载标准差±15%,所以设90秒; -
更优方案:在
/health端点里加加载状态检查:
这样K8s能区分“正在加载”和“加载失败”。# 全局变量 model_loaded = False model_load_start = time.time() @app.get("/health") def health_check(): global model_loaded if not model_loaded: # 检查是否超时 if time.time() - model_load_start > 120: return {"status": "loading_failed"} return {"status": "ok", "loaded": model_loaded}
4.2 坑2:GPU显存碎片化,Triton报
cudaErrorMemoryAllocation
现象
:Triton日志
Failed to allocate GPU memory for model instance
,但
nvidia-smi
显示显存只用了60%。
根因
:GPU显存分配是连续的,模型A占1.2GB,模型B占0.8GB,中间0.5GB空隙无法被新模型利用,形成碎片。
解法
:
-
在
config.pbtxt中强制设置dynamic_batching和instance_group:dynamic_batching [ ] instance_group [ [ { count: 2 gpus: [0] } ] ]count: 2表示在GPU0上启动2个实例,共享显存池,避免单实例独占; -
更彻底:用
nvidia-docker的--gpus device=0绑定特定GPU,配合NVIDIA_VISIBLE_DEVICES=0环境变量,杜绝跨GPU调度。
4.3 坑3:Prometheus指标乱码,Grafana显示
NaN
现象
:
curl http://pod-ip:8000/metrics
返回正常,但Grafana图表全是空。
根因
:FastAPI默认返回
Content-Type: text/plain; charset=utf-8
,但Prometheus要求
text/plain; version=0.0.4; charset=utf-8
。
解法
:在
/metrics
路由里手动设Header:
from fastapi.responses import Response
@app.get("/metrics")
def metrics():
from prometheus_client import generate_latest
return Response(
generate_latest(),
media_type="text/plain; version=0.0.4; charset=utf-8"
)
4.4 坑4:Docker镜像里中文路径报错
UnicodeEncodeError
现象
:模型文件名含中文,
Dockerfile COPY
时报错
'ascii' codec can't encode characters
。
根因
:Alpine/Debian slim镜像默认locale是
C
,不支持UTF-8。
解法
:在
Dockerfile
开头加:
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
RUN apt-get update && apt-get install -y locales && \
locale-gen C.UTF-8 && \
update-locale LANG=C.UTF-8 LC_ALL=C.UTF-8
4.5 坑5:Uvicorn workers过多,CPU反降
现象
:4核服务器设
--workers 8
,QPS从850降到620。
根因
:Uvicorn是异步框架,worker过多导致CPython GIL争抢加剧,上下文切换开销超过收益。
解法
:公式
workers = (CPU核心数 × 2) + 1
只适用于同步框架(如Gunicorn)。Uvicorn最佳值是
CPU核心数 + 1
,我们4核机器实测
--workers 5
时QPS最高。
4.6 坑6:ONNX Runtime在ARM服务器上找不到
libonnxruntime.so
现象
:
ImportError: libonnxruntime.so: cannot open shared object file
。
根因
:
pip install onnxruntime
默认装x86_64版本,ARM需装
onnxruntime-arm64
。
解法
:在
requirements.in
中写:
# For ARM64 servers
onnxruntime-arm64==1.15.1; platform_machine == "aarch64"
# For x86_64
onnxruntime==1.15.1; platform_machine == "x86_64"
4.7 坑7:K8s滚动更新时,旧Pod被杀,新Pod未就绪,流量丢失
现象
:
kubectl rollout restart deployment/ml-model-api
后,部分请求返回502。
根因
:
maxSurge
和
maxUnavailable
默认值不合理,导致新Pod未通过readinessProbe就被切流量。
解法
:在Deployment中显式配置:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
maxUnavailable: 0
确保旧Pod全退出前,绝不减少可用副本数。
4.8 坑8:模型预测结果随机波动,同输入不同输出
现象
:同一
curl
请求,有时返回
{"prediction": 0}
,有时
{"prediction": 1}
。
根因
:模型里用了
np.random.seed()
但未固定,或ONNX Runtime启用了
enable_cpu_mem_arena=False
导致内存复用。
解法
:
-
在推理前加
np.random.seed(42); -
ONNX Runtime初始化时加:
sess_options = ort.SessionOptions() sess_options.enable_cpu_mem_arena = False session = ort.InferenceSession("model.onnx", sess_options=sess_options)
4.9 坑9:Docker构建缓存失效,每次都是全量重装
现象
:
requirements.txt
没变,但
pip install
仍执行,构建时间从2分钟涨到8分钟。
根因
:
COPY . .
把
__pycache__/
、
.git/
等目录也复制了,Docker认为内容变了。
解法
:用
.dockerignore
:
.git
__pycache__
*.pyc
.env
4.10 坑10:Prometheus抓不到指标,target显示
DOWN
现象
:Prometheus UI里
State
列是
DOWN
,
Last Scrape Error
是
context deadline exceeded
。
根因
:K8s Service没配
targetPort
,或Pod没开
8000
端口。
解法
:检查
kubectl describe service ml-model-api
,确认
TargetPort
指向
8000
;再
kubectl exec -it pod-name -- netstat -tuln | grep 8000
,确认端口监听。
4.11 坑11:模型输入数据类型不匹配,ONNX Runtime报
InvalidArgument
现象
:
session.run()
抛
InvalidArgument: Expected input of type float32 but got float64
。
根因
:NumPy默认
float64
,ONNX要求
float32
。
解法
:强制转换:
input_data = np.array([request.features], dtype=np.float32) # 必须显式
4.12 坑12:GitHub Actions构建失败,报
The process '/usr/bin/docker' failed
现象
:Actions日志
Error: spawn /usr/bin/docker ENOENT
。
根因
:Ubuntu runner默认不装Docker。

797

被折叠的 条评论
为什么被折叠?



