InferenceService 到底幫你生了什麼
在 OpenShift AI 上讓一個模型變成可以打的 HTTP 端點,你要寫的東西是這個:
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: llm-scratch
namespace: llm-serve-demo
annotations:
serving.kserve.io/deploymentMode: RawDeployment
spec:
predictor:
serviceAccountName: llm-sa
containers:
- name: kserve-container
image: <你的 registry>/tools/llm-serve:cpu
ports:
- containerPort: 8000
env:
- name: STORAGE_URI # ← 這一行是關鍵
value: s3://models/llm/
- name: ARTIFACTS
value: /mnt/models
readinessProbe:
httpGet: { path: /health, port: 8000 }
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests: { cpu: "500m", memory: 1Gi }
limits: { cpu: "2", memory: 3Gi }
41 行。 這篇是關於這 41 行換來了什麼。
生出來的東西
oc apply 之後,去找「誰的 owner 是 InferenceService」:
oc get deployment,service -n llm-serve-demo -o json | \
jq -r '.items[] | select(.metadata.ownerReferences[]?.kind=="InferenceService")
| "\(.kind)/\(.metadata.name)"'
Deployment/llm-scratch-predictor
Service/llm-scratch-predictor
就兩個。 一個 Deployment、一個 ClusterIP Service。
在 3.x 的 RawDeployment 模式下,KServe 沒有幫你做什麼魔法——
它就是把你的容器包成一個標準的 k8s 部署。這是好事:
排錯回到你熟悉的 oc get deploy/pod/svc,不用先學 Knative。
但 pod 裡不只你的容器
oc get pod -l serving.kserve.io/inferenceservice=llm-scratch \
-o jsonpath='{range .items[0].spec.initContainers[*]}init: {.name}{"\n"}{end}{range .items[0].spec.containers[*]}container: {.name}{"\n"}{end}'
init: storage-initializer
container: kserve-container ← 你的
container: kube-rbac-proxy ← ODH 加的
你寫了一個容器,跑起來是三個。
storage-initializer:這是 STORAGE_URI 的作用
你在 YAML 裡寫了 STORAGE_URI: s3://models/llm/,
KServe 就會在你的容器啟動前塞一個 init container 進去,
把 S3 上的東西下載到 /mnt/models。
所以你的應用程式只要讀本地路徑就好(我用 ARTIFACTS=/mnt/models 告訴它去哪讀),
完全不用知道 S3 的存在,也不用在程式裡放任何 credential。
這個設計的重點是:模型權重不進 image。
- image 是不可變的、要簽章的、要掃描的、要走版本流程的
- 模型是會換的、可能一天換三次的
把它們綁在一起,等於每次換模型都要重 build 一次 image。分開之後, 同一個 image 可以服務不同的模型,靠環境變數指到不同的 S3 路徑。
Java 類比:像你的 jar 不會把
application.yml打包進去, 而是在啟動時從 config server 拉。同樣的道理,只是這裡拉的是幾百 MB 的權重。
kube-rbac-proxy:ODH 幫你加的認證層
這個不是 KServe 給的,是 OpenShift AI 加的 sidecar, 用叢集的 RBAC 保護 metrics 端點。你沒要求,它自己來。
它不會幫你生的:對外入口
這是 3.x 最容易踩的一點。
oc get isvc llm-scratch -o jsonpath='{.status.address.url}'
# http://llm-scratch-predictor.llm-serve-demo.svc.cluster.local
.svc.cluster.local——那是叢集內部位址。 從叢集外面打不到。
在 2.x 的 Serverless 模式下,Knative 會幫你處理對外路由。 3.x 的 RawDeployment 不會。 你得自己開 Route:
oc create route edge llm-play \
--service=llm-scratch-predictor --port=8000 -n llm-serve-demo
我的叢集上這個 Route 是這樣來的——它沒有 ownerReference, 因為它不是 KServe 生的,是我自己建的:
oc get route llm-play -o jsonpath='to={.spec.to.name} host={.spec.host}'
# to=llm-scratch-predictor host=llm-play-llm-serve-demo.apps-crc.testing
⚠️ 「模型上線了」和「模型打得到」在 3.x 是兩件事。 驗收時要分開確認——
oc get isvc顯示READY=True只代表前者。
(另外 .status.components.predictor.url 會給你一個
...example.com 的假網址,那是 KServe 的預設 domain 沒設定,
不要拿它去打。)
最小可用流程
從零到能打,四步:
# 1. 模型放上 S3(我用 MinIO)
mc cp ckpt.pt tokenizer.json myminio/models/llm/
# 2. image 推上 registry(模型不在裡面)
podman push <registry>/tools/llm-serve:cpu
# 3. 建 InferenceService(就是上面那 41 行)
oc apply -f isvc-llm.yaml
# 4. 開對外入口 ← 這步不能省
oc create route edge llm-play --service=llm-scratch-predictor --port=8000
驗證:
oc get isvc llm-scratch # READY 要是 True
curl -sk https://llm-play-....apps-crc.testing/health
常見卡點速查
| 症狀 | 先看哪裡 |
|---|---|
Init:Error |
S3 通不通、STORAGE_URI 對不對、bucket 有沒有東西 |
READY=False 但 pod Running |
readinessProbe 的 path / port 對不對 |
| 打不到 | 有沒有開 Route;port 是不是你以為的那個 |
oc exec ... curl 說沒有 curl |
serving image 通常是 minimal,用 oc port-forward |
你們模型上線是用 KServe、自己寫 Deployment,還是廠商的方案?為什麼選那個?
🧪 這篇的實驗環境與 lab 檔案(最後更新 2026-08-30)
叢集
- CRC 2.63.0 · OpenShift 4.22.7 · Kubernetes v1.35.6
- 單節點:13 vCPU / 40 GiB RAM / 120 GB disk
- 宿主:Framework Laptop 16(Ryzen AI 7 350 · 8C/16T · 64 GB RAM · 1 TB NVMe · RTX 5070 顯卡模組)
- ⚠️ 叢集內看不到 GPU(CRC 是 VM,RTX 5070 未 passthrough)
Operator
opendatahub-operator.v3.5.0← 即 RHOAI 3.x 的上游開源版cert-manager-operator.v1.20.0(3.x 的必要相依;2.x 不需要)openshift-pipelines-operator-rh.v1.23.2、openshift-gitops-operator.v1.21.3
DataScienceCluster 開啟的元件
kserve、aipipelines、dashboard、workbenches、modelregistry、kueue(Unmanaged)- 其餘(
ray/trustyai/feast/aigateway…)為Removed
叢集外的依賴(跑在宿主的 podman 上,crc start 不會帶起來)
- Harbor v2.15.2(私有 registry)· MinIO(S3)
模型端
- Python 3.12.13 · PyTorch 2.11.0+cu128 · FastAPI + uvicorn · prometheus-client
lab 檔案
- YAML/Containerfile/腳本:github.com/ryanGTR/openshift-ai-30days
(含 Day 對照表;主機名是佔位符,跑
set-lab-host.sh換成你自己的) - 服務的那個模型:llm-from-scratch(從零手刻的小 GPT)
⚠️ ODH ≠ RHOAI:元件同源,但 namespace 與部分名稱不同
(我這裡是 opendatahub,商用版是 redhat-ods-* 那一套)。
指令的邏輯可以照用,字串要自己對一次。
留言與指正
我特別想知道:你們模型上線是用 KServe、自己寫 Deployment,還是廠商的方案?為什麼選那個?