☰
KServe 通过 HTTP(S) URI 加载模型:sklearn 单文件与 TensorFlow 打包工件实战
2026/10/10 2:16:16 网站建设 项目流程
  • 模型推理服务
  • 云原生
  • 后端
  • 微服务
  • MLOps
  • 人工智能

【免费下载链接】kserve

Standardized Distributed Generative and Predictive AI Inference Platform for Scalable, Multi-Framework Deployment on Kubernetes

项目地址:https://gitcode.com/gh_mirrors/ks/kserve
点击查看免费下载

导读

在 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预测输入样本,下文将逐一展开。

前置条件:集群与环境准备

在开始之前,需要满足以下三项条件:

  1. 本地~/.kube/config指向一个已安装 KServe 的集群。KServe 的安装方式可参考 hack/kserve-install.sh 以及 install 目录下的发布版本清单(如kserve.yaml/kserve_kubeflow.yaml);
  2. 集群的 Istio Ingress 网关必须可以被外部网络访问(即入口流量可达);
  3. 集群的 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: |- ewoiYWNjb3VudC1uYW1lIjogInNvbWVfYWNjb3VudF9uYW1lIiwKInNlY3JldC1rZXkiOiAic29tZV9zZWNyZXRfa2V5Igp9

host 与 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: mysecret
apiVersion: 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 的实现可以看到两条路径的解析顺序:

  1. 注解优先:如果 InferenceService 上带serving.kserve.io/storageSecretName注解,CreateSecretVolumeAndEnvFromServiceAccount会直接根据注解中的 Secret 名挂载凭据并返回;
  2. 回退到 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 场景的完整链路可以概括为三步:

  1. 控制器读取 storageUri:InferenceService 的spec.predictor.<runtime>.storageUri字段是模型来源的唯一入口,KServe 控制器据此判断模型来自对象存储、PVC 还是纯 URI;
  2. 凭据注入:控制器调用CredentialBuilder(pkg/credentials/service_account_credentials.go)为 storage-initializer 容器注入环境变量或卷。对于 HTTP(S) 凭据,https.BuildSecretEnvs生成<host>-headers环境变量(见 pkg/credentials/https/https_secret.go);对于 S3/GCS 等类型,则分别注入对应的密钥环境变量或凭证文件卷;
  3. 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

项目地址:https://gitcode.com/gh_mirrors/ks/kserve
点击查看免费下载

相关推荐

上一篇:WebVM:浏览器中的Linux沙箱,重新定义云端开发边界
下一篇:SilentPatch终极修复指南:让GTA经典三部曲在现代电脑上完美运行

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询