- 人工智能
- 大模型
- MLOps
- LLMOps
- 模型推理服务
- 云原生
- 后端
【免费下载链接】seldon-core
An MLOps framework to package, deploy, monitor and manage thousands of production machine learning models
本文基于 Seldon Core 2 仓库中的
docs-gb/kubernetes/service-meshes/ambassador.md文档及其配套 notebook(service-meshes/ambassador/ambassador.ipynb)编写。文章讲解如何在 Seldon Core 2 的安装命名空间中通过 Ambassador / Emissary ingress 暴露模型推理端点,包括单模型路由、REST 与 gRPC 双协议 Mapping 配置、基于权重的流量拆分尝试,以及背后"单一seldon-meshService + 请求头路由"的实现原理。读完本文,你将能够独立完成 Ambassador 与 Seldon Core 2 的接入、验证与清理全流程,并理解seldon-model请求头在模型路由中的核心作用。
一、为什么需要 Service Mesh 接入:Seldon 的单一端点模型
Seldon Core 2 的模型与 Pipeline 在安装命名空间中统一通过一个名为seldon-mesh的 Service 端点对外暴露(见 service-meshes/README.md)。所有模型、Pipeline 和实验(Experiment)都可以通过这个唯一的 Service 端点访问,区别仅在于调用方需要在推理请求上携带特定的请求头(Header)。
这一设计使得 Seldon 对服务网格的选择完全无感——无论是 Ambassador、Istio 还是 Traefik,只要网格能将流量路由到这个seldon-mesh端点,并正确转发(或注入)所需的请求头即可。仓库在service-meshes/目录下按字母序提供了三个网格的示例集成:
- Ambassador(即本文主题,基于其 Emissary ingress 产品)
- Istio(service-meshes/istio)
- Traefik(service-meshes/traefik)
1.1 请求头路由:seldon-model
网格负责把请求送达seldon-mesh,但具体访问哪一个模型、Pipeline 或实验,则由请求头决定。Seldon 的 agent 组件会从请求中提取该头部并据此进行内部路由。
在 scheduler/pkg/util/constants.go 中,相关头常量定义如下:
// Headers const ( SeldonModelHeader = "seldon-model" SeldonPipelineHeader = "pipeline" SeldonInternalModelHeader = "seldon-internal-model" SeldonLoggingHeader = "Seldon-Logging" SeldonRouteHeader = "x-seldon-route" ExternalHeaderPrefix = "x-" SeldonModelHeaderSuffix = "model" SeldonPipelineHeaderSuffix = "pipeline" )在 agent 的反向代理实现 scheduler/pkg/agent/rproxy.go 中,externalModelName = req.Header.Get(util.SeldonModelHeader),即从seldon-model头中解析出目标模型名(该头还可能携带实验标签,用于 Envoy 路由)。因此,Ambassador 的 Mapping 中通过add_request_headers为每个请求注入seldon-model: <模型名>,本质上是替调用方完成"指定目标模型"这一步,从而可以把同一个入口 URL 按模型维度分流。
二、前置准备与测试环境
本示例假定你已经按照 Emissary 的官方教程安装好了 Emissary ingress(本文仓库示例测试使用的版本为emissary-ingress-7.3.2,通过 helm 安装)。文档中给出的假设与测试条件是:
- 前提:已按 Emissary 官方文档完成安装;
- 测试版本:
emissary-ingress-7.3.2(helm 安装); - 所有 Seldon 资源(
Model、Mapping、Host、Listener)均部署在seldon-mesh命名空间中。
此外,示例的完整可运行版本位于仓库 service-meshes/ambassador 目录,包含:
ambassador.ipynb:可交互执行的 Jupyter notebook;README.md:notebook 转换出的 Markdown 步骤记录;config/:kustomize 组织的基础(base)与覆盖(overlay)配置;static/:由config/经kustomize build生成的全量 YAML(供直接查看或应用)。
static/目录中的全量 YAML 由 Makefile 目标自动生成(见 service-meshes/ambassador/Makefile):
static/single-model.yaml: ./config/single-model/model.yaml ./config/single-model/mappings.yaml kustomize build config/single-model > static/single-model.yaml static/traffic-split.yaml: ./config/traffic-split/models.yaml ./config/single-model/mappings.yaml kustomize build config/traffic-split > static/traffic-split.yaml三、单模型示例:Iris 分类器接入 Ambassador
本节演示通过 Ambassador 暴露一个 Seldon Iris 分类器模型,配置内容包括:
- 默认的 Ambassador Host 与 Listener;
- 针对 REST 与 gRPC 两个端点的 Ambassador Mapping;
- 对应的 Seldon
Model资源。
3.1 完整 YAML 配置
以下完整配置保存在 service-meshes/ambassador/static/single-model.yaml(由config/single-model目录 kustomize 生成,源文件见 config/single-model/mappings.yaml 与 config/single-model/model.yaml):
apiVersion: getambassador.io/v3alpha1 kind: Host metadata: name: wildcard namespace: seldon-mesh spec: hostname: '*' requestPolicy: insecure: action: Route --- apiVersion: getambassador.io/v3alpha1 kind: Listener metadata: name: emissary-ingress-listener-8080 namespace: seldon-mesh spec: hostBinding: namespace: from: ALL port: 8080 protocol: HTTP securityModel: INSECURE --- apiVersion: getambassador.io/v3alpha1 kind: Mapping metadata: name: iris-grpc namespace: seldon-mesh spec: add_request_headers: seldon-model: value: iris grpc: true hostname: '*' prefix: /inference.GRPCInferenceService rewrite: "" service: seldon-mesh:80 --- apiVersion: getambassador.io/v3alpha1 kind: Mapping metadata: name: iris-http namespace: seldon-mesh spec: add_request_headers: seldon-model: value: iris hostname: '*' prefix: /v2/ rewrite: "" service: seldon-mesh:80 --- apiVersion: mlops.seldon.io/v1alpha1 kind: Model metadata: name: iris namespace: seldon-mesh spec: requirements: - sklearn storageUri: gs://seldon-models/mlserver/iris3.2 关键参数逐项说明
| 资源 / 字段 | 值 | 说明 |
|---|---|---|
Host.spec.hostname | * | 通配主机名,匹配任意 Host 头 |
Host.spec.requestPolicy.insecure.action | Route | 允许明文(非 TLS)请求直接路由,无需重定向 |
Listener.spec.port/protocol | 8080/HTTP | 监听 8080 端口的 HTTP 流量 |
Listener.spec.securityModel | INSECURE | 非 TLS 安全模型 |
Listener.spec.hostBinding.namespace.from | ALL | 允许来自所有命名空间的 Host 绑定 |
Mapping.spec.prefix | /inference.GRPCInferenceService | gRPC 服务前缀,对应 KServe/V2 数据面的 gRPC 服务名 |
Mapping.spec.prefix | /v2/ | REST 前缀,对应 V2 Inference Protocol 的 REST 路径 |
Mapping.spec.grpc | true | 仅 gRPC Mapping 设置,标记为 gRPC 路由 |
Mapping.spec.service | seldon-mesh:80 | 上游 Service,即 Seldon 的单一网格端点及其端口 |
Mapping.spec.rewrite | "" | 重写为空字符串,保留原始路径交给上游处理 |
Mapping.spec.add_request_headers.seldon-model | iris | 为每个请求注入模型选择头,值为 SeldonModel资源名 |
Model.spec.storageUri | gs://seldon-models/mlserver/iris | 模型存储位置(本示例为 GCS 上的 MLServer 格式 Iris 模型) |
Model.spec.requirements | [sklearn] | 声明模型运行所需的能力(capability),调度器据此匹配 Server |
注意 gRPC Mapping 的prefix使用/inference.GRPCInferenceService,这与数据面 gRPC 服务GRPCInferenceService的完整方法名(<Service>/<Method>,如inference.GRPCInferenceService/ModelInfer)对应。该服务定义位于仓库 apis/mlops/v2_dataplane/v2_dataplane.proto,其中包含ModelInfer、ModelReady、ServerLive、ServerMetadata等 RPC,以及 gRPC 调用所需的全部 message 定义(ModelInferRequest、ModelInferResponse、InferTensorContents等)。
3.3 部署与验证流程
以下命令均出自 notebook(ambassador.ipynb),可在安装了 Emissary 的集群上按序执行。
(1)获取 Emissary Ingress 的外部 IP
INGRESS_IP=!kubectl get svc emissary-ingress -n emissary -o jsonpath='{.status.loadBalancer.ingress[0].ip}' INGRESS_IP=INGRESS_IP[0] import os os.environ['INGRESS_IP'] = INGRESS_IP INGRESS_IP预期输出类似:
'172.21.255.1'(2)预览将要应用的配置
kustomize build config/single-model输出即为 3.1 节展示的完整 YAML(Host、Listener、两个 Mapping、一个 Model)。
(3)应用配置
kustomize build config/single-model | kubectl apply --validate=false -f -预期输出:
host.getambassador.io/wildcard created listener.getambassador.io/emissary-ingress-listener-8080 created mapping.getambassador.io/iris-grpc created mapping.getambassador.io/iris-http created model.mlops.seldon.io/iris created这里使用--validate=false是因为getambassador.io/v3alpha1与mlops.seldon.io/v1alpha1属于 CRD 资源,跳过 schema 校验以避免版本差异导致的校验失败。
(4)等待模型就绪
kubectl wait --for condition=ready --timeout=300s model --all -n seldon-mesh预期输出:
model.mlops.seldon.io/iris condition met(5)通过 REST 端点发起推理
使用 curl 调用 V2 数据面 REST 推理接口(路径POST /v2/models/<模型名>/infer):
curl -v http://${INGRESS_IP}/v2/models/iris/infer -H "Content-Type: application/json"\ -d '{"inputs": [{"name": "predict", "shape": [1, 4], "datatype": "FP32", "data": [[1, 2, 3, 4]]}]}'完整响应示例:
* Trying 172.21.255.1... * Connected to 172.21.255.1 (172.21.255.1) port 80 (#0) > POST /v2/models/iris/infer HTTP/1.1 > Host: 172.21.255.1 > User-Agent: curl/7.47.0 > Accept: */* > Content-Type: application/json > Content-Length: 94 > * upload completely sent off: 94 out of 94 bytes < HTTP/1.1 200 OK < content-length: 196 < content-type: application/json < date: Sat, 16 Apr 2022 15:45:43 GMT < server: envoy < x-envoy-upstream-service-time: 792 < seldon-route: iris_1 < * Connection #0 to host 172.21.255.1 left intact {"model_name":"iris_1","model_version":"1","id":"72ac79f5-b355-4be3-b8c5-2ebedaa39f60","parameters":null,"outputs":[{"name":"predict","shape":[1],"datatype":"INT64","parameters":null,"data":[2]}]}需要注意几点:
- 响应头
server: envoy表明请求确实经由 Emissary(基于 Envoy)转发; - 响应头
seldon-route: iris_1是 Seldon 返回的"粘性会话"路由头(常量x-seldon-route见 scheduler/pkg/util/constants.go),用于标识本次实际执行的模型实例; - 返回 JSON 中的
model_name为iris_1,这是模型iris当前被调度到的具体实例标识。
(6)通过 gRPC 端点发起推理
使用 grpcurl 调用ModelInfer,需要引用仓库中的 proto 定义文件apis/mlops/v2_dataplane/v2_dataplane.proto:
grpcurl -d '{"model_name":"iris","inputs":[{"name":"input","contents":{"fp32_contents":[1,2,3,4]},"datatype":"FP32","shape":[1,4]}]}' \ -plaintext \ -import-path ../../apis \ -proto ../../apis/mlops/v2_dataplane/v2_dataplane.proto \ ${INGRESS_IP}:80 inference.GRPCInferenceService/ModelInfer说明:命令中的-import-path ../../apis与-proto ../../apis/mlops/v2_dataplane/v2_dataplane.proto是相对于 notebook 所在目录(service-meshes/ambassador/)的路径;若在其他目录执行,请相应改为从仓库根目录出发的 apis/mlops/v2_dataplane/v2_dataplane.proto。gRPC 请求体采用InferTensorContents的fp32_contents字段携带张量数据(该 message 定义在 proto 文件第 308 行附近)。
预期响应:
{ "modelName": "iris_1", "modelVersion": "1", "outputs": [ { "name": "predict", "datatype": "INT64", "shape": [ "1" ], "contents": { "int64Contents": [ "2" ] } } ] }(7)清理资源
kustomize build config/single-model | kubectl delete -f -预期输出:
host.getambassador.io "wildcard" deleted listener.getambassador.io "emissary-ingress-listener-8080" deleted mapping.getambassador.io "iris-grpc" deleted mapping.getambassador.io "iris-http" deleted model.mlops.seldon.io "iris" deleted四、流量拆分示例:两个模型之间的权重路由
Seldon 原生提供了服务网格无关的Experiment资源来做流量拆分,但如果希望把流量控制交给 Ambassador 层,示例也给出了在两个模型iris1/iris2之间按权重拆分的配置(完整文件见 service-meshes/ambassador/static/traffic-split.yaml,源文件见 config/traffic-split/mappings.yaml 与 config/traffic-split/models.yaml)。
⚠️重要提醒(来自官方文档):流量拆分目前因 Emissary 的已知问题(issue #4062)无法正常工作。官方推荐使用 Seldon 的
Experiment资源进行流量拆分,而非依赖 Ambassador 的权重路由。本节配置保留在此仅作为参考示例。
4.1 完整 YAML 配置
apiVersion: getambassador.io/v3alpha1 kind: Host metadata: name: wildcard namespace: seldon-mesh spec: hostname: '*' requestPolicy: insecure: action: Route --- apiVersion: getambassador.io/v3alpha1 kind: Listener metadata: name: emissary-ingress-listener-8080 namespace: seldon-mesh spec: hostBinding: namespace: from: ALL port: 8080 protocol: HTTP securityModel: INSECURE --- apiVersion: getambassador.io/v3alpha1 kind: Mapping metadata: name: iris1-grpc namespace: seldon-mesh spec: add_request_headers: seldon-model: value: iris1 grpc: true hostname: '*' prefix: /inference.GRPCInferenceService rewrite: "" service: seldon-mesh:80 --- apiVersion: getambassador.io/v3alpha1 kind: Mapping metadata: name: iris1-http namespace: seldon-mesh spec: add_request_headers: seldon-model: value: iris1 add_response_headers: seldon_model: value: iris1 hostname: '*' prefix: /v2 rewrite: "" service: seldon-mesh:80 --- apiVersion: getambassador.io/v3alpha1 kind: Mapping metadata: name: iris2-grpc namespace: seldon-mesh spec: add_request_headers: seldon-model: value: iris2 grpc: true hostname: '*' prefix: /inference.GRPCInferenceService rewrite: "" service: seldon-mesh:80 weight: 50 --- apiVersion: getambassador.io/v3alpha1 kind: Mapping metadata: name: iris2-http namespace: seldon-mesh spec: add_request_headers: seldon-model: value: iris2 add_response_headers: seldon_model: value: iris2 hostname: '*' prefix: /v2 rewrite: "" service: seldon-mesh:80 weight: 50 --- apiVersion: mlops.seldon.io/v1alpha1 kind: Model metadata: name: iris1 namespace: seldon-mesh spec: requirements: - sklearn storageUri: gs://seldon-models/mlserver/iris --- apiVersion: mlops.seldon.io/v1alpha1 kind: Model metadata: name: iris2 namespace: seldon-mesh spec: requirements: - sklearn storageUri: gs://seldon-models/mlserver/iris4.2 与单模型示例的差异要点
- 定义了两个
Model资源(iris1、iris2),使用相同的模型存储gs://seldon-models/mlserver/iris,可分别加载为两个模型实例; iris1的 HTTP Mapping 前缀为/v2,而单模型示例中为/v2/——注意尾部斜杠的细微差别,前缀匹配语义因此不同;iris2的 gRPC 与 HTTP Mapping 都设置了weight: 50,而iris1未显式设置weight(默认为 0),意图是按 50/50 拆分流量;- 两个 HTTP Mapping 额外配置了
add_response_headers.seldon_model,用于在响应中回传实际服务模型名,便于观察分流结果。
4.3 部署与验证流程
(1)预览并应用配置
kustomize build config/traffic-split kustomize build config/traffic-split | kubectl apply -f -预期输出:
host.getambassador.io/wildcard created listener.getambassador.io/emissary-ingress-listener-8080 created mapping.getambassador.io/iris1-grpc created mapping.getambassador.io/iris1-http created mapping.getambassador.io/iris2-grpc created mapping.getambassador.io/iris2-http created model.mlops.seldon.io/iris1 created model.mlops.seldon.io/iris2 created(2)等待模型就绪
kubectl wait --for condition=ready --timeout=300s model --all -n seldon-mesh预期输出:
model.mlops.seldon.io/iris1 condition met model.mlops.seldon.io/iris2 condition met(3)REST 推理(注意路径仍使用iris)
curl -v http://${INGRESS_IP}/v2/models/iris/infer -H "Content-Type: application/json" \ -d '{"inputs": [{"name": "predict", "shape": [1, 4], "datatype": "FP32", "data": [[1, 2, 3, 4]]}]}'预期响应:
* Trying 172.21.255.1... * Connected to 172.21.255.1 (172.21.255.1) port 80 (#0) > POST /v2/models/iris/infer HTTP/1.1 > Host: 172.21.255.1 > User-Agent: curl/7.47.0 > Accept: */* > Content-Type: application/json > Content-Length: 94 > * upload completely sent off: 94 out of 94 bytes < HTTP/1.1 200 OK < content-length: 197 < content-type: application/json < date: Sat, 16 Apr 2022 15:46:17 GMT < server: envoy < x-envoy-upstream-service-time: 920 < seldon-route: iris2_1 < seldon_model: iris2 < * Connection #0 to host 172.21.255.1 left intact {"model_name":"iris2_1","model_version":"1","id":"ed521c32-cd85-4cb8-90eb-7c896803f271","parameters":null,"outputs":[{"name":"predict","shape":[1],"datatype":"INT64","parameters":null,"data":[2]}]}从响应可以看出,本次请求经 Ambassador 路由后命中iris2(seldon-route: iris2_1、响应头seldon_model: iris2、返回 JSON 中model_name为iris2_1)。注意 curl 的请求 URL 中模型名是iris,实际路由到哪个模型由 Mapping 注入的seldon-model头决定,这也是"单一入口 + 头部路由"设计的直观体现。
(4)gRPC 推理
grpcurl -d '{"model_name":"iris1","inputs":[{"name":"input","contents":{"fp32_contents":[1,2,3,4]},"datatype":"FP32","shape":[1,4]}]}' \ -plaintext \ -import-path ../../apis \ -proto ../../apis/mlops/v2_dataplane/v2_dataplane.proto \ ${INGRESS_IP}:80 inference.GRPCInferenceService/ModelInfer预期响应(同样命中了iris2实例):
{ "modelName": "iris2_1", "modelVersion": "1", "outputs": [ { "name": "predict", "datatype": "INT64", "shape": [ "1" ], "contents": { "int64Contents": [ "2" ] } } ] }(5)清理资源
kustomize build config/traffic-split | kubectl delete -f -预期输出:
host.getambassador.io "wildcard" deleted listener.getambassador.io "emissary-ingress-listener-8080" deleted mapping.getambassador.io "iris1-grpc" deleted mapping.getambassador.io "iris1-http" deleted mapping.getambassador.io "iris2-grpc" deleted mapping.getambassador.io "iris2-http" deleted model.mlops.seldon.io "iris1" deleted model.mlops.seldon.io "iris2" deleted五、源码视角:流量如何在 Ambassador 与 Seldon 之间衔接
5.1 kustomize 组织方式
service-meshes/ambassador/config/采用 kustomize 的 base/overlay 结构:
config/listener/是公共 base,包含 Host 与 Listener(host.yaml、listener.yaml);config/single-model/以 base 为基础,叠加单个模型的Model与两个 Mapping(kustomization.yaml);config/traffic-split/以 base 为基础,叠加两个模型的Model与四个 Mapping(kustomization.yaml)。
两个 overlay 的kustomization.yaml都通过bases: - ../listener复用 Host/Listener,并通过namespace: seldon-mesh统一设置命名空间。这样,网格层的公共配置只需维护一份。
5.2seldon-model头如何驱动路由
- Ambassador 侧:Mapping 通过
add_request_headers.seldon-model注入模型名(Ambassador/Emissary 会在转发时把配置头附加到请求上)。 - Seldon 侧:
seldon-mesh服务背后的 agent 反向代理(scheduler/pkg/agent/rproxy.go)读取seldon-model头确定目标模型,并将内部模型名放入seldon-internal-model头继续向下游转发;gRPC 路径同理(见 scheduler/pkg/agent/rproxy_grpc.go)。 - 实验(Experiment)场景:
seldon-model头还可以携带实验标签,供网格层 Envoy 依据实验规则做流量路由,这正是 scheduler/pkg/util/constants.go 中注释所体现的设计意图。
5.3 数据面协议:REST 与 gRPC 的同一套契约
Ambassador 的 REST Mapping(/v2/)与 gRPC Mapping(/inference.GRPCInferenceService)其实指向同一个 Seldon 数据面协议——KServe V2 / Triton Inference Protocol。协议的服务与 message 定义统一在 apis/mlops/v2_dataplane/v2_dataplane.proto:
service GRPCInferenceService提供ServerLive、ServerReady、ModelReady、ServerMetadata、ModelMetadata、ModelInfer、ModelStreamInfer以及控制面相关的RepositoryIndex/RepositoryModelLoad/RepositoryModelUnload;- REST 的
/v2/models/<model>/infer与 gRPC 的ModelInfer语义一一对应,这也是为什么 curl 与 grpcurl 的请求体可以如此相似(name/shape/datatype/data或contents)。
六、常见问题与注意事项
- 流量拆分目前不可用:Ambassador 方式的
weight拆分受 Emissary 已知问题(issue #4062)影响,当前无法正常工作。官方建议改用 Seldon 的Experiment资源做网格无关的流量拆分,相关示例可参考 docs-gb/kubernetes/resources/experiment.md。 - 前缀斜杠的差异:单模型示例 REST 前缀为
/v2/(带尾部斜杠),流量拆分示例为/v2(不带尾部斜杠),两者匹配语义不同,复制配置时注意区分。 --validate=false的必要性:mlops.seldon.io/v1alpha1等 CRD 资源在部分 kubectl 版本下会触发 schema 校验错误,示例中的kubectl apply --validate=false正是为规避此问题。- 外部访问地址:示例通过 LoadBalancer 类型的
emissary-ingressService 获取入口 IP,端口 80 对应 Listener 的 8080 端口映射。实际集群中也可能使用 NodePort 或 Ingress Controller 的其它暴露方式,需按环境调整INGRESS_IP。 - gRPC 客户端工具:验证 gRPC 端点需要 grpcurl,并须提供 proto 文件;请直接使用仓库内 apis/mlops/v2_dataplane/v2_dataplane.proto,避免自行维护一份可能失配的协议定义。
- 人工智能
- 大模型
- MLOps
- LLMOps
- 模型推理服务
- 云原生
- 后端
【免费下载链接】seldon-core
An MLOps framework to package, deploy, monitor and manage thousands of production machine learning models
相关推荐
Seldon Core 2 服务网格集成实战:基于 seldon-mesh 单端点与请求头路由对接 Ambassador、Istio、Traefik
Seldon Core 2 服务网格集成实战:基于 seldon mesh 单端点与请求头路由对接 Ambassador、Istio、Traefik 导读 Se
人工智能大模型MLOpsLLMOps模型推理服务云原生后端Seldon Core 2服务网格集成终极指南:Istio、Ambassador和Traefik配置详解
Seldon Core 2服务网格集成终极指南:Istio、Ambassador和Traefik配置详解 Seldon Core是一个强大的MLOps框架,专门
人工智能大模型MLOpsLLMOps模型推理服务云原生后端Seldon Core 2 集成 AWS MSK:SASL/SCRAM 认证配置实战指南
Seldon Core 2 集成 AWS MSK:SASL/SCRAM 认证配置实战指南 本文面向需要将 Seldon Core 2 接入 Amazon MSK
人工智能大模型MLOpsLLMOps模型推理服务云原生后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考