Kubernetes Operator#
LMCache Kubernetes 操作符自动化了 LMCache 多进程服务器的部署和生命周期管理。您只需声明一个 LMCacheEngine 自定义资源,操作符将协调所有底层的 Kubernetes 对象,而无需手动编写 DaemonSets、Services 和 ConfigMaps(如手册 部署指南 指南中所述)。
为什么使用 Operator#
手动 DaemonSet 方式可以正常工作,但存在一些操作符已消除的陷阱:
Auto-injected pod settings -- The operator always mounts the host's
/dev/shm(hostPath) and sets--host 0.0.0.0. Forgetting the shared/dev/shmin a hand-written manifest causes silent CUDA IPC failures (cudaErrorMapBufferObjectFailed) that are hard to debug.节点本地服务发现 -- 操作符创建一个 ClusterIP 服务,并设置
internalTrafficPolicy=Local,同时生成一个连接用 ConfigMap,vLLM Pod 直接挂载即可。无需hostNetwork,无需 Downward API,也无需 shell 变量替换。自动计算资源规格 -- 内存请求和限制由
l1.sizeGB推算得出,避免因资源不足导致 OOM 被杀或因资源过剩浪费节点容量。声明式 Prometheus 集成 -- 设置
prometheus.serviceMonitor.enabled: true,操作符会创建一个ServiceMonitorCR,Prometheus 操作符会自动发现它。CRD 验证 -- OpenAPI Schema 验证在
kubectl apply阶段、Pod 创建之前捕获配置错误(例如l1.sizeGB <= 0、端口范围无效等)。
前提条件#
Kubernetes 1.20+
kubectl配置为访问您的集群(可选) Prometheus Operator 以支持 ServiceMonitor
安装 Operator#
选项 A:从发布版一行安装(推荐)
# Latest stable release
kubectl apply -f https://github.com/LMCache/LMCache/releases/download/operator-latest/install.yaml
# Or nightly build from the dev branch
kubectl apply -f https://github.com/LMCache/LMCache/releases/download/operator-nightly-latest/install.yaml
选项 B:从源代码构建
cd operator
make build
make install
make deploy IMG=<your-registry>/lmcache-operator:latest
部署 LMCacheEngine#
一个最小的 CR 在每个 GPU 节点上部署一个具有 60 GB L1 缓存的 DaemonSet:
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: my-cache
spec:
l1:
sizeGB: 60
kubectl apply -f lmcache-engine.yaml
操作符自动:
在每个匹配的节点上创建一个运行 LMCache 服务器 Pod 的 DaemonSet
Mounts the host's
/dev/shm(hostPath) for CUDA IPC and passes--host 0.0.0.0to the server为 vLLM 发现创建一个节点本地的 ClusterIP 服务
创建一个连接 ConfigMap (
my-cache-connection),其中包含 vLLM 所需的kv-transfer-configJSON。自动计算 L1 缓存大小的资源请求/限制
默认将
nodeSelector设置为nvidia.com/gpu.present: \"true\"
备注
该操作符将容器镜像默认设置为 lmcache/vllm-openai:latest。可以通过 spec.image.repository 和 spec.image.tag 来覆盖,以固定特定版本。
连接 vLLM#
操作符创建一个名为 <engine-name>-connection 的 ConfigMap,其中包含 kv-transfer-config JSON。您可以让操作符的变更 webhook 为您注入它(推荐 -- 这样可以保持您的 vLLM 清单整洁),或者手动挂载它。有关 webhook 流程,请参见下面的 连接注入(Webhook);本节的其余部分描述了其等效的手动挂载。
在你的 vLLM 部署中挂载它:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm
spec:
replicas: 1
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
spec:
containers:
- name: vllm
image: lmcache/vllm-openai:latest
env:
# Deterministic hashing required by LMCache
- name: PYTHONHASHSEED
value: "0"
command: ["/bin/sh", "-c"]
args:
- |
exec python3 -m vllm.entrypoints.openai.api_server \
--model <your-model> \
--port 8000 \
--gpu-memory-utilization 0.8 \
--kv-transfer-config "$(cat /etc/lmcache/kv-transfer-config.json)"
ports:
- name: http
containerPort: 8000
volumeMounts:
- name: kv-transfer-config
mountPath: /etc/lmcache
readOnly: true
# Required for CUDA IPC between vLLM and LMCache: both pods
# must see the same /dev/shm tmpfs
- name: lmcache-dev-shm
mountPath: /dev/shm
resources:
limits:
nvidia.com/gpu: "1"
volumes:
- name: kv-transfer-config
configMap:
name: my-cache-connection # <engine-name>-connection
- name: lmcache-dev-shm
hostPath:
path: /dev/shm
type: Directory
vLLM Pod 的关键要求:
Shared /dev/shm -- CUDA IPC (
cudaIpcOpenMemHandle) needs vLLM and LMCache to see the same/dev/shmtmpfs (PyTorch's CUDA IPC handles reference a shared-memory ref-counter file there). Mount the host's/dev/shmvia hostPath as above, or setspec.hostIPC: trueon the engine andhostIPC: trueon the vLLM pod to share the host IPC namespace instead.PYTHONHASHSEED=0 -- 确保 Token 哈希的确定性,使 vLLM 与 LMCache 生成一致的缓存键。
ConfigMap 挂载 --
$(cat ...)模式以内联方式读取连接 JSON。ConfigMap 名称始终为<LMCacheEngine name>-connection。无需 hostNetwork -- 操作符的节点本地服务通过
internalTrafficPolicy=Local处理路由。
连接注入(Webhook)#
手动连接 ConfigMap 挂载和 $(cat ...) 参数替换在 vLLM 部署中是重复的。一个随操作员提供的 变更入场 webhook 可以为您完成此操作,从而保持 vLLM 清单的整洁。它镜像了 CacheBlend webhook(参见 CacheBlend),并使用 lmcache- 注释/标签区分符,以便两个注入器不会在同一 pod 上相互干扰。
当在一个已选择的 pod 上调用且存在 <engine>-connection ConfigMap 时,webhook 会在入场时修改 pod 以添加:
--kv-transfer-config <JSON>--LMCacheMPConnector配置,逐字读取自引擎的<engine>-connectionConfigMap,并内联到 vLLM 容器的args中(无需卷挂载);a hostPath mount of the host's
/dev/shm(CUDA IPC with the node-local server;hostIPC: trueinstead when the engine setsspec.hostIPC);PYTHONHASHSEED=0在 vLLM 容器环境中,仅在缺失时设置 -- 它保留您已经设置的值。
The connector config lives in the connection ConfigMap; the webhook also
reads the LMCacheEngine CR (when present) to mirror its spec.hostIPC
mode and its optional injection sub-spec. It fails open
(failurePolicy: Ignore) and is idempotent (re-admitted pods carrying the
lmcache.ai/lmcache-injected stamp are allowed unchanged).
前提条件#
cert-manager +
make deploy``(不是 ``make run,后者仅限控制器并通过ENABLE_WEBHOOKS=false禁用 webhook)-- 与 CacheBlend webhook 相同;每个集群安装一次(请参见 CacheBlend "附加先决条件")。Pod Security Standards -- the injected hostPath
/dev/shmmount (andhostIPC, when the engine opts in) is rejected by thebaseline/restrictedPSS profiles, so the vLLM pod's namespace must be labeledpod-security.kubernetes.io/enforce=privileged.引擎在同一命名空间中协调 -- webhook 直接读取
<engine>-connectionConfigMap,因此LMCacheEngine必须已经存在于 vLLM pod 的命名空间中。
为 vLLM Pod 启用注入#
将 opt-in 标签和 engine-binding 注释添加到 pod 模板中,并通过镜像 ENTRYPOINT 启动 vLLM(仅参数)-- 跳过 command: [\"/bin/sh\", \"-c\", ...] 包装器(webhook 会标记 lmcache.ai/lmcache-skip-reason=command-override,因为附加的参数不会到达 vllm serve):
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-lmcache
spec:
replicas: 1
selector:
matchLabels:
app: vllm-lmcache
template:
metadata:
labels:
app: vllm-lmcache
lmcache.ai/lmcache-inject: "true" # opt-in (webhook objectSelector)
annotations:
lmcache.ai/lmcache-engine: "my-cache" # bind to the engine (same namespace)
# Optional -- name the vLLM container if it is not the first one:
# lmcache.ai/lmcache-container: "vllm"
spec:
runtimeClassName: nvidia
# Do NOT mount an emptyDir at /dev/shm -- the webhook injects a
# hostPath mount of the host's /dev/shm; an emptyDir would shadow the
# host's /dev/shm and break cudaIpcOpenMemHandle.
containers:
- name: vllm
image: lmcache/vllm-openai:latest
# Args-only launch (image ENTRYPOINT is ["vllm", "serve"]). The
# webhook appends --kv-transfer-config; do NOT add it yourself
# (a user-supplied one stamps skip-reason=kv-transfer-config-present).
args: ["<your-model>", "--port", "8000", "--gpu-memory-utilization", "0.8"]
ports:
- name: http
containerPort: 8000
resources:
limits:
nvidia.com/gpu: "1"
可编辑的清单位于 operator/config/samples/vllm_lmcache_deployment.yaml。
验证注入#
该 webhook 修改的是 Pods,而不是 Deployment,因此请检查一个 pod(而不是 Deployment 规格):
kubectl get pod -l app=vllm-lmcache -o yaml | \
grep -E "lmcache-dev-shm|hostIPC|kv-transfer-config|lmcache-injected|lmcache-skip-reason"
如果没有任何内容被注入,请检查 Pod 的 lmcache.ai/lmcache-skip-reason 注解:
command-override-- pod 使用sh -c包装器,因此注入的参数无法到达vllm serve。kv-transfer-config-present-- 用户已经提供了--kv-transfer-config;该 webhook 不会覆盖它。engine-not-found--<engine>-connectionConfigMap 缺失(引擎尚未协调,或命名空间错误,或名称错误)。target-container-not-found--lmcache.ai/lmcache-container注解指定了一个 pod 中不存在的容器。
使用 failurePolicy: Ignore 时,webhook / 证书问题也会使 pod 静默未被修改 -- 确认操作员 pod 处于 Running 状态,并且 MutatingWebhookConfiguration 存在。
使用最新的(或固定的)LMCache#
默认情况下,vLLM pod 运行其镜像中内置的 lmcache。要运行 不同 的 lmcache 构建,例如将最新的 lmcache 部署到较旧的稳定 vLLM 镜像上,或保持 vLLM 客户端与其 LMCacheEngine 服务器运行的确切构建相同,请在引擎上设置 spec.injection.payloadImage。然后,网络钩子会将该镜像的 lmcache 树额外分阶段到每个选择加入的 pod 中:一个 emptyDir + 一个初始化容器将树复制进去,一个只读挂载,以及 PYTHONPATH=/lmcache-payload,以便 vLLM 导入分阶段的 lmcache 而不是内置的版本。无需重建 vLLM 镜像。
1. 构建负载镜像。 它在启动时将解压缩的 lmcache 树放在 /payload 下,并将其复制到 $SHARED_DIR。 docker/Dockerfile.payload 通过从 lmcache 镜像中提取与 ABI 匹配的 lmcache 来构建它(SOURCE_IMAGE 构建参数选择版本):
docker build -f docker/Dockerfile.payload \
--build-arg SOURCE_IMAGE=lmcache/vllm-openai:latest-nightly \
-t <registry>/lmcache-payload:latest .
docker push <registry>/lmcache-payload:latest
2. 指向引擎。 payloadImage.repository 没有有效的默认值(继承的镜像默认值不是有效负载),因此需要明确设置;保持 injection 未设置将仅保留连接线。
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: my-cache-versioned
spec:
l1:
sizeGB: 60
injection:
payloadImage:
repository: <registry>/lmcache-payload
tag: latest
pullPolicy: Always # :latest moves -- re-pull for the current build
# imagePullSecrets: # private payload registry only
# - name: my-registry-secret
绑定到此引擎的选择性 Pod(如上所述的标签 + 注释)无需更改——Webhook 会自动处理有效负载。可应用的示例:config/samples/lmcache_v1alpha1_lmcacheengine_injection.yaml 和 config/samples/vllm_lmcache_injection_deployment.yaml。
备注
有效负载的 lmcache 必须与导入它的 vLLM 镜像 **ABI 兼容**(相同的 Python 次版本和兼容的 torch)——它包含编译的扩展。如果它们不一致,import lmcache 在 vLLM pod 中会因 undefined symbol 错误而失败。从与您的 vLLM 镜像接近的 lmcache 镜像构建有效负载可以保持它们的兼容性。
3. 验证正在运行的 pod 上的交换 -- 将正常导入与忽略注入的 PYTHONPATH 的导入进行对比:
POD=$(kubectl get pod -l app=vllm-lmcache-versioned -o name | head -1)
# imports the STAGED build (from /lmcache-payload):
kubectl exec $POD -c vllm -- python3 -c \
"import lmcache; print(lmcache.__version__, lmcache.__file__)"
# PYTHONPATH stripped -> the image's baked-in build (site-packages):
kubectl exec $POD -c vllm -- env -u PYTHONPATH python3 -c \
"import lmcache; print(lmcache.__version__, lmcache.__file__)"
两个不同来源的相同模块确认了交换。如果没有任何内容被预先准备,请检查 lmcache.ai/lmcache-skip-reason 在 pod 上的内容。
验证部署#
# Check LMCacheEngine status
kubectl get lmc
预期输出:
NAME PHASE READY DESIRED AGE
my-cache Running 3 3 5m
# Check the connection ConfigMap
kubectl get configmap my-cache-connection -o yaml
# Check LMCache pods
kubectl get pods -l app.kubernetes.io/managed-by=lmcache-operator
# Check detailed status with endpoints
kubectl describe lmc my-cache
CRD 规格参考#
镜像#
字段 |
默认 |
描述 |
|---|---|---|
|
|
容器镜像仓库。 |
|
|
容器镜像标签。 |
|
|
|
|
-- |
镜像拉取密钥引用。 |
服务器#
字段 |
默认 |
描述 |
|---|---|---|
|
|
ZMQ 监听端口 (1024--65535)。 |
|
|
Token 分块大小。 |
|
|
ZMQ 请求的工作线程。 |
|
|
|
|
|
健康检查和缓存管理的 HTTP 前端端口(1024--65535)。 |
L1 缓存#
字段 |
默认 |
描述 |
|---|---|---|
|
必需 |
L1 缓存大小(以 GB 为单位)。必须大于 0。 |
逐出#
字段 |
默认 |
描述 |
|---|---|---|
|
|
|
|
|
触发逐出的使用比例 (0.0--1.0]。 |
|
|
逐出比例 (0.0--1.0]。 |
Prometheus#
字段 |
默认 |
描述 |
|---|---|---|
|
|
对外暴露 Prometheus 指标。 |
|
|
|
|
|
创建一个 ServiceMonitor CR。 |
|
|
抓取间隔。 |
|
-- |
ServiceMonitor 上的附加标签。 |
L2 存储#
字段 |
默认 |
描述 |
|---|---|---|
|
-- |
L2 后端列表 ( |
|
-- |
Optional serde transform on KV bytes to/from the L2 adapter
(rendered as the |
|
-- |
At-rest encryption of L2 KV bytes (AES-GCM, keyed per
|
|
-- |
Required. User-created Secret in the engine's namespace holding
the master key under the |
|
|
How per- |
|
|
AES key size: |
GPU 和安全性#
字段 |
默认 |
描述 |
|---|---|---|
|
|
GPU 供应商: |
|
|
Run the pod in the host IPC namespace instead of mounting the host's
|
|
|
在特权模式下运行引擎容器。在大多数集群中, |
调度#
字段 |
默认 |
描述 |
|---|---|---|
|
GPU 节点 |
默认为 |
|
-- |
Pod 亲和性规则。 |
|
-- |
Pod 容忍度规则。 |
|
-- |
用于 Pods 的优先级类。 |
覆盖与额外选项#
字段 |
默认 |
描述 |
|---|---|---|
|
|
|
|
-- |
覆盖自动计算的资源。 |
|
-- |
额外的环境变量。 |
|
-- |
额外的卷。 |
|
-- |
额外的卷挂载。 |
|
-- |
Additional init containers run before the lmcache container starts,
in order. Use to prepare state the engine can't create itself, e.g.
pre-sizing a |
|
-- |
额外的 Pod 注解。 |
|
-- |
额外的 Pod 标签。 |
|
-- |
Pod 使用的 ServiceAccount。 |
|
-- |
额外的 CLI 标志(最后附加,可以覆盖)。 |
自动计算资源#
当 spec.resourceOverrides 未设置时,操作符从 l1.sizeGB 派生资源:
CPU 请求:
4核心内存请求:
ceil(l1.sizeGB + 5)Gi内存限制:
ceil(memoryRequest * 1.5)Gi
例如,l1.sizeGB: 60 会产生 65 Gi 的请求和 98 Gi 的限制。
自动注入的 Pod 设置#
The operator always injects these into the pod spec:
Host /dev/shm mount (hostPath) -- Required for CUDA IPC between LMCache and vLLM: both processes must see the same
/dev/shmtmpfs (PyTorch's CUDA IPC handles reference a shared-memory ref-counter file there). Setspec.hostIPC: trueto share the host IPC namespace instead (needed on clusters that block hostPath volumes, and forgpuVendor: amd); the mount is then omitted.--host 0.0.0.0 -- 将服务器绑定到所有接口,以便节点本地服务可以路由到它。
NVIDIA_VISIBLE_DEVICES=all -- 确保 GPU 可用于基于 IPC 的内存传输。
NVIDIA_DRIVER_CAPABILITIES=all -- 将所有驱动程序功能(计算、实用程序等)暴露给容器。
TCP socket 探测 -- 启动(初始 5 秒,30 次失败)、存活(10 秒)和就绪(5 秒)探测在服务器端口上。
备注
The operator never mounts an emptyDir at /dev/shm. Mounting an emptyDir
there would shadow the host's /dev/shm with a private tmpfs and break
CUDA IPC.
创建的资源#
对于名为 my-cache 的 LMCacheEngine:
资源 |
名称 |
目的 |
|---|---|---|
DaemonSet |
|
运行 LMCache 服务器 Pod。 |
服务 (ClusterIP) |
|
节点本地发现( |
无头服务 |
|
Prometheus 抓取目标。 |
ConfigMap |
|
供 vLLM 使用的 |
ServiceMonitor |
|
启用后由 Prometheus Operator 集成使用。 |
连接 ConfigMap 包含:
{
"kv_connector": "LMCacheMPConnector",
"kv_role": "kv_both",
"kv_connector_extra_config": {
"lmcache.mp.host": "tcp://my-cache.default.svc.cluster.local",
"lmcache.mp.port": "5555"
}
}
状态与条件#
kubectl describe lmc my-cache
状态部分包括:
phase:
Pending、Running、Degraded或Failed。readyInstances / desiredInstances: 实例计数。
endpoints: 每个节点的连接信息(节点名称、主机 IP、Pod 名称、端口、就绪状态)。
条件:
Available-- 至少有一个实例已就绪。AllInstancesReady-- 所有预期实例均已就绪。ConfigValid-- Spec 验证通过。
验证规则#
操作符在 apply 时验证 CR 的 Spec:
字段 |
规则 |
|---|---|
|
必需,必须 > 0。 |
|
必须是 |
|
必须在 (0.0, 1.0] 之间。 |
|
必须在 (0.0, 1.0] 之间。 |
|
必须在 [1024, 65535] 之间。 |
|
Exactly one serde type must be set (only |
|
Required, must be non-empty. |
|
Rejected if the raw adapter config already sets a |
示例#
仅针对 GPU 节点#
使用 nodeSelector 将 LMCache 限定在 GPU 节点上运行。新加入的 GPU 节点会自动获得一个 LMCache Pod:
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: my-cache
spec:
nodeSelector:
nvidia.com/gpu.present: "true"
l1:
sizeGB: 60
备注
当未指定时,操作符默认将 nodeSelector 设置为 nvidia.com/gpu.present: \"true\",因此最小化的 CR 默认已针对 GPU 节点。
自定义服务器端口#
如果默认端口(5555)与其他服务冲突:
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: my-cache
spec:
server:
port: 6555
l1:
sizeGB: 60
连接 ConfigMap 会自动更新 —— vLLM Pod 重启后即可使用新端口。
Encrypted L2 Backend#
Encrypt KV bytes at rest in the L2 tier with the aesgcm serde. Create
the master-key Secret first, in the engine's namespace (the operator never
generates keys):
head -c 16 /dev/urandom > master.key # 16 bytes for AES-128, 32 for AES-256
kubectl create secret generic lmcache-l2-master-key \
--from-file=master=master.key
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: my-cache
spec:
l1:
sizeGB: 60
l2Backend:
raw:
type: fs
config:
base_path: /data/lmcache/l2
serde:
aesgcm:
masterKeySecretRef:
name: lmcache-l2-master-key
The Secret is mounted directly into the engine pods (read-only, only the
master data key). A missing Secret or missing key surfaces as a pod
mount event and self-heals once the Secret is fixed. See KV Cache 压缩 for
the key model and threat model.
集成 Prometheus 监控的生产环境配置#
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: production-cache
namespace: llm-serving
spec:
nodeSelector:
nvidia.com/gpu.present: "true"
image:
repository: lmcache/standalone
tag: v0.1.0
server:
port: 6555
chunkSize: 256
maxWorkers: 4
l1:
sizeGB: 60
eviction:
triggerWatermark: 0.8
evictionRatio: 0.2
prometheus:
enabled: true
port: 9090
serviceMonitor:
enabled: true
labels:
release: kube-prometheus-stack
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
priorityClassName: system-node-critical
请参阅 可观察性 以获取指标名称和 Grafana 配置。
覆盖自动计算的资源#
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: my-cache
spec:
l1:
sizeGB: 60
resourceOverrides:
requests:
memory: "70Gi"
cpu: "8"
limits:
memory: "100Gi"
Pre-Sizing an L2 Raw Block Device#
The raw_block L2 adapter opens its device_path file expecting it to
already exist at the configured size -- it does not create or grow the file
itself. On a fresh node, that file is missing and the engine crashes with a
FileNotFoundError. Use initContainers to fallocate (or truncate) it
before the lmcache container starts:
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: my-cache
spec:
l1:
sizeGB: 60
volumes:
- name: kv-cache-root
hostPath:
path: /path/to/local/disk # e.g. a mounted NVMe scratch disk
type: DirectoryOrCreate
volumeMounts:
- name: kv-cache-root
mountPath: /mnt/kv-cache-root
initContainers:
- name: preallocate-raw-block
image: busybox
command:
- sh
- -c
- |
test -f /mnt/kv-cache-root/lmcache-l2.raw || \
fallocate -l 10000000000 /mnt/kv-cache-root/lmcache-l2.raw || \
truncate -s 10000000000 /mnt/kv-cache-root/lmcache-l2.raw
volumeMounts:
- name: kv-cache-root
mountPath: /mnt/kv-cache-root
l2Backend:
raw:
type: raw_block
config:
device_path: "/mnt/kv-cache-root/lmcache-l2.raw"
capacity_bytes: 10000000000 # 10 GB, must match the size above
备注
capacity_bytes in l2Backend.raw.config must match the size the
init container allocates. The test -f ... || guard makes the
fallocate idempotent -- a pod restart never truncates a file that
already has data, so a populated L2 cache survives engine restarts.
Init containers listed here run in order, before the lmcache container,
and share the engine's volumes/volumeMounts -- mount the same
volume in both if the init container needs to touch it.
CacheBlend#
CacheBlend 通过重新计算一小部分 Token 来复用偏移(非前缀)位置的缓存 KV。该操作符将其作为第二个 CRD CacheBlendEngine 进行管理,并通过一个 变更准入 Webhook 将纯 Python 的 lmcache-cacheblend vLLM 插件注入到您的服务 Pod 中,因此您 无需 重新构建 vLLM 镜像。有关该技术的详细信息,请参见 混合。
它由两个部分组成,操作符同时管理它们:
a GPU-resident CacheBlend engine (
lmcache server --engine-type blend), deployed as a DaemonSet with the same GPU model asLMCacheEngine(runtimeClassName: nvidia+NVIDIA_VISIBLE_DEVICES=all+ the host/dev/shmmount -- orhostIPCwhenspec.hostIPCis set -- plusprivilegedwhenspec.privilegedis set, and nonvidia.com/gpuclaim) so it shares the vLLM GPU for same-device CUDA IPC; andvLLM 侧插件,由 Webhook 注入到已选择启用的 Pod 中。
附加先决条件#
除上述操作符前提条件外,还需满足:
cert-manager -- webhook 的服务证书由 cert-manager
Issuer+Certificate颁发。在make deploy之前安装它:kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml kubectl -n cert-manager wait --for=condition=Available deploy --all --timeout=180s使用 Webhook 部署 -- 使用
make deploy(而不是make run,后者仅为控制器并通过ENABLE_WEBHOOKS=false禁用 Webhook)。Pod Security Standards -- the webhook injects a hostPath
/dev/shmmount (orhostIPC/privilegedwhen the engine opts in), which thebaseline/restrictedprofiles reject, so label the engine's and the vLLM pod's namespacespod-security.kubernetes.io/enforce=privileged.
部署 CacheBlendEngine#
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: CacheBlendEngine
metadata:
name: my-cacheblend
spec:
l1:
sizeGB: 60
injection:
# The (private) cacheblend-plugin init-container image -- repository/tag/
# pullPolicy, like spec.image. Set repository to YOUR image; the
# inherited engine-image default is not a valid payload.
payloadImage:
repository: <registry>/cacheblend-plugin
tag: <tag>
# Appended to the vLLM pod so the private payload image can pull; the
# Secret must exist in the vLLM pod's namespace.
imagePullSecrets:
- name: my-registry-secret
该引擎以 DaemonSet 方式运行 lmcache server --engine-type blend,并生成一个包含 CBKVConnector kv-transfer-config 的 my-cacheblend-connection ConfigMap(操作符将节点本地服务的主机/端口和 cb.* 可调参数连接起来)。
为 vLLM Pod 启用注入#
为 Pod 模板打上 webhook 所需的标签,并按名称将其绑定到引擎。通过镜像 ENTRYPOINT 启动 vLLM(仅传参数)——应跳过 command: [\"/bin/sh\", \"-c\", ...] 包装器,否则附加的参数无法传递给 vllm serve:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-cacheblend
spec:
replicas: 1
selector:
matchLabels:
app: vllm-cacheblend
template:
metadata:
labels:
app: vllm-cacheblend
lmcache.ai/cacheblend-inject: "true" # opt-in (webhook objectSelector)
annotations:
lmcache.ai/cacheblend-engine: "my-cacheblend" # bind to the engine
spec:
runtimeClassName: nvidia
containers:
- name: vllm
image: lmcache/vllm-openai:<pinned-tag>
args: ["<your-model>", "--port", "8000", "--gpu-memory-utilization", "0.8"]
resources:
limits:
nvidia.com/gpu: "1"
The webhook injects the plugin init container, PYTHONPATH, the /dev/shm
mount, the
private-image pull secret, and the required CacheBlend vLLM flags
(--kv-transfer-config from the engine's connection ConfigMap,
--pipeline-parallel-size 1, --no-enable-chunked-prefill,
--enforce-eager). You supply only the model and your non-CacheBlend flags.
验证注入#
该 Webhook 变更的是 Pod,而非 Deployment,因此请检查 Pod:
kubectl get pod -l app=vllm-cacheblend -o yaml | \
grep -E "initContainers|cb-plugin|PYTHONPATH|kv-transfer-config|cacheblend-injected|skip-reason"
若未注入任何内容,请检查 Pod 的 lmcache.ai/cacheblend-skip-reason 注解:command-override(使用了 sh -c 包装器)、kv-transfer-config-present(已自行设置了该配置)、engine-not-found(缺少 <name>-connection ConfigMap)、payload-image-unset(引擎的 injection.payloadImage 未设置仓库),或 target-container-not-found(所请求的 targetContainer / cacheblend-container 注解指向了 Pod 中不存在的容器)。在 failurePolicy: Ignore 的情况下,Webhook 或证书问题也会使 Pod 静默地保持未变更 —— 请确认操作符 Pod 处于 Running 状态,且 MutatingWebhookConfiguration 已存在。
CacheBlendEngine 字段#
CacheBlendEngineSpec 包含 LMCacheEngineSpec 的全部字段(见上方 CRD Spec 参考),并新增了:
字段 |
默认 |
描述 |
|---|---|---|
|
|
计算 Token 重要性评分所在的层( |
|
|
非前缀命中 Token 被重新计算的比例( |
|
unset |
Pads PARTIAL row counts to a multiple of this bucket
( |
|
必需 |
(私有)cacheblend-plugin 初始化容器镜像( |
|
-- |
追加到 vLLM Pod 的私有负载镜像拉取密钥。 |
|
第一个容器 |
要注入的 vLLM 容器的名称。 |
|
|
|
server.chunkSize defaults to 256 and must equal 256 (the blend matcher
requires it).
PD Disaggregation#
The operator has first-class support for PD (Prefill-Decode) disaggregation.
Adding a pd block to an LMCacheEngine spec switches the engine's
connection ConfigMap to include MultiConnector configs (NixlConnector +
LMCacheMPConnector) alongside the standard bare connector, and tells the
webhook to inject the NIXL side-channel environment variables into opted-in
vLLM pods automatically.
See 分离式预填充 for background on what PD disaggregation is and how the pieces fit together.
备注
PD disaggregation requires NIXL in the vLLM environment (lmcache[nixl]
extra) and vllm-project/vllm#46865 merged.
How it works#
Deploy a single LMCacheEngine CR with a pd block. One DaemonSet
instance per node serves both prefiller and decoder vLLM pods. The operator:
Builds a multi-key ConfigMap -- the
<engine>-connectionConfigMap emits three keys so the same engine can serve all pod types:kv-transfer-config.json-- bareLMCacheMPConnector(fallback for pods without apd-roleannotation; no NIXL).kv-transfer-config-prefiller.json--MultiConnectorwithkv_role=kv_producer.kv-transfer-config-decoder.json--MultiConnectorwithkv_role=kv_consumer.
Injects the correct config via the webhook -- the webhook reads the
lmcache.ai/pd-roleannotation on each vLLM pod and injects the matching ConfigMap key as--kv-transfer-config.Injects NIXL env vars -- opted-in PD pods receive two extra env vars:
VLLM_NIXL_SIDE_CHANNEL_HOST-- set to the pod's own IP via the downward API (status.podIP).VLLM_NIXL_SIDE_CHANNEL_PORT-- taken fromspec.pd.nixlSideChannelPort(default5558). If the pod pre-sets this env var, the webhook will leave it unchanged -- useful when both roles run on the same host and need distinct ports (e.g. prefiller5557, decoder5558).
PDSpec Fields#
字段 |
默认 |
描述 |
|---|---|---|
|
|
Port the NIXL agent advertises for handshake negotiation. Injected by
the webhook unless the pod pre-sets |
|
|
|
|
(omitted) |
When set to |
Deploying a PD Engine#
A single LMCacheEngine handles both roles:
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: lmcache-engine
spec:
l1:
sizeGB: 100
server:
port: 5555
chunkSize: 256
pd:
nixlLoadFailurePolicy: fail
kubectl apply -f engine.yaml
kubectl get lmc # wait for Running
Opting vLLM Pods In#
Use the standard label + annotation pattern (see
连接注入(Webhook)), adding the lmcache.ai/pd-role
annotation to select the prefiller or decoder config:
# prefiller vLLM pod template
metadata:
labels:
lmcache.ai/lmcache-inject: "true"
annotations:
lmcache.ai/lmcache-engine: "lmcache-engine"
lmcache.ai/pd-role: "prefiller"
# decoder vLLM pod template
metadata:
labels:
lmcache.ai/lmcache-inject: "true"
annotations:
lmcache.ai/lmcache-engine: "lmcache-engine"
lmcache.ai/pd-role: "decoder"
The webhook injects --kv-transfer-config (the role-specific MultiConnector
JSON), hostIPC: true, PYTHONHASHSEED=0, VLLM_NIXL_SIDE_CHANNEL_HOST,
and VLLM_NIXL_SIDE_CHANNEL_PORT into each opted-in pod. Do not mount
the ConfigMap or add --kv-transfer-config yourself.
备注
NIXL RDMA and hostNetwork -- NIXL's UCX backend requires valid RDMA GIDs, which are derived from the host's network interfaces. Under standard overlay CNI (each pod has its own network namespace) the GID table inside the pod is empty and UCX backend initialization fails. The recommended workarounds are:
hostNetwork: true+dnsPolicy: ClusterFirstWithHostNet(quick test). With hostNetwork both roles share the host IP, so set distinctVLLM_NIXL_SIDE_CHANNEL_PORTvalues per role (e.g. 5557 / 5558) in the pod env before the webhook runs -- the webhook will not override a pre-set value.SR-IOV with Multus (production): assign each pod a dedicated VF with its own GID, no
hostNetworkrequired.
Pods without a lmcache.ai/pd-role annotation that are bound to a PD engine
fall back to the bare LMCacheMPConnector config (no NIXL) -- they still
benefit from the LMCache KV cache without participating in disaggregation.
Router#
The vllm-router is not managed by the operator. Deploy it as a plain
Kubernetes Deployment pointing at the prefiller and decoder vLLM Services:
vllm-router \
--policy round_robin \
--vllm-pd-disaggregation \
--prefill http://pd-prefiller.<namespace>.svc.cluster.local:8001 \
--decode http://pd-decoder.<namespace>.svc.cluster.local:8002 \
--host 0.0.0.0 --port 30000
A ready-to-edit manifest is at
operator/config/samples/vllm_pd_disaggregation.yaml.
备注
Name your prefiller and decoder vLLM Services with a prefix other than
vllm- (e.g., pd-prefiller, pd-decoder). Kubernetes injects
<SERVICE_NAME>_* env vars into every pod in the namespace; a vllm-
prefix generates VLLM_* vars that vLLM's env-var validator flags as
unknown.
LMCacheCoordinator#
LMCacheCoordinator CRD 运行 mp coordinator -- 一个跨集群的 HTTP 服务,跟踪 mp 服务器实例,逐出心跳超时的实例,执行 L2 配额逐出,并托管全局 CacheBlend 指纹目录。它是一个普通的 (非 GPU) Deployment,通过 ClusterIP 服务暴露;引擎通过 coordinator.ref 或 coordinator.url 访问它。
部署协调器#
一个可以编辑的清单位于操作员仓库中的 config/samples/lmcache_v1alpha1_lmcachecoordinator.yaml。一个最小的协调器:
apiVersion: lmcache.lmcache.ai/v1alpha1
kind: LMCacheCoordinator
metadata:
name: my-coordinator
spec:
port: 9300
kubectl get lmcc my-coordinator # shortName: lmcc
连接引擎#
通过其 coordinator 块将 LMCacheEngine / CacheBlendEngine 指向协调器。使用 ref 在同一命名空间中命名协调器(操作符将其解析为集群内服务 URL),或使用 url 指定明确的端点:
spec:
coordinator:
ref:
name: my-coordinator # or: url: http://my-coordinator.default.svc:9300
heartbeatInterval: 5 # seconds; must be > 0
l2EventReporting: false # report L2 store/lookup events for fleet eviction
协调器 CRD 规格参考#
拓扑#
字段 |
默认 |
描述 |
|---|---|---|
|
|
协调器 Pod。注册表是每个进程的内存中,因此大于 1 仅在共享持久后端后面才有意义。必须大于或等于 0。 |
|
共享引擎镜像 |
运行与引擎相同的 lmcache 二进制文件。 |
|
-- |
镜像拉取密钥引用。 |
HTTP 服务器#
字段 |
默认 |
描述 |
|---|---|---|
|
|
协调器的 HTTP 服务器绑定的地址。 |
|
|
HTTP 端口 (1--65535)。 |
成员资格与健康#
字段 |
默认 |
描述 |
|---|---|---|
|
|
在没有心跳的情况下,经过的秒数后,实例将被逐出。设置应高于引擎的 |
|
|
健康检查扫描之间的秒数; |
L2 配额逐出#
字段 |
默认 |
描述 |
|---|---|---|
|
|
L2 逐出清扫之间的秒数; |
|
|
每个周期逐出的跟踪键的比例(按数量计算),范围为 [0.0, 1.0]。 |
|
|
触发逐出的配额使用比例,(0.0, 1.0]。 |
全局 CacheBlend 目录#
字段 |
默认 |
描述 |
|---|---|---|
|
|
全局 CacheBlend 目录中每个块的令牌数(匹配单位)。必须等于 混合服务器使用的 LMCache 块大小。必须大于 0。 |
|
|
在匹配探针之间的位置。 |
Prometheus、调度与覆盖#
字段 |
默认 |
描述 |
|---|---|---|
|
|
暴露指标容器端口。请参见下面的说明。 |
|
|
指标端口。 |
|
|
创建一个 ServiceMonitor CR(和无头指标服务)。 |
|
|
抓取间隔。 |
|
|
|
|
-- |
Pod 资源请求/限制(无自动计算;协调器轻量级 CPU/内存)。 |
|
-- |
Pod 调度控制。 |
|
-- |
标准的 Pod 形状字段。 |
|
-- |
额外的 CLI 标志(最后附加,可以覆盖任何自动生成的标志)。 |
备注
协调器进程尚未暴露/metrics端点。虽然存在Prometheus的连接,但只有在添加指标后才有用;serviceMonitor.enabled默认为false。
创建协调器资源#
对于名为 my-coordinator 的 LMCacheCoordinator:
资源 |
名称 |
目的 |
|---|---|---|
部署 |
|
运行协调器 HTTP 服务器 Pod。 |
服务 (ClusterIP) |
|
在 HTTP 端口上进行全舰队发现。 |
无头服务 |
|
当 |
ServiceMonitor |
|
Prometheus Operator 集成(当 |
其他组件用来访问协调器的状态 endpoint 是 http://<name>.<namespace>.svc:<port>(例如 http://my-coordinator.default.svc:9300)。
协调器状态与条件#
状态部分包括:
phase:
Pending、Running、Degraded或Failed。副本 / 就绪副本: 来自部署的 Pod 数量。
endpoint: 集群内用于访问协调器的 URL。
observedGeneration: 最近一次已调和的版本。
条件:
Available-- 至少一个副本已准备好。所有实例已准备好-- 所有期望的实例均已准备好。ConfigValid-- Spec 验证通过。
协调器验证规则#
字段 |
规则 |
|---|---|
|
必须在 [1, 65535] 之间。 |
|
必须 >= 0。 |
|
必须 > 0。 |
|
必须 >= 0。 |
|
必须在 [0.0, 1.0] 之间。 |
|
必须在 (0.0, 1.0] 之间。 |
|
必须 > 0。 |
Operator 与手动部署对比#
对比项 |
手动 DaemonSet |
LMCacheEngine Operator |
|---|---|---|
|
必须手动设置 |
Auto-injected (hostPath mount; |
|
必须手动设置 |
自动注入 |
服务发现 |
|
节点本地 ClusterIP 服务 + ConfigMap |
vLLM 配置 |
将 JSON 复制到部署中 |
挂载 |
资源大小调整 |
手动计算 |
从 |
Prometheus |
手动 ServiceMonitor |
|
验证 |
仅限运行时错误 |
|
新的 GPU 节点 |
DaemonSet 处理它 |
DaemonSet 处理它(相同) |
安全注意事项#
The hostPath /dev/shm mount exposes the host's shared-memory tmpfs to the
container -- a narrower grant than the whole host IPC namespace, but still
host-level access. spec.hostIPC: true (opt-in, default false) instead
exposes the host's IPC namespace (System V IPC, POSIX message queues): any
process in the container can interact with IPC resources from other processes
on the same host.
仅在受信任的环境中部署。
Clusters using Pod Security Standards must allow the
privilegedprofile for the LMCache namespace -- thebaselineandrestrictedprofiles reject hostPath volumes andhostIPCalike.spec.privileged默认为false。启用后(对于gpuVendor: amd是必需的),引擎容器将以特权模式运行,授予其完全的设备访问权限——仅在 GPU 可见性需要时启用。
开发#
make generate # Generate DeepCopy methods
make manifests # Generate CRD YAML + RBAC
make build # Compile operator binary
make fmt # go fmt
make vet # go vet
make test # Run unit tests
make lint # Run golangci-lint
推送自定义 Operator 镜像:
# Docker Hub
make docker-build docker-push IMG=docker.io/<your-user>/lmcache-operator:latest
make deploy IMG=docker.io/<your-user>/lmcache-operator:latest
# Multi-platform (amd64 + arm64)
make docker-buildx IMG=<your-registry>/lmcache-operator:latest
如果您的集群需要拉取凭据:
kubectl create secret docker-registry regcred \
--docker-server=<your-registry> \
--docker-username=<username> \
--docker-password=<password> \
-n lmcache-operator-system