1. 项目概述:这不是“部署”,而是让模型真正活在业务流水线里
“From Notebook to Production: Running ML in the Real World (Part 4)”——这个标题里藏着一个被严重低估的真相:前三个部分讲的可能是模型训练、评估、API封装,但Part 4才是真正决定ML项目生死的临界点。它不叫“上线”,也不叫“发布”,更不是把Flask服务跑起来就完事;它是让一个在Jupyter里跑得飞起的模型,开始每天凌晨三点自动拉取新数据、校验特征分布偏移、拒绝异常输入、生成可审计的预测日志、在CPU资源跌到30%时自动扩容、被业务方投诉“结果和昨天不一样”时能5分钟内定位是数据源变更还是模型退化……这才是“Real World”的真实水位。
我做过17个从0到1落地的ML项目,其中12个卡在Part 3(API化),剩下5个里,有3个在Part 4的前三周就因监控缺失、回滚失败或权限混乱被迫下线。为什么?因为Part 4根本不是技术单点问题,而是一套生产级ML运维协议(MLOps Protocol)的强制落地。它要求你同时扮演数据工程师、SRE、合规专员和业务翻译官——你得用Prometheus看模型延迟P99,用Great Expectations校验上游ETL输出的schema一致性,用OpenTelemetry追踪一条预测请求从Kafka Topic到特征存储再到模型服务的全链路耗时,还得给风控团队写一份《本次模型更新对逾期率阈值影响的量化说明》。这些事,Jupyter notebook里连import都写不出来。
所以这篇不是教你怎么写docker build -t ml-model .,而是拆解一个真实运行在金融反欺诈场景下的模型服务,在过去18个月里如何扛住日均2300万次预测请求、经历6次核心数据源升级、完成4次无感模型热切换、应对2次因第三方SDK漏洞触发的紧急回滚——所有操作都记录在案,所有决策都有依据,所有故障都能归因。它解决的核心问题很朴素:当模型不再是“研究对象”,而成为业务系统里一个会呼吸、会告警、会自愈的组件时,你靠什么确保它不拖垮整个系统,反而持续创造确定性价值?适合正在把第二个模型推上生产环境的算法工程师、刚接手模型运维的平台工程师,以及想搞懂“为什么我们花了三个月训出的模型,业务方说‘不敢用’”的产品负责人。
2. 内容整体设计与思路拆解:放弃“一次性部署”,拥抱“持续可信交付”
2.1 为什么传统CI/CD流程在ML场景下必然失效?
很多团队直接把模型服务塞进现有CI/CD流水线:代码提交→单元测试→构建Docker镜像→K8s部署→健康检查→通知Slack。表面看很完美,但实际运行中会暴露出三个结构性断层:
数据断层:CI/CD只校验代码变更,但模型效果严重依赖数据。上游数仓凌晨两点执行的分区合并任务,可能让特征计算逻辑悄然失效(比如
SUM(revenue)变成SUM(DISTINCT revenue)),而CI/CD对此毫无感知。我们曾遇到一个推荐模型,在A/B测试中CTR提升12%,上线后一周内订单转化率暴跌27%——根因是数仓同学优化了用户行为表的去重逻辑,导致“7日内活跃天数”特征从整数变成浮点数,模型输入层未做类型强校验,大量NaN被转为0,最终把高价值用户误判为沉默用户。模型断层:CI/CD验证的是“模型能加载”,而非“模型能正确推理”。一个PyTorch模型在本地用
torch.load()加载权重没问题,但在生产环境GPU驱动版本不匹配时,model.eval()可能静默跳过某些LayerNorm计算,导致输出偏差。更隐蔽的是ONNX导出时的算子兼容性问题:某次升级onnxruntime到1.15后,GatherElements算子在TensorRT后端出现索引越界,错误结果被当作正常输出返回,直到风控规则触发批量拦截才暴露。契约断层:API接口定义(如OpenAPI spec)只描述输入输出格式,不约束语义稳定性。比如
/predict接口始终返回{"score": float, "risk_level": str},但risk_level的枚举值从["low", "medium", "high"]悄悄扩展为["low", "medium", "high", "critical"],下游业务系统若用字符串匹配而非枚举校验,就会把"critical"当成未知值默认置为"low",造成严重漏判。
因此,Part 4的设计起点必须是:将“模型可信度”作为一等公民纳入交付流水线,与代码质量、基础设施稳定性同等权重。我们采用的方案叫“三阶门禁(Three-Gate Gatekeeping)”,每个阶段设置不可绕过的硬性检查点,任何一项失败即阻断发布:
| 门禁阶段 | 检查目标 | 技术实现 | 失败后果 |
|---|---|---|---|
| Gate 1:数据契约验证 | 确保训练/推理数据分布一致、schema兼容、关键统计量在容忍区间 | 使用Great Expectations构建数据质量检查集,集成到Airflow DAG前置任务;对特征工程代码做静态AST分析,识别潜在的非幂等操作(如random.sample()) | 阻断模型训练任务,不生成新模型版本 |
| Gate 2:模型行为验证 | 验证模型在标准数据集上的预测稳定性、数值精度、内存占用、冷启动延迟 | 构建Golden Dataset(含1000条覆盖边界case的样本),运行pytest驱动的模型行为测试套件;使用NVIDIA Nsight Systems采集GPU kernel耗时;用memory_profiler监控单次推理峰值内存 | 阻断模型打包任务,不生成Docker镜像 |
| Gate 3:服务契约验证 | 验证API服务在压测下的SLA达标、错误码语义正确、日志可追溯、配置可审计 | 基于Locust编写场景化压测脚本(模拟突发流量+长尾延迟);用OpenAPI Validator校验响应体结构与文档一致性;通过K8s ConfigMap注入的配置项,必须经Hash校验并写入审计日志 | 阻断K8s部署任务,不更新Service Endpoint |
这个设计背后的核心逻辑是:把“信任”从“人肉确认”转化为“机器可验证的契约”。比如Gate 1的数据契约,我们不依赖数据工程师口头承诺“这次ETL没改逻辑”,而是要求每次ETL任务执行后,自动向特征仓库写入data_contract.json,包含该分区的feature_schema、null_ratio、value_distribution(直方图摘要)、drift_score(KS检验p-value)。模型训练服务在拉取数据前,必须比对当前contract与训练时contract的哈希值,不一致则拒绝启动——这比任何会议纪要都可靠。
2.2 为什么选择Kubernetes而非Serverless或纯VM?
市面上常听到“用AWS Lambda跑模型最省钱”“用Azure Container Apps自动扩缩容”,但我们坚持K8s自有集群,原因很实在:
冷启动不可控:Serverless平台冷启动时间波动极大(Lambda实测P95达1.8秒),而我们的风控模型SLA要求P99<300ms。一次冷启动延迟就可能让交易超时失败。K8s通过
minReplicas=2+HPA+pre-warm script(容器启动后预加载模型到GPU显存)可将冷启动稳定在47ms内。资源隔离刚性需求:多个模型服务共享节点时,一个模型的CUDA内存泄漏会拖垮同节点所有服务。K8s的
ResourceQuota和LimitRange能硬性限制单Pod GPU显存(如nvidia.com/gpu: 1)、CPU周期(cpu: 2000m)、内存(memory: 4Gi),配合RuntimeClass指定NVIDIA Container Toolkit运行时,确保故障域隔离。调试链路完整性:Serverless的日志分散在CloudWatch/Lambda Insights,无法关联容器内进程堆栈与K8s事件。而K8s中,
kubectl logs -f model-service-7d8c9b4f5-2xq9p能实时看到Python进程stdout/stderr,kubectl describe pod显示OOMKilled事件,kubectl top pods展示实时资源消耗,三者时间戳对齐,故障定位效率提升3倍以上。
当然,K8s不是银弹。我们为此付出的代价是:必须自建模型镜像仓库(Harbor)、定制化调度器(支持GPU拓扑感知调度)、开发Operator管理模型生命周期(如自动创建对应Ingress、Secret、ConfigMap)。但比起业务因不可控延迟或资源争抢导致的资损,这笔技术债值得背。
2.3 为什么监控体系必须包含“模型层”而非仅“基础设施层”?
很多团队的监控大屏上全是CPU、内存、HTTP 5xx错误率,却唯独缺了“模型健康度”。这就像给汽车装满胎压监测、油量报警、发动机转速表,却不装ABS故障灯。我们定义的模型层监控包含四个不可妥协的维度:
输入质量监控(Input Health):
- 实时采样1%的请求,用
scipy.stats.ks_1samp检验关键特征(如user_age,transaction_amount)分布是否偏离训练集(p-value < 0.01即告警) - 对分类特征(如
device_type)计算entropy,熵值骤降预示新设备类型涌入或旧类型消失 - 用
pandas_profiling生成每小时数据快照报告,存入MinIO供人工复核
- 实时采样1%的请求,用
预测稳定性监控(Prediction Stability):
- 计算滑动窗口内
score的标准差,超过阈值(如0.15)触发“预测抖动”告警 - 对同一用户ID的连续10次请求,检测
risk_level标签是否高频震荡(>3次/小时),定位特征时效性问题
- 计算滑动窗口内
业务效果监控(Business Impact):
- 将模型输出与下游业务动作绑定:如
score > 0.8触发人工审核,则监控“审核通过率”是否异常下降(可能模型过于保守) - A/B测试期间,用
causalml库计算CATE(Conditional Average Treatment Effect),避免将自然波动误判为模型效果
- 将模型输出与下游业务动作绑定:如
系统性能监控(System Performance):
- 不只是
latency_ms,还要区分feature_retrieval_time(从Redis拉特征)、inference_time(模型计算)、postprocessing_time(结果转换) - GPU利用率需单独监控:
nvidia-smi dmon -s u -d 1采集util指标,持续低于20%提示模型未充分利用硬件
- 不只是
这套监控不是摆设。去年Q3,输入质量监控发现transaction_amount分布右偏(均值从¥237升至¥312),我们立即暂停模型更新,排查发现是支付渠道新增了大额企业转账入口。若仅看基础设施监控,一切风平浪静——而业务侧已收到多起“高风险用户被误放行”的投诉。
3. 核心细节解析与实操要点:让每个环节都经得起推敲
3.1 数据契约验证:用代码定义数据可信边界
数据契约(Data Contract)不是文档,而是可执行的Python函数。我们基于Great Expectations 0.16+构建了一套契约模板,核心在于将数据质量规则与业务语义强绑定,而非泛泛而谈“非空”“唯一”。
以反欺诈场景最关键的user_login_frequency_7d(用户7日内登录次数)为例,其契约文件user_features_contract.py包含:
# 定义数据源连接(指向特征仓库的Delta Table) datasource_config = { "class_name": "Datasource", "execution_engine": {"class_name": "SparkDFExecutionEngine"}, "data_connectors": { "default_runtime_data_connector_name": { "class_name": "RuntimeDataConnector", "batch_identifiers": ["batch_id"], } } } # 定义期望集(Expectation Suite) expectation_suite = ExpectationSuite( expectation_suite_name="user_features_contract_v202310" ) # 业务语义化规则(非技术规则!) expectation_suite.add_expectation( expectation_configuration=ExpectationConfiguration( expectation_type="expect_column_values_to_be_between", kwargs={ "column": "user_login_frequency_7d", "min_value": 0, "max_value": 100, # 业务逻辑:人类不可能7天登录100次 "strict_min": True, "strict_max": False, }, meta={ "notes": { "format": "markdown", "content": "此字段由用户行为日志聚合生成,理论最大值受产品功能限制(每日最多5次登录入口)" } } ) ) # 分布稳定性规则(对抗概念漂移) expectation_suite.add_expectation( expectation_configuration=ExpectationConfiguration( expectation_type="expect_column_kl_divergence_to_be_less_than", kwargs={ "column": "user_login_frequency_7d", "partition_object": { # 引用训练时保存的基准分布 "weights": [0.1, 0.2, 0.4, 0.2, 0.1], "bins": [0, 1, 3, 7, 15, 100] }, "threshold": 0.15, # KL散度阈值,超过则触发数据漂移告警 } ) )实操要点:
- 基准分布(
partition_object)不是静态快照,而是训练时自动从训练数据生成并存入S3的gs://my-bucket/contracts/user_features_baseline.json,包含bins(分箱边界)和weights(各箱概率)。每次契约验证时,动态加载该文件进行KL散度计算。 - 所有规则必须标注
meta.notes,用Markdown写明业务依据。当规则失败时,告警消息直接附带这段说明,让数据工程师一眼明白“为什么这个阈值是100而不是200”。 - 我们禁用
expect_table_row_count_to_equal这类脆弱规则——数仓分区合并可能导致行数变化,但不影响特征质量。重点永远是业务可接受的语义边界。
提示:Great Expectations的
ValidationOperator已废弃,必须用Checkpoint替代。我们封装了一个ContractValidator类,统一处理Delta Lake表的读取、期望执行、结果写入Elasticsearch(用于告警聚合)。代码中所有路径都通过环境变量注入(CONTRACT_BUCKET,BASELINE_PATH),确保开发/测试/生产环境隔离。
3.2 模型行为验证:超越准确率的深度测试
模型行为测试(Model Behavior Testing)的目标是:证明模型在生产环境中的“行为”与在训练环境中的“行为”一致。这需要三类测试:
3.2.1 数值稳定性测试(Numerical Stability Test)
GPU计算存在浮点精度差异,不同CUDA版本、不同显卡型号的torch.mm()结果可能有微小偏差。我们要求:同一输入在任意环境下的输出score绝对误差≤1e-5。
# test_numerical_stability.py import torch import numpy as np def test_gpu_precision_consistency(): # 加载训练时保存的Golden Input(float32) golden_input = torch.load("tests/data/golden_input.pt") # 在当前环境运行推理 model = load_model_from_registry("fraud_model_v2.3") with torch.no_grad(): output_current = model(golden_input).cpu().numpy() # 加载训练环境基准输出(float32) output_baseline = np.load("tests/data/golden_output_v2.3.npy") # 计算最大绝对误差 max_error = np.max(np.abs(output_current - output_baseline)) assert max_error <= 1e-5, f"Numerical drift detected: {max_error}"实操要点:
- Golden Input必须是真实生产请求采样(非合成数据),且标注
request_id和timestamp,存入MinIO按版本管理。 - 基准输出(
golden_output_v2.3.npy)在模型训练完成、通过所有离线评估后立即生成,并签名存档。任何后续修改必须走模型版本升级流程。 - 测试必须在目标GPU环境(如A10g)上执行,不能在CPU上跑——CPU的
torch.mm()和GPU的cublasLtMatmul数值路径完全不同。
3.2.2 边界Case压力测试(Edge Case Stress Test)
覆盖真实业务中的极端场景,而非教科书式corner case:
| 场景 | 输入示例 | 预期行为 | 测试方法 |
|---|---|---|---|
| 空特征向量 | user_id="unknown"导致所有特征为NaN | 返回{"error": "MISSING_FEATURES", "code": 400} | 构造1000个user_id不存在的请求,验证错误码和响应体结构 |
| 超长文本特征 | user_description字段长达128KB(远超训练时的2KB上限) | 触发ValueError并记录feature_truncation日志 | 用locust发送超长请求,检查K8s Pod日志中是否存在该关键词 |
| 时序特征错乱 | last_login_timestamp>current_time(服务器时间同步异常) | 自动矫正为current_time,并告警TIMESTAMP_SKEW | 修改容器系统时间,验证模型是否拒绝异常时间戳 |
实操要点:
- 边界Case库必须由业务方、风控专家、算法工程师共同维护,每季度评审更新。例如,当产品上线“夜间免密登录”功能后,立即增加
login_time_hour IN [0, 1, 2, 3, 4]的专项测试。 - 所有测试用例必须标注
severity(CRITICAL/HIGH/MEDIUM),CRITICAL用例失败直接阻断发布。
3.2.3 内存与延迟基线测试(Memory & Latency Baseline)
我们为每个模型定义硬性SLA:
- 冷启动延迟 ≤ 50ms(从Pod Ready到首次成功响应)
- P99推理延迟 ≤ 280ms(含特征获取)
- 单次推理峰值内存 ≤ 1.2GB(CPU模式)或 ≤ 3.8GB(GPU模式)
测试脚本test_performance_baseline.py使用pytest-benchmark框架:
def test_inference_latency(benchmark): model = load_model_from_registry("fraud_model_v2.3") input_batch = load_golden_batch("batch_100_samples.pt") # 100条真实请求 # 预热GPU _ = model(input_batch[:1]) # 基准测试 result = benchmark.pedantic( model, args=(input_batch,), iterations=5, rounds=20, warmup_rounds=2 ) # 验证P99延迟 p99_latency = np.percentile(result.stats['rounds'], 99) assert p99_latency <= 280.0, f"P99 latency {p99_latency}ms exceeds SLA" def test_memory_usage(): import psutil process = psutil.Process() mem_before = process.memory_info().rss / 1024 / 1024 # MB model = load_model_from_registry("fraud_model_v2.3") _ = model(load_golden_batch("batch_1_sample.pt")) mem_after = process.memory_info().rss / 1024 / 1024 peak_mem = mem_after - mem_before assert peak_mem <= 1200.0, f"Peak memory {peak_mem}MB exceeds SLA"实操要点:
- 测试必须在与生产环境同规格的节点上执行(相同CPU型号、GPU型号、内核版本)。我们用K8s
nodeSelector固定测试Pod到专用benchmark节点池。 - 内存测试需排除Python GC干扰:
gc.disable()并在测试前后手动调用gc.collect()。 - 延迟测试必须包含端到端链路:从HTTP客户端发起请求,经Ingress Controller、Service、Pod,再返回——这才是用户真实体验。
3.3 服务契约验证:让API不只是“能通”,更要“可信”
服务契约(Service Contract)是OpenAPI 3.0规范的增强版,我们增加了三个关键扩展:
3.3.1 语义错误码契约(Semantic Error Code Contract)
标准HTTP状态码(如400 Bad Request)无法表达业务语义。我们在OpenAPIresponses中定义业务错误码:
paths: /predict: post: responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/PredictionResponse' '400': description: Client error with semantic detail content: application/json: schema: type: object properties: error: type: string enum: [MISSING_FEATURES, INVALID_FEATURE_VALUE, TIMESTAMP_SKEW, FEATURE_SCHEMA_MISMATCH] description: Business-specific error code, not generic HTTP reason message: type: string code: type: integer description: Internal system code for logging and tracing request_id: type: string description: Correlation ID for debugging实操要点:
- 所有
enum值必须在代码中定义为常量(如ERROR_MISSING_FEATURES = "MISSING_FEATURES"),禁止字符串硬编码。 - 每个错误码必须有对应的监控指标:
model_error_total{error="MISSING_FEATURES"},并配置告警(如5分钟内>10次触发PagerDuty)。 - 我们用
openapi-spec-validator工具在CI中校验YAML语法,用自研ContractChecker扫描代码,确保raise ModelException(ERROR_MISSING_FEATURES)与OpenAPI定义完全一致。
3.3.2 日志可追溯契约(Traceable Logging Contract)
每条预测请求必须生成三条可关联的日志:
- 接入层日志(Ingress Controller):记录
request_id,client_ip,http_method,path,status_code,latency_ms - 应用层日志(Model Service):记录
request_id,user_id,model_version,input_hash,output_score,output_risk_level,feature_retrieval_time - 审计层日志(Sidecar Container):记录
request_id,k8s_pod_name,k8s_namespace,timestamp,写入独立Syslog流
三者通过request_id(UUID4生成)全局串联。我们用OpenTelemetry Collector统一采集,配置servicegraphprocessor自动生成调用拓扑图。
实操要点:
request_id必须在Ingress层注入(Nginx Ingress Controller的configuration-snippet),禁止应用层生成——避免因应用重启丢失trace上下文。- 应用层日志必须用JSON格式,字段名严格遵循契约(如
input_hash而非hash_input),便于Logstash解析。 - 审计日志由独立Sidecar容器(
fluent-bit)采集,与主应用容器解耦,确保即使应用崩溃,审计日志仍完整。
3.3.3 配置可审计契约(Auditable Configuration Contract)
所有影响模型行为的配置必须满足:
- 存储在K8s
ConfigMap或Secret中(禁止硬编码、禁止环境变量) - 变更必须经GitOps流程(Argo CD同步)
- 每次变更生成审计事件,包含
operator,old_value,new_value,timestamp
例如,模型阈值配置configmap/model-thresholds.yaml:
apiVersion: v1 kind: ConfigMap metadata: name: model-thresholds annotations: checksum/config: "sha256:abc123..." # Argo CD用此校验配置一致性 data: risk_score_threshold: "0.75" # 影响score->risk_level映射 feature_timeout_ms: "5000" # 特征服务超时实操要点:
- 我们用
kubeaudit工具在CI中扫描YAML,禁止出现env:块或valueFrom.secretKeyRef以外的Secret引用方式。 - Argo CD的
Application资源必须启用syncPolicy.automated.prune=true,确保ConfigMap删除时自动清理。 - 所有配置变更必须关联Jira Ticket(如
PROD-1234),Argo CD的审计日志自动提取该Ticket号写入Elasticsearch。
4. 实操过程与核心环节实现:从代码到生产的完整链路
4.1 构建可重现的模型镜像:Dockerfile的魔鬼细节
我们的Dockerfile不是简单FROM python:3.9-slim,而是经过12轮生产验证的最小可行镜像:
# 使用多阶段构建,分离构建环境与运行环境 FROM nvidia/cuda:11.8.0-devel-ubuntu22.04 AS builder # 安装系统依赖(避免pip install时编译) RUN apt-get update && apt-get install -y \ build-essential \ libsm6 libxext6 \ && rm -rf /var/lib/apt/lists/* # 创建非root用户(安全强制要求) RUN groupadd -g 1001 -r mluser && useradd -S -u 1001 -r -g mluser mluser # 设置工作目录 WORKDIR /app # 复制requirements.txt并安装(利用Docker layer缓存) COPY requirements.txt . RUN pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir -r requirements.txt # 复制源码 COPY . . # 构建wheel包(预编译,加速运行时加载) RUN pip wheel --no-deps --wheel-dir /app/wheels . # 运行时镜像(极致精简) FROM nvidia/cuda:11.8.0-runtime-ubuntu22.04 # 复制构建好的wheel和依赖 COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages COPY --from=builder /app/wheels /app/wheels # 安装运行时必需的lib(不包含编译工具链) RUN apt-get update && apt-get install -y \ libglib2.0-0 libsm6 libxext6 libxrender-dev \ && rm -rf /var/lib/apt/lists/* # 创建运行用户 RUN groupadd -g 1001 -r mluser && useradd -S -u 1001 -r -g mluser mluser # 复制应用代码(不包含test/目录) COPY --from=builder /app/src /app/src COPY --from=builder /app/config /app/config # 设置非root用户 USER 1001:1001 # 验证GPU可用性(关键!) RUN nvidia-smi -L || (echo "GPU not available in runtime image" && exit 1) # 暴露端口 EXPOSE 8000 # 启动命令(使用gunicorn,非uvicorn——生产环境更稳) CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--worker-class", "gthread", "--threads", "2", "--timeout", "30", "--keep-alive", "5", "src.app:app"]关键参数解释与实操经验:
- CUDA版本锁定:
nvidia/cuda:11.8.0而非nvidia/cuda:11.8,避免minor版本升级导致libcudnn.so.8链接失败。我们曾因11.8.1镜像中cuDNN版本从8.6.0升至8.7.0,导致PyTorch 1.13.1加载失败,回滚耗时47分钟。 - 非root用户强制:K8s Pod Security Policy要求
runAsNonRoot: true,且USER指令必须在COPY之后,否则文件所有权为root,非root用户无法读取。 - gunicorn替代uvicorn:uvicorn在高并发下偶发
ConnectionResetError,gunicorn的gthreadworker模式经压测更稳定。--threads 2确保每个worker处理I/O密集型特征请求时不阻塞。 - GPU可用性验证:
RUN nvidia-smi -L在构建时执行,若失败则镜像构建中断——这比运行时才发现GPU不可用早30分钟。
注意:
requirements.txt中必须指定精确版本(如torch==1.13.1+cu117),禁用torch>=1.13.0。我们用pip-tools生成锁定文件:pip-compile --generate-hashes requirements.in > requirements.txt。
4.2 K8s部署清单:让模型服务像数据库一样可靠
我们的K8s部署不是简单kubectl apply -f deployment.yaml,而是包含7个相互关联的资源:
# 1. ServiceAccount(最小权限原则) apiVersion: v1 kind: ServiceAccount metadata: name: fraud-model-sa namespace: ml-prod annotations: eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/fraud-model-role --- # 2. Role(限定命名空间内权限) apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: fraud-model-role namespace: ml-prod rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get"] # 仅读取Secret,不列出 - apiGroups: ["monitoring.coreos.com"] resources: ["servicemonitors"] verbs: ["create", "update"] # 允许创建Prometheus监控 --- # 3. RoleBinding(绑定权限) apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: fraud-model-binding namespace: ml-prod subjects: - kind: ServiceAccount name: fraud-model-sa roleRef: kind: Role name: fraud-model-role apiGroup: rbac.authorization.k8s.io --- # 4. ConfigMap(配置中心) apiVersion: v1 kind: ConfigMap metadata: name: fraud-model-config namespace: ml-prod data: MODEL_VERSION: "v2.3.1" FEATURE_STORE_URL: "https://feature-store.internal" LOG_LEVEL: "INFO" --- # 5. Secret(敏感信息) apiVersion: v1 kind: Secret metadata: name: fraud-model-secret namespace: ml-prod type: Opaque data: FEATURE_STORE_TOKEN: <base64-encoded-token> --- # 6. Deployment(核心服务) apiVersion: apps/v1 kind: Deployment metadata: name: fraud-model namespace: ml-prod spec: replicas: 3 selector: matchLabels: app: fraud-model template: metadata: labels: app: fraud-model annotations: checksum/config: "sha256:$(sha256sum configmap.yaml | cut -d' ' -f1)" # 触发滚动更新 spec: serviceAccountName: fraud-model-sa containers: - name: model image: harbor.mycompany.com/ml/fraud-model:v2.3.1 ports: - containerPort: 8000 envFrom: - configMapRef: name: fraud-model-config - secretRef: name: fraud-model-secret resources: limits: cpu: "2000m" memory: "4Gi" nvidia.com/gpu: 1 requests: cpu: "1000m" memory: "2Gi" nvidia.com/gpu: 1 livenessProbe: httpGet: path: /healthz port: 8000 initialDelaySeconds: 60 periodSeconds: 30 readinessProbe: httpGet: path: /readyz port: 8000 initialDelaySeconds: 30 periodSeconds: 10 nodeSelector: kubernetes.io/os: linux cloud.google.com/gke-accelerator: nvidia-tesla-a10g # GPU拓扑调度 --- # 7. ServiceMonitor(Prometheus监控) apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: fraud-model-monitor namespace: ml-prod spec: selector: matchLabels: app: fraud-model endpoints: - port: metrics interval: 30s实操要点:
- GPU拓扑调度:
nodeSelector确保Pod只调度到A10g节点,避免因调度到T4节点导致CUDA版本不兼容。我们禁用tolerations,不允许多种GPU混部。 - 健康检查路径分离:
/healthz检查进程存活(如ps aux | grep gunicorn),/readyz检查业务就绪(如redis.ping()、feature_store.health_check()),避免流量打入未加载完模型的Pod。 - ConfigMap哈希注解:
checksum/config是Arfo CD的关键,当ConfigMap内容变更,Deployment的annotations随之改变,触发滚动更新——这是声明式配置生效的基石。