- 模型推理服务
- 云原生
- 后端
- 微服务
- MLOps
- 人工智能
【免费下载链接】kserve
Standardized Distributed Generative and Predictive AI Inference Platform for Scalable, Multi-Framework Deployment on Kubernetes
导读
在 KServe 中,除了从对象存储(S3/GCS/Azure)或 PVC 拉取模型外,你还可以直接通过storageUri指向一个暴露在http/https端点上的模型对象来完成推理服务部署。本文以 docs/samples/storage/uri/README.md 为核心,完整演示两种典型场景——单文件模型(如 sklearn 的model.joblib)与打包工件(如 TensorFlow 的tar/tgz),并深入解析带鉴权头的 HTTP(S) 请求如何通过 Secret 注入,以及 KServe 控制器底层是如何把storageUri翻译成存储初始化动作的。读完本文,你将能够独立完成"模型放在任意 HTTP(S) 可达位置、无需对象存储也能用 KServe 上线推理服务"的完整配置。
URI 存储方式概述:单文件与打包工件
storageUri选项的核心能力,是把模型对象通过一个 URI 暴露给 KServe。它支持两类形态:
- 单文件模型:例如 sklearn 由 joblib 导出的
model.joblib,只有一个文件,直接指向即可; - 打包工件(artifacts):例如
tar或zip归档,内含某一模型类型所需的全部依赖文件。典型代表是 TensorFlow,它必须遵循严格的目录结构(saved_model.pb与variables/目录)才能被 servable。
仓库中该示例的完整配套文件位于 docs/samples/storage/uri/ 目录,包含sklearn.yaml、tensorflow.yaml两个 InferenceService 清单以及input.json预测输入样本,下文将逐一展开。
前置条件:集群与环境准备
在开始之前,需要满足以下三项条件:
- 本地
~/.kube/config指向一个已安装 KServe 的集群。KServe 的安装方式可参考 hack/kserve-install.sh 以及 install 目录下的发布版本清单(如kserve.yaml/kserve_kubeflow.yaml); - 集群的 Istio Ingress 网关必须可以被外部网络访问(即入口流量可达);
- 集群的 Istio Egress 网关必须允许
http/https流量出站——因为推理 Pod 需要主动去拉取位于集群外部的模型 URI。
为 HTTP/HTTPS 请求创建访问凭据 Secret
如果你的 HTTP(S) 服务请求不需要携带请求头,可以跳过本节。需要携带时(例如私有模型仓库的账号鉴权),按以下格式定义一个Opaque类型的 Secret:
apiVersion: v1 kind: Secret metadata: name: mysecret type: Opaque data: https-host: ZXhhbXBsZS5jb20= headers: |- ewoiYWNjb3VudC1uYW1lIjogInNvbWVfYWNjb3VudF9uYW1lIiwKInNlY3JldC1rZXkiOiAic29tZV9zZWNyZXRfa2V5Igp9host 与 headers 的编码规则
https-host与headers两个键的值都必须是base64 编码后的内容,且headers需要是格式合法的 JSON 字符串。编码过程如下:
example.com # echo -n "example.com" | base64 ZXhhbXBsZS5jb20= --- { "account-name": "some_account_name", "secret-key": "some_secret_key" } # echo -n '{\n"account-name": "some_account_name",\n"secret-key": "some_secret_key"\n}' | base64 ewoiYWNjb3VudC1uYW1lIjogInNvbWVfYWNjb3VudF9uYW1lIiwKInNlY3JldC1rZXkiOiAic29tZV9zZWNyZXRfa2V5Igp9即:
https-host:对模型所在域名(如example.com)做echo -n "..." | base64;headers:先把请求头写成 JSON(注意转义换行符\n),再整体 base64。
源码视角:Secret 如何被翻译为请求头
从源码可以确认这两个键名与注入机制。在 pkg/credentials/https/https_secret.go 中定义了常量:
HTTPSHost = "https-host":指明该 Secret 作用于哪个主机;HEADERS = "headers":携带请求头 JSON。
其核心函数BuildSecretEnvs会同时读取https-host与headers两个键,然后构造一个名为<host>-headers的环境变量注入到 storage-initializer 容器中:
envs = append(envs, corev1.EnvVar{ Name: string(uriHost) + HeadersSuffix, // 形如 example.com-headers Value: string(headers), })这也解释了文档中"这些 headers 会被应用到所有相同 host 的 http/https 请求上"的行为——凭据是按 host 维度命名的,拉取模型时只有 URI 的 host 与 Secret 中https-host匹配时才会附加对应请求头。
引用 Secret 的方式一:InferenceService 注解
通过注解serving.kserve.io/storageSecretName在 InferenceService 上直接引用 Secret:
apiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: sklearn-from-uri annotations: serving.kserve.io/storageSecretName: mysecret spec: predictor: sklearn: storageUri: https://<your-model-host>/sklearn/frozen/model.joblib?raw=true引用 Secret 的方式二:ServiceAccount
你也可以把 Secret 挂到 ServiceAccount 的secrets字段上,然后在 predictor 中指定serviceAccountName:
apiVersion: v1 kind: ServiceAccount metadata: name: sa secrets: - name: mysecretapiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: sklearn-from-uri spec: predictor: serviceAccountName: sa sklearn: storageUri: https://<your-model-host>/sklearn/frozen/model.joblib?raw=true两种引用方式的优先级
从 pkg/credentials/service_account_credentials.go 的实现可以看到两条路径的解析顺序:
- 注解优先:如果 InferenceService 上带
serving.kserve.io/storageSecretName注解,CreateSecretVolumeAndEnvFromServiceAccount会直接根据注解中的 Secret 名挂载凭据并返回; - 回退到 ServiceAccount:否则遍历
serviceAccount.Secrets列表,逐个调用mountSecretCredential尝试挂载。
mountSecretCredential(service_account_credentials.go)会按 Secret 内的键类型分发:当检测到https-host键时,走https.BuildSecretEnvs注入 URI 请求头;其余键(如 S3 的AWS_ACCESS_KEY_ID、GCS 凭证文件、Azure、HDFS、HuggingFace token 等)则分别走对应的凭据构建器。值得注意的是,注解名serving.kserve.io/storageSecretName并非硬编码,而是从credentials配置段的storageSecretNameAnnotation字段读取(默认值即此值),该配置项的定义与单测断言可参见 pkg/credentials/service_account_credentials_test.go。
实战一:sklearn 单文件模型
训练并冻结模型
以下脚本用鸢尾花(iris)数据集训练一个简单的 SVM 分类器,并冻结为model.joblib。注意:KServe 的 sklearn server 要求scikit-learn==1.0.2:
from sklearn import svm from sklearn import datasets import joblib def train(X, y): clf = svm.SVC(gamma='auto') clf.fit(X, y) return clf def freeze(clf, path='../frozen'): joblib.dump(clf, f'{path}/model.joblib') return True if __name__ == '__main__': iris = datasets.load_iris() X, y = iris.data, iris.target clf = train(X, y) freeze(clf)接下来把冻结出的model.joblib放到一个 HTTP(S) 可达的位置(例如推送到某个代码托管平台的仓库中,并使用其原始文件下载地址),得到形如https://<your-model-host>/sklearn/frozen/model.joblib?raw=true的 URI。
创建 InferenceService
仓库配套示例文件 docs/samples/storage/uri/sklearn.yaml 即为此场景的清单:
apiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: sklearn-from-uri spec: predictor: sklearn: storageUri: https://<your-model-host>/sklearn/frozen/model.joblib?raw=true应用该 CRD:
kubectl apply -f sklearn_uri.yaml预期输出:
$ inferenceservice.serving.kserve.io/sklearn-from-uri created运行预测
先确定 Ingress 的 IP 与端口,并设置INGRESS_HOST与INGRESS_PORT环境变量。随后使用 curl 命中/v1/models/<model>:predict端点(预测输入样本可参考 docs/samples/storage/uri/input.json,其instances为两条鸢尾花特征向量):
MODEL_NAME=sklearn-from-uri INPUT_PATH=@./input.json curl -v -H "Host: ${SERVICE_HOSTNAME}" http://${INGRESS_HOST}:${INGRESS_PORT}/v1/models/$MODEL_NAME:predict -d $INPUT_PATH预期输出:
$ * Trying 10.0.1.16... * TCP_NODELAY set % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Connected to 10.0.1.16 (10.0.1.16) port 30749 (#0) > POST /v1/models/sklearn-from-uri:predict HTTP/1.1 > Host: sklearn-from-uri.kfserving-uri-storage.example.com > User-Agent: curl/7.58.0 > Accept: */* > Content-Length: 86 > Content-Type: application/x-www-form-urlencoded > } [86 bytes data] * upload completely sent off: 86 out of 86 bytes < HTTP/1.1 200 OK < content-length: 23 < content-type: application/json; charset=UTF-8 < date: Thu, 06 Aug 2020 23:13:42 GMT < server: istio-envoy < x-envoy-upstream-service-time: 7 < { [23 bytes data] 100 109 100 23 100 86 605 2263 --:--:-- --:--:-- --:--:-- 2868 * Connection #0 to host 10.0.1.16 left intact { "predictions": [ 1, 1 ] }返回 200 OK,predictions数组中两条样本的预测类别均为1,说明模型已从 URI 成功加载并完成推理。
实战二:TensorFlow 打包工件
TensorFlow 模型由多个文件组成,必须按严格目录结构组织(saved_model.pb+variables/目录),无法用单个文件 URI 表达,因此需要先打成tar或tgz再上传。这正好验证了 URI 方式对"打包工件"的支持。
训练并保存模型
以下脚本同样基于 iris 数据训练一个简单的 Keras 全连接网络,并将模型保存到../frozen/0001(带版本号的目录):
from sklearn import datasets import numpy as np import tensorflow as tf def _ohe(targets): y = np.zeros((150, 3)) for i, label in enumerate(targets): y[i, label] = 1.0 return y def train(X, y, epochs, batch_size=16): model = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=(4,)), tf.keras.layers.Dense(16, activation=tf.nn.relu), tf.keras.layers.Dense(16, activation=tf.nn.relu), tf.keras.layers.Dense(3, activation='softmax') ]) model.compile(tf.keras.optimizers.RMSprop(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy']) model.fit(X, y, epochs=epochs) return model def freeze(model, path='../frozen'): model.save(f'{path}/0001') return True if __name__ == '__main__': iris = datasets.load_iris() X, targets = iris.data, iris.target y = _ohe(targets) model = train(X, y, epochs=50) freeze(model)训练结束后的处理与 sklearn 不同:需要先把冻结产物打包成 tarball:
cd ../frozen tar -cvf artifacts.tar 0001/ gzip < artifacts.tar > artifacts.tgz其中假定0001/目录结构如下:
|-- 0001/ |-- saved_model.pb |-- variables/ |--- variables.data-00000-of-00001 |--- variables.index注意:对 TensorFlow 而言,从指定版本号的目录构建 tarball 是必需的(0001/即版本号,SavedModel 加载依赖该版本化目录结构)。打包完成后,把.tar或.tgz文件推送到某个远程 URI 即可。
创建 InferenceService
仓库配套示例文件 docs/samples/storage/uri/tensorflow.yaml 即为此场景的清单:
apiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: tensorflow-from-uri-gzip spec: predictor: tensorflow: storageUri: https://<your-model-host>/tensorflow/frozen/model_artifacts.tar.gz应用该 CRD:
kubectl apply -f tensorflow_uri.yaml预期输出:
$ inferenceservice.serving.kserve.io/tensorflow-from-uri created运行预测
同样先设置INGRESS_HOST与INGRESS_PORT,然后发起预测:
MODEL_NAME=tensorflow-from-uri INPUT_PATH=@./input.json curl -v -H "Host: ${SERVICE_HOSTNAME}" http://${INGRESS_HOST}:${INGRESS_PORT}/v1/models/$MODEL_NAME:predict -d $INPUT_PATH预期输出:
$ * Trying 10.0.1.16... * TCP_NODELAY set % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Connected to 10.0.1.16 (10.0.1.16) port 30749 (#0) > POST /v1/models/tensorflow-from-uri:predict HTTP/1.1 > Host: tensorflow-from-uri.default.example.com > User-Agent: curl/7.58.0 > Accept: */* > Content-Length: 86 > Content-Type: application/x-www-form-urlencoded > } [86 bytes data] * upload completely sent off: 86 out of 86 bytes < HTTP/1.1 200 OK < content-length: 112 < content-type: application/json < date: Thu, 06 Aug 2020 23:21:19 GMT < x-envoy-upstream-service-time: 151 < server: istio-envoy < { [112 bytes data] 100 198 100 112 100 86 722 554 --:--:-- --:--:-- --:--:-- 1285 * Connection #0 to host 10.0.1.16 left intact { "predictions": [ [ 0.0204100646, 0.680984616, 0.298605353 ], [ 0.0296604875, 0.658412039, 0.311927497 ] ] }响应中每条样本返回一个三维概率分布向量(softmax 输出),模型加载与推理链路正常。
底层原理:storageUri 如何驱动模型拉取
从源码结构看,URI 场景的完整链路可以概括为三步:
- 控制器读取 storageUri:InferenceService 的
spec.predictor.<runtime>.storageUri字段是模型来源的唯一入口,KServe 控制器据此判断模型来自对象存储、PVC 还是纯 URI; - 凭据注入:控制器调用
CredentialBuilder(pkg/credentials/service_account_credentials.go)为 storage-initializer 容器注入环境变量或卷。对于 HTTP(S) 凭据,https.BuildSecretEnvs生成<host>-headers环境变量(见 pkg/credentials/https/https_secret.go);对于 S3/GCS 等类型,则分别注入对应的密钥环境变量或凭证文件卷; - storage-initializer 下载:注入完成后的容器启动时会根据
storageUri协议选择对应下载器:直接下载单文件,或先下载归档再解压到约定目录(仓库中该组件的实现位于 python/storage-initializer)。TensorFlow 示例中0001/版本目录随 tarball 解压后即落在模型目录下,从而满足 SavedModel 的 servable 结构要求。
另外,storageUri与存储凭据的默认配置(如默认 storage-config Secret 名storage-config、注解名等)在 pkg/constants/constants.go 中有统一定义,并可通过credentials配置段覆盖,参见 config/configmap/inferenceservice.yaml。
常见问题与注意事项
- headers 与 host 的匹配:Secret 中
https-host的值必须与模型 URI 的 host 完全一致,请求头才会被附加。若 URI 指向多台主机,需要为每台主机分别创建带对应https-host的 Secret; - base64 编码细节:
https-host与headers均为 base64 值,务必用echo -n(不要带换行)编码,headers必须是合法 JSON 并在编码前处理好转义; - TensorFlow 版本目录:打包时必须包含带版本号的子目录(如
0001/),这是 TensorFlow SavedModel 可被服务的硬性要求; - 依赖版本:示例中的 sklearn 场景要求
scikit-learn==1.0.2,避免 joblib 序列化格式与 KServe sklearn server 运行环境不兼容; - Egress 放行:推理 Pod 拉取 URI 依赖集群 Egress 网关允许出站
http/https流量,配置了严格出口策略时需要显式放行,否则 Pod 会因拉取超时而反复重启; - 单文件与归档的选择:单文件模型(如 sklearn 的 joblib、XGBoost 的模型文件)直接指向文件 URI 即可;多文件、有目录结构要求的模型(如 TensorFlow SavedModel)必须先归档为
tar/tgz再上传,storageUri指向归档文件。
至此,你已经掌握了 KServe 中"不依赖对象存储、直接用 HTTP(S) URI 上线模型"的完整套路:既能通过单文件 URI 服务 sklearn 这类轻量模型,也能通过归档 URI 服务 TensorFlow 这类多文件模型,并能为私有模型仓库配置按 host 匹配的请求头鉴权。
- 模型推理服务
- 云原生
- 后端
- 微服务
- MLOps
- 人工智能
【免费下载链接】kserve
Standardized Distributed Generative and Predictive AI Inference Platform for Scalable, Multi-Framework Deployment on Kubernetes
相关推荐
ChatDev 附件与工件 API 实战指南:文件上传、实时事件与打包下载
ChatDev 附件与工件 API 实战指南:文件上传、实时事件与打包下载 导读 :本文以 ChatDev 2.0(LLM 驱动的多 Agent 协作平台)中的
AI AgentAgent 框架Agent 工作流低代码工作流自动化OpenBMBPaddleNLP 模型下载与加载实战:以 distilroberta-base 为例(模型文件清单、CLI 下载与快速推理)
PaddleNLP 模型下载与加载实战:以 distilroberta base 为例(模型文件清单、CLI 下载与快速推理) 本篇指南围绕本仓库 modelc
人工智能深度学习计算机视觉NLP语音Velero v1.12 新特性深度解析:CSI 快照数据移动、Resource Modifiers 与多 VolumeSnapshotClass
Velero v1.12 新特性深度解析:CSI 快照数据移动、Resource Modifiers 与多 VolumeSnapshotClass 导读 本文以
模型推理服务云原生后端微服务MLOps人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考