生产级机器学习服务:容器化+API+可观测性铁三角

发布时间:2026/6/10 21:32:58

生产级机器学习服务:容器化+API+可观测性铁三角 1. 项目概述这不是“跑通模型”而是让模型在真实世界里活下来“From Notebook to Production: Running ML in the Real World (Part 4)”——这个标题本身就像一句行话暗号老手一眼就懂前面三篇已经蹚过了数据清洗、特征工程、模型训练和验证的泥潭现在终于到了最硬核、也最容易被忽视的最后一关把那个在Jupyter里闪闪发光的model.predict()变成公司API网关后面稳定吐出结果的/v1/predict。它不讲算法有多炫只问一件事当流量突增3倍、上游数据格式悄悄变了、GPU显存突然告急、或者凌晨两点监控报警说延迟飙升到2秒时你的模型还在不在岗还能不能干活会不会把错误结果当成正确答案发出去这才是“真实世界”的定义——没有CtrlEnter重跑的按钮只有日志、告警、回滚脚本和一张写满SLO的运维看板。我做过7个从0到1落地的ML服务项目其中4个在上线后第一周就因“生产环境适配不足”被紧急下线。最常见的不是模型不准而是特征提取代码在Docker里找不到/data/raw路径模型加载耗时23秒超出了API网关3秒超时阈值线上用的pandas版本比本地低0.2导致pd.cut()行为不一致批量预测结果错了一列甚至还有一次因为测试环境用的是CPU推理上线后切到GPU但没做CUDA版本兼容检查服务直接core dump。这些都不是理论问题是凌晨三点你被电话叫醒时盯着Prometheus图表上那根刺破红色警戒线的曲线手心冒汗的真实压力。所以Part 4的核心从来不是“怎么部署”而是“怎么让模型在无人值守、持续变化、资源受限、故障频发的真实系统里保持可观察、可控制、可恢复、可演进”。它解决的不是技术可行性而是工程可靠性。适合所有已经能调通模型但还没经历过第一次线上P0事故的算法工程师、MLOps初学者以及那些总被业务方追问“你们模型到底能不能扛住大促”的技术负责人。它不教你怎么写loss function但会告诉你为什么model.save_weights()比model.save()更适合灰度发布为什么一个简单的healthz端点能帮你少熬50%的夜。2. 核心设计思路拆解为什么“容器化API服务可观测性”是铁三角2.1 拒绝“本地跑通即交付”真实世界的三大不可靠假设很多团队卡在Part 4根本原因在于死守三个在实验室里成立、在生产环境里崩塌的假设。我把它称为“笔记本幻觉”环境一致性幻觉认为pip install -r requirements.txt就能复现本地环境。实测发现仅Python生态就有至少4个层面的不一致风险Python解释器小版本3.8.10 vs 3.8.12、C扩展编译器gcc 9 vs gcc 11、底层BLAS库OpenBLAS vs Intel MKL、甚至glibc版本。我们曾在一个金融风控模型上线时因服务器glibc版本低scikit-learn的HistGradientBoostingClassifier在预测时静默返回全零而本地完全正常。解决方案不是“升级服务器”而是用Docker镜像固化整个运行时栈包括基础OS层。数据静态幻觉认为训练时的数据schema就是永远的真理。现实是上游业务数据库字段名可能被产品经理一句话改掉user_id→customer_idCSV分隔符可能从逗号变成制表符上游ETL脚本更新了甚至时区信息可能从UTC变成东八区新接入的数据源。解决方案不是让算法工程师天天盯SQL变更而是构建带schema校验和自动类型转换的预处理管道且校验逻辑必须独立于模型代码作为服务启动前的必检项。资源无限幻觉认为“我的模型只要16G显存就够了”。但生产环境里GPU是共享资源。Kubernetes调度器可能把你的服务和另一个内存泄漏的Java应用塞进同一台物理机导致可用显存只剩8G或者你的服务被配置了2核CPU限制而特征工程里的pandas.groupby().apply()是单线程阻塞操作一卡就是5秒。解决方案不是申请更多资源而是对服务做严格的资源画像用psutil和nvidia-smi在warmup阶段采集CPU/内存/GPU显存/IO的基线再据此设置K8s的requests/limits并为关键路径添加超时熔断。这三大幻觉正是Part 4设计的起点。它不追求“最先进”而追求“最鲁棒”。所有技术选型都服务于一个目标把不确定性关进笼子。2.2 铁三角架构容器化、API服务、可观测性为何缺一不可我把生产级ML服务的骨架拆成三个咬合的齿轮少一个整个系统就会打滑容器化Containerization是地基它解决的是“在哪里跑”的问题。Docker镜像不是简单的打包工具它是环境契约。我们的标准镜像分三层基础层python:3.9-slim-bullseye CUDA 11.8、依赖层pip install所有包含--no-cache-dir --force-reinstall确保二进制兼容、应用层模型文件、预处理代码、Flask/FastAPI服务入口。关键细节我们禁用pip install的--find-links所有whl包都预先下载并校验SHA256避免PyPI临时故障导致CI失败模型文件不放在镜像内而是通过K8s的VolumeMount挂载实现模型热更新无需重建镜像。API服务API Serving是接口它解决的是“怎么用”的问题。很多人用Flask但高并发场景下它的同步IO模型是瓶颈。我们默认选FastAPI核心原因有三一是原生支持异步async def predict()能轻松处理数千并发连接二是自动生成OpenAPI文档前端联调不用写curl命令直接点网页调试三是依赖注入系统能把数据库连接池、缓存客户端、特征存储SDK作为参数注入到路由函数彻底解耦。一个常被忽略的细节FastAPI的BackgroundTasks不是用来加速预测的而是用来做异步日志上报和特征采样——预测主路径必须极致轻量所有副作用都剥离出去。可观测性Observability是神经它解决的是“现在怎么样”的问题。没有它你就是在黑盒里修发动机。我们的最小可观测集包含三件套指标Metrics、日志Logs、链路Traces。指标用Prometheus抓取暴露predict_latency_seconds_bucket直方图、model_load_time_secondsGauge、http_requests_totalCounter日志用structured loggingstructlog每条日志带request_id、model_version、input_hash方便全链路追踪链路用OpenTelemetry从API网关开始埋点贯穿特征获取、模型加载、预测执行、后处理全流程。最关键的设计所有可观测性组件都与业务代码零耦合。通过Uvicorn的on_startup/on_shutdown事件注册用中间件拦截请求用atexit钩子保证进程退出前日志刷盘。这样算法工程师改模型完全不用碰监控代码。这三个齿轮必须同步转动。只做容器化你得到一个无法被调用的孤岛只做API服务你得到一个无法被诊断的黑箱只做可观测性你得到一堆无法被修正的噪音。Part 4的全部价值就藏在这三者的精密咬合里。3. 核心环节实操详解从模型打包到服务上线的完整流水线3.1 模型资产标准化告别“model.pkl”拥抱ONNXMLflow在笔记本里joblib.dump(model, model.pkl)很爽。但在生产里这是定时炸弹。.pkl文件严重绑定Python版本、scikit-learn版本、甚至NumPy的内部结构。我们曾因升级numpy从1.21到1.22导致线上加载pkl失败错误信息是AttributeError: module object has no attribute array_function_dispatch排查了6小时才发现根源。我们的标准化方案是双轨制ONNX为主MLflow为辅。ONNXOpen Neural Network Exchange是跨框架的通用语言。无论你用TensorFlow、PyTorch还是XGBoost训练都能导出为ONNX。我们要求所有新模型必须提供ONNX版本。导出过程不是简单调用skl2onnx.convert_sklearn()而是严格遵循三步Schema定义用onnxmltools.convert.convert_sklearn()的initial_types参数明确定义输入输出的shape和dtype例如[(float_input, FloatTensorType([None, 12]))]禁止使用None模糊维度算子兼容性检查用onnx.checker.check_model()验证ONNX模型有效性再用onnxruntime.InferenceSession()在目标环境如CUDA 11.8中加载测试确认无InvalidGraph错误量化压缩对精度要求不苛刻的模型如推荐排序用onnxruntime.quantization做INT8量化体积减少75%推理速度提升2-3倍。我们实测一个BERT文本分类模型FP32 ONNX大小1.2GBINT8后仅320MBP99延迟从850ms降至310ms。MLflow是模型的“身份证”和“档案馆”。它不替代ONNX而是管理ONNX的元数据。每次模型训练完成CI流水线自动执行mlflow models serve \ --model-uri models:/fraud-detection/Production \ --port 5001 \ --host 0.0.0.0这行命令背后MLflow做了三件事一是从后端存储我们用S3拉取指定版本的ONNX文件二是根据conda.yaml重建隔离环境三是启动一个轻量服务。但这只是开发验证。生产部署时我们只用MLflow的get_model_info()API获取模型URI和签名signature然后由自己的FastAPI服务去加载ONNX完全绕过MLflow的serving模块——因为它的HTTP层不够可控无法满足我们的SLO。提示ONNX模型文件本身不包含预处理逻辑这是新手最大误区。我们必须把特征工程代码如StandardScaler的mean_和scale_单独序列化为JSON或NPZ和ONNX文件一起存入S3并在服务启动时同步加载。否则线上预测就是“用A的均值减B的标准差”结果必然灾难。3.2 FastAPI服务骨架不只是写一个predict()函数一个能扛住生产压力的FastAPI服务骨架比内容更重要。以下是我们的标准main.py精简版每一行都有深意from fastapi import FastAPI, BackgroundTasks, HTTPException, Depends from pydantic import BaseModel import onnxruntime as ort import numpy as np import structlog from typing import List, Dict, Any import time import psutil # 全局logger结构化输出 logger structlog.get_logger() # 定义输入输出schema强制类型检查 class PredictionRequest(BaseModel): features: List[List[float]] # 二维数组batch_size x n_features class PredictionResponse(BaseModel): predictions: List[float] model_version: str latency_ms: float # 全局ONNX会话服务启动时加载一次 ort_session None model_version unknown # 启动时加载模型记录耗时 app.on_event(startup) async def startup_event(): global ort_session, model_version start_time time.time() try: # 从环境变量读取模型路径支持S3或本地 model_path os.getenv(MODEL_PATH, /app/models/model.onnx) logger.info(Loading ONNX model, pathmodel_path) # 使用CUDA Execution Provider若不可用则fallback到CPU providers [CUDAExecutionProvider, CPUExecutionProvider] ort_session ort.InferenceSession(model_path, providersproviders) model_version os.getenv(MODEL_VERSION, 1.0.0) load_time time.time() - start_time logger.info(Model loaded successfully, versionmodel_version, load_time_sload_time) except Exception as e: logger.error(Failed to load model, errorstr(e)) raise # 健康检查端点K8s liveness probe用 app.get(/healthz) def health_check(): if ort_session is None: raise HTTPException(status_code503, detailModel not loaded) return {status: ok, model_version: model_version} # 核心预测端点带超时和熔断 app.post(/predict, response_modelPredictionResponse) async def predict( request: PredictionRequest, background_tasks: BackgroundTasks, # 依赖注入这里可以注入缓存、DB等 # db: Session Depends(get_db) ): start_time time.time() # 1. 输入校验维度、数值范围 if not request.features: raise HTTPException(status_code400, detailEmpty features list) if len(request.features[0]) ! 12: # 硬编码特征数来自ONNX schema raise HTTPException(status_code400, detailfExpected 12 features, got {len(request.features[0])}) # 2. 转换为numpy arrayONNX要求 try: input_array np.array(request.features, dtypenp.float32) except ValueError as e: raise HTTPException(status_code400, detailfInvalid feature format: {e}) # 3. 执行预测带超时防止ONNX runtime卡死 try: # 设置ONNX runtime的session选项 ort_session.run_options ort.RunOptions() ort_session.run_options.add_run_config_entry(timeout, 5000) # 5秒超时 # 获取ONNX输入名动态不硬编码 input_name ort_session.get_inputs()[0].name outputs ort_session.run(None, {input_name: input_array}) predictions outputs[0].flatten().tolist() except Exception as e: logger.error(ONNX inference failed, errorstr(e), input_shapeinput_array.shape) raise HTTPException(status_code500, detailInference error) latency_ms (time.time() - start_time) * 1000 # 4. 异步上报指标和日志不阻塞主响应 background_tasks.add_task( log_prediction, request_idrequest_id, predictionspredictions, latency_mslatency_ms ) return PredictionResponse( predictionspredictions, model_versionmodel_version, latency_mslatency_ms ) # 异步日志函数避免IO阻塞 def log_prediction(request_id: str, predictions: List[float], latency_ms: float): logger.info( Prediction completed, request_idrequest_id, predictions_countlen(predictions), p99_latency_mslatency_ms, # 这里可以加采样比如只记录1%的详细输入 sample_input_hashhashlib.md5(str(predictions[:3]).encode()).hexdigest() if len(predictions) 3 else )这段代码的“魔鬼细节”app.on_event(startup)确保模型只加载一次避免每次请求都反序列化这是性能基石/healthz端点返回model_versionK8s的滚动更新能基于此做金丝雀发布只把流量切给新version的服务输入校验硬编码12维这来自ONNX模型的initial_types是契约不是魔法数字background_tasks.add_task()把日志上报剥离出主路径实测将P99延迟降低40%ort_session.run_options.add_run_config_entry(timeout, 5000)是ONNX Runtime的隐藏功能防止底层C代码死锁。3.3 Dockerfile与K8s部署从镜像构建到流量切换一个生产级Dockerfile不是为了“能跑”而是为了“能管”。我们的Dockerfile严格遵循多阶段构建# 构建阶段安装依赖编译C扩展 FROM python:3.9-slim-bullseye AS builder RUN apt-get update apt-get install -y --no-install-recommends \ build-essential \ libglib2.0-0 \ libsm6 \ libxext6 \ libxrender-dev \ rm -rf /var/lib/apt/lists/* COPY requirements.txt . # 升级pip到最新版避免旧版pip安装wheel失败 RUN pip install --upgrade pip # 安装依赖--no-cache-dir避免镜像臃肿 RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt # 运行阶段极简基础镜像只复制wheel FROM python:3.9-slim-bullseye # 创建非root用户安全基线 RUN addgroup -g 1001 -f appgroup adduser -S appuser -u 1001 USER appuser # 复制wheelpip install --find-links /wheels --no-index COPY --frombuilder /wheels /wheels COPY --frombuilder /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0 /usr/lib/x86_64-linux-gnu/ # 复制应用代码 COPY app/ /app/ WORKDIR /app # 只安装wheel不联网确保可重现 RUN pip install --no-cache-dir --find-links /wheels --no-index * # 暴露端口声明健康检查 EXPOSE 8000 HEALTHCHECK --interval30s --timeout3s --start-period5s --retries3 \ CMD wget --quiet --tries1 --spider http://localhost:8000/healthz || exit 1 # 启动命令Uvicorn配置 CMD [uvicorn, main:app, --host, 0.0.0.0:8000, --port, 8000, --workers, 4, --limit-concurrency, 100, --timeout-keep-alive, 5]关键点解析多阶段构建构建阶段用大镜像装编译器运行阶段用极小镜像最终镜像大小从1.2GB压到320MB非root用户adduser -S appuser创建系统用户USER appuser切换满足PCI-DSS安全审计HEALTHCHECKK8s的livenessProbe直接复用--start-period5s给模型加载留足时间Uvicorn参数--workers 4对应CPU核心数--limit-concurrency 100防止单个worker被长请求占满--timeout-keep-alive 5缩短连接空闲时间释放连接池。K8s部署文件deployment.yaml同样精雕细琢apiVersion: apps/v1 kind: Deployment metadata: name: ml-fraud-service labels: app: ml-fraud-service spec: replicas: 3 # 至少3副本防止单点故障 selector: matchLabels: app: ml-fraud-service template: metadata: labels: app: ml-fraud-service annotations: # 注入模型版本用于Prometheus标签 prometheus.io/scrape: true prometheus.io/port: 8000 spec: # 强制使用非root用户 securityContext: runAsNonRoot: true runAsUser: 1001 containers: - name: api image: registry.example.com/ml-fraud-service:v1.2.0 imagePullPolicy: IfNotPresent ports: - containerPort: 8000 name: http # 资源限制基于warmup实测数据 resources: requests: memory: 1Gi cpu: 500m nvidia.com/gpu: 1 limits: memory: 2Gi cpu: 1000m nvidia.com/gpu: 1 # 健康检查 livenessProbe: httpGet: path: /healthz port: 8000 initialDelaySeconds: 60 # 给模型加载留足60秒 periodSeconds: 30 readinessProbe: httpGet: path: /healthz port: 8000 initialDelaySeconds: 30 periodSeconds: 10 # 挂载模型存储 volumeMounts: - name: model-storage mountPath: /app/models volumes: - name: model-storage persistentVolumeClaim: claimName: ml-model-pvc --- # Service集群内访问 apiVersion: v1 kind: Service metadata: name: ml-fraud-service spec: selector: app: ml-fraud-service ports: - port: 8000 targetPort: 8000 --- # Ingress外部流量入口带金丝雀权重 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ml-fraud-ingress annotations: nginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-weight: 5 # 5%流量到新版本 spec: rules: - http: paths: - path: /predict pathType: Prefix backend: service: name: ml-fraud-service port: number: 8000这个YAML的实战价值initialDelaySeconds: 60是血泪教训。模型加载慢如果liveness probe太激进会反复重启Podnvidia.com/gpu: 1是K8s GPU调度的关键必须和节点上的nvidia-device-plugin配合nginx.ingress.kubernetes.io/canary-weight: 5开启金丝雀发布新模型先跑5%流量结合Prometheus的predict_latency_seconds_bucket和http_requests_total{status~5..}指标确认无异常后再切100%。4. 生产环境排障实录那些凌晨三点教会我的事4.1 延迟飙升从“模型慢”到“网络慢”的真相现象某日凌晨1:23告警ml-fraud-service的P99延迟从320ms突增至2100ms持续17分钟。值班同事第一反应是“模型退化了”紧急回滚模型版本无效。排查路径按时间顺序查指标Prometheus中predict_latency_seconds_bucket显示延迟飙升集中在le22秒桶说明大量请求卡在2秒左右。同时http_requests_total{code200}下降code504Gateway Timeout上升——这指向网关超时而非模型本身。查日志structlog日志中Prediction completed事件的时间戳与/predict请求时间戳差值确实在2000ms左右但ONNX inference failed日志为零。说明预测执行很快问题在预测之后。查链路OpenTelemetry链路追踪显示/predictspan的process阶段即模型预测平均耗时210ms但send response阶段耗时1890ms。问题出在响应发送环节。查网络kubectl exec -it pod -- bash进入容器执行curl -w curl-format.txt -o /dev/null -s http://upstream-service/feature发现上游特征服务响应时间从50ms涨到1800ms。根源是上游服务所在节点的磁盘IO饱和iostat -x 1显示%util100%。根因与修复上游特征服务未做熔断当其变慢时我们的服务仍持续重试导致连接池耗尽新请求排队等待。修复方案在FastAPI服务中为特征获取添加tenacity库的重试和熔断from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type retry( stopstop_after_attempt(3), waitwait_exponential(multiplier1, min1, max10), retryretry_if_exception_type((requests.exceptions.Timeout, requests.exceptions.ConnectionError)) ) def fetch_features(user_id: str) - dict: # ... requests.get(...)同时K8s Service配置maxSurge: 1和maxUnavailable: 0确保滚动更新时上游服务始终有足够副本。实操心得延迟问题90%不在模型里而在I/O、网络、下游依赖。永远先看http_requests_total{code~5..}和process_cpu_seconds_total再看模型指标。模型预测慢是“症状”不是“病因”。4.2 结果漂移当“相同输入”给出“不同输出”现象业务方反馈同一批用户ID在上午10点和下午3点调用/predict返回的欺诈概率差异巨大0.12 vs 0.89但输入特征完全一致。排查路径复现用Postman固定request_id重复调用确认结果确实不一致。隔离在测试环境用相同ONNX模型和相同输入结果稳定。问题只在生产。深挖检查ONNX模型加载代码发现ort.InferenceSession的providers参数是[CUDAExecutionProvider, CPUExecutionProvider]。当GPU显存紧张时ONNX Runtime会自动fallback到CPU而CPU和GPU的浮点运算精度、舍入方式不同导致微小差异在深度模型中被放大。验证kubectl exec进Pod执行nvidia-smi发现GPU显存使用率98%dmesg | grep -i out of memory有OOM killer日志。根因与修复ONNX Runtime的provider fallback机制在资源紧张时引入了非确定性。修复方案强制指定provider禁用fallback# 启动时检测GPU可用性 try: ort_session ort.InferenceSession(model_path, providers[CUDAExecutionProvider]) logger.info(Using CUDA provider) except Exception as e: logger.warning(CUDA unavailable, falling back to CPU, errorstr(e)) # 但这里不fallback而是抛出异常让K8s重启Pod到有GPU的节点 raise RuntimeError(CUDA provider required but unavailable)同时K8s Deployment中resources.limits.nvidia.com/gpu: 1改为resources.requests.nvidia.com/gpu: 1确保调度器只把Pod调度到有空闲GPU的节点从源头杜绝OOM。注意ONNX的CPU和GPU provider结果理论上应一致但实践中因cuBLAS版本、CUDA流调度等存在微小差异。对金融、医疗等高敏感场景必须锁定provider宁可服务不可用也不可结果不确定。4.3 内存泄漏从“服务稳定”到“OOM Killed”的渐进式崩溃现象服务上线后第5天Pod被K8s OOMKilled日志最后一条是Killed process 123 (python) total-vm:12345678kB, anon-rss:2097152kB, file-rss:0kB。重启后正常但3天后再次OOM。排查路径监控Prometheus中container_memory_usage_bytes{containerapi}曲线呈阶梯式上升每次GC后回落一点但基线越来越高。分析kubectl top pod确认是api容器内存增长。kubectl exec进容器用psutil打印内存import psutil p psutil.Process() print(fMemory info: {p.memory_info()}) print(fMemory percent: {p.memory_percent()})发现rss常驻内存持续增长。 3.定位用tracemalloc在服务中添加内存快照import tracemalloc tracemalloc.start() app.get(/mem-snapshot) def mem_snapshot(): snapshot tracemalloc.take_snapshot() top_stats snapshot.statistics(lineno) return {top10: [str(stat) for stat in top_stats[:10]]}调用/mem-snapshot发现onnxruntime.capi._pybind_state相关调用占内存90%。根因与修复ONNX Runtime的InferenceSession在某些版本中对动态shape输入如batch size变化存在内存泄漏。我们的服务接收不定长batch触发了该bug。修复方案升级ONNX Runtime到1.15.1已修复并添加session复用保护# 全局session池按batch size分桶 session_pool { 1: ort.InferenceSession(...), # 小batch 32: ort.InferenceSession(...), # 中batch 128: ort.InferenceSession(...) # 大batch } # 预测时选择最接近的session batch_size len(request.features) chosen_size min(session_pool.keys(), keylambda k: abs(k - batch_size)) session session_pool[chosen_size]实操心得内存泄漏是慢性病必须建立常态化的内存监控。我们在每个Pod中部署node-exporter并配置告警规则container_memory_usage_bytes{containerapi} / container_spec_memory_limit_bytes{containerapi} 0.85提前预警。5. 持续演进与经验沉淀让Part 4成为团队能力基线5.1 CI/CD流水线从“手动部署”到“一键发布”的质变一个成熟的ML生产流程必须把Part 4的每一个环节都固化进CI/CD。我们的GitLab CI流水线gitlab-ci.yml核心阶段如下stages: - test - build - deploy-staging - quality-gate - deploy-prod # 测试阶段不只是单元测试 test-model: stage: test image: python:3.9 script: - pip install pytest pytest-cov - pytest tests/ --covapp --cov-reporthtml # 关键用真实ONNX模型做集成测试 - python tests/integration_test.py --model-path s3://models/fraud-v1.2.0.onnx # 构建阶段生成镜像并扫描 build-image: stage: build image: docker:20.10.16 services: - docker:20.10.16-dind script: - export IMAGE_TAG$CI_COMMIT_SHORT_SHA - docker build -t $CI_REGISTRY_IMAGE:$IMAGE_TAG . - docker push $CI_REGISTRY_IMAGE:$IMAGE_TAG # 安全扫描 - docker scan $CI_REGISTRY_IMAGE:$IMAGE_TAG --severity high # 部署到Staging自动触发 deploy-staging: stage: deploy-staging image: bitnami/kubectl:1.25 script: - kubectl set image deployment/ml-fraud-service api$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA environment: staging # 质量门禁自动化决策是否上线 quality-gate: stage: quality-gate image: python:3.9 script: - pip install prometheus-api-client - python scripts/quality_gate.py \ --prometheus-url http://prometheus-staging:9090 \ --query rate(http_requests_total{jobml-fraud-service,code~5..}[1h]) / rate(http_requests_total{jobml-fraud-service}[1h]) \ --threshold 0.001 # 错误率0.1% allow_failure: false # 失败则中断流水线 # 生产部署需人工确认 deploy-prod: stage: deploy-prod image: bitnami/kubectl:1.25 script: - kubectl set image deployment/ml-fraud-service api$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA environment: production when: manual # 手动点击触发 only: - main这个流水线的“护城河”作用quality-gate阶段用Prometheus实时指标做决策不是“测试通过就上线”而是“线上表现达标才放行”。我们曾在此阶段拦截了3次上线一次是新模型错误率从0.05%升至0.12%一次是P95延迟从300ms升至420ms超出SLO 400ms一次是内存使用率7天趋势上涨15%预示泄漏。when: manual确保生产发布有人工把关但把关的依据是客观数据不是主观感觉。5.2 文档即代码让“如何运维”成为

相关新闻