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/shm in 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,操作符会创建一个 ServiceMonitor CR,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.0 to the server

  • 为 vLLM 发现创建一个节点本地的 ClusterIP 服务

  • 创建一个连接 ConfigMap (my-cache-connection),其中包含 vLLM 所需的 kv-transfer-config JSON。

  • 自动计算 L1 缓存大小的资源请求/限制

  • 默认将 nodeSelector 设置为 nvidia.com/gpu.present: \"true\"

备注

该操作符将容器镜像默认设置为 lmcache/vllm-openai:latest。可以通过 spec.image.repositoryspec.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/shm tmpfs (PyTorch's CUDA IPC handles reference a shared-memory ref-counter file there). Mount the host's /dev/shm via hostPath as above, or set spec.hostIPC: true on the engine and hostIPC: true on 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>-connection ConfigMap,并内联到 vLLM 容器的 args 中(无需卷挂载);

  • a hostPath mount of the host's /dev/shm (CUDA IPC with the node-local server; hostIPC: true instead when the engine sets spec.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/shm mount (and hostIPC, when the engine opts in) is rejected by the baseline / restricted PSS profiles, so the vLLM pod's namespace must be labeled pod-security.kubernetes.io/enforce=privileged.

  • 引擎在同一命名空间中协调 -- webhook 直接读取 <engine>-connection ConfigMap,因此 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>-connection ConfigMap 缺失(引擎尚未协调,或命名空间错误,或名称错误)。

  • 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_DIRdocker/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.yamlconfig/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 规格参考#

镜像#

字段

默认

描述

image.repository

lmcache/vllm-openai

容器镜像仓库。

image.tag

latest

容器镜像标签。

image.pullPolicy

IfNotPresent

Always, Never, 或 IfNotPresent

imagePullSecrets

--

镜像拉取密钥引用。

服务器#

字段

默认

描述

server.port

5555

ZMQ 监听端口 (1024--65535)。

server.chunkSize

256

Token 分块大小。

server.maxWorkers

1

ZMQ 请求的工作线程。

server.hashAlgorithm

blake3

builtin, sha256_cbor, 或 blake3

server.httpPort

8080

健康检查和缓存管理的 HTTP 前端端口(1024--65535)。

L1 缓存#

字段

默认

描述

l1.sizeGB

必需

L1 缓存大小(以 GB 为单位)。必须大于 0。

逐出#

字段

默认

描述

eviction.policy

LRU

LRUnoop。在仅缓冲模式下,使用 noopl2Backend.storePolicy: skip_l1

eviction.triggerWatermark

0.8

触发逐出的使用比例 (0.0--1.0]。

eviction.evictionRatio

0.2

逐出比例 (0.0--1.0]。

Prometheus#

字段

默认

描述

prometheus.enabled

true

对外暴露 Prometheus 指标。

prometheus.port

9090

/metrics 端点端口。

prometheus.serviceMonitor.enabled

false

创建一个 ServiceMonitor CR。

prometheus.serviceMonitor.interval

30s

抓取间隔。

prometheus.serviceMonitor.labels

--

ServiceMonitor 上的附加标签。

L2 存储#

字段

默认

描述

l2Backend

--

L2 后端列表 (type + config)。请参阅 二级 KV 存储

l2Backend.serde

--

Optional serde transform on KV bytes to/from the L2 adapter (rendered as the serde sub-dict of --l2-adapter). Exactly one serde type must be set. See KV Cache 压缩.

l2Backend.serde.aesgcm

--

At-rest encryption of L2 KV bytes (AES-GCM, keyed per cache_salt). L1 (host RAM) and L0 (GPU) remain plaintext.

l2Backend.serde.aesgcm.masterKeySecretRef.name

--

Required. User-created Secret in the engine's namespace holding the master key under the master data key; mounted read-only into the engine pods at /etc/lmcache/keys/master. The operator never generates key material.

l2Backend.serde.aesgcm.keyProvider

hkdf

How per-cache_salt keys are derived from the master key. Only hkdf (HKDF-SHA256) is implemented.

l2Backend.serde.aesgcm.aesBits

128

AES key size: 128 or 256.

GPU 和安全性#

字段

默认

描述

gpuVendor

nvidia

GPU 供应商:nvidia``(使用 ``nvidia RuntimeClass)或 ``amd``(在默认运行时上运行)。

hostIPC

false

Run the pod in the host IPC namespace instead of mounting the host's /dev/shm via hostPath. Cross-pod CUDA IPC needs only the shared /dev/shm tmpfs, so the default hostPath mount suffices on NVIDIA clusters; set true where hostPath volumes are blocked by an admission policy, or for gpuVendor: amd (the HIP IPC path is unverified without it). The injection webhook mirrors this setting on opted-in vLLM pods.

privileged

false

在特权模式下运行引擎容器。在大多数集群中,runtimeClassName: nvidia + NVIDIA_VISIBLE_DEVICES=all 已经可以在没有它的情况下授予 GPU 可见性;仅在引擎无法看到 GPU 的情况下设置为 true。对于 gpuVendor: amd 是必需的(没有 RuntimeClass 设备注入,因此特权是访问 /dev/kfd//dev/dri 的唯一途径)。启用它需要命名空间允许 privileged Pod 安全标准。

调度#

字段

默认

描述

nodeSelector

GPU 节点

默认为 nvidia.com/gpu.present: \"true\"

affinity

--

Pod 亲和性规则。

tolerations

--

Pod 容忍度规则。

priorityClassName

--

用于 Pods 的优先级类。

覆盖与额外选项#

字段

默认

描述

logLevel

INFO

DEBUG, INFO, WARNING, ERROR

resourceOverrides

--

覆盖自动计算的资源。

env

--

额外的环境变量。

volumes

--

额外的卷。

volumeMounts

--

额外的卷挂载。

initContainers

--

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 raw_block L2 adapter's device_path file (see Pre-Sizing an L2 Raw Block Device below).

podAnnotations

--

额外的 Pod 注解。

podLabels

--

额外的 Pod 标签。

serviceAccountName

--

Pod 使用的 ServiceAccount。

extraArgs

--

额外的 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/shm tmpfs (PyTorch's CUDA IPC handles reference a shared-memory ref-counter file there). Set spec.hostIPC: true to share the host IPC namespace instead (needed on clusters that block hostPath volumes, and for gpuVendor: 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-cacheLMCacheEngine

资源

名称

目的

DaemonSet

my-cache

运行 LMCache 服务器 Pod。

服务 (ClusterIP)

my-cache

节点本地发现(internalTrafficPolicy=Local)。

无头服务

my-cache-metrics

Prometheus 抓取目标。

ConfigMap

my-cache-connection

供 vLLM 使用的 kv-transfer-config JSON。

ServiceMonitor

my-cache

启用后由 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: PendingRunningDegradedFailed

  • readyInstances / desiredInstances: 实例计数。

  • endpoints: 每个节点的连接信息(节点名称、主机 IP、Pod 名称、端口、就绪状态)。

  • 条件

    • Available -- 至少有一个实例已就绪。

    • AllInstancesReady -- 所有预期实例均已就绪。

    • ConfigValid -- Spec 验证通过。

验证规则#

操作符在 apply 时验证 CR 的 Spec:

字段

规则

l1.sizeGB

必需,必须 > 0。

eviction.policy

必须是 LRU``noop``(如果设置)。

eviction.triggerWatermark

必须在 (0.0, 1.0] 之间。

eviction.evictionRatio

必须在 (0.0, 1.0] 之间。

server.port

必须在 [1024, 65535] 之间。

l2Backend.serde

Exactly one serde type must be set (only aesgcm today).

l2Backend.serde.aesgcm.masterKeySecretRef.name

Required, must be non-empty.

l2Backend.serde + l2Backend.raw

Rejected if the raw adapter config already sets a serde key (one would silently overwrite the other).

示例#

仅针对 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 as LMCacheEngine (runtimeClassName: nvidia + NVIDIA_VISIBLE_DEVICES=all + the host /dev/shm mount -- or hostIPC when spec.hostIPC is set -- plus privileged when spec.privileged is set, and no nvidia.com/gpu claim) so it shares the vLLM GPU for same-device CUDA IPC; and

  • vLLM 侧插件,由 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/shm mount (or hostIPC/privileged when the engine opts in), which the baseline/restricted profiles reject, so label the engine's and the vLLM pod's namespaces pod-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-configmy-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 参考),并新增了:

字段

默认

描述

blend.checkLayer

1

计算 Token 重要性评分所在的层(cb.check_layer)。

blend.recompRatio

0.15

非前缀命中 Token 被重新计算的比例(cb.recomp_ratio)。

blend.partialBucket

unset

Pads PARTIAL row counts to a multiple of this bucket (cb.partial_bucket). Needed on fp8-MoE models, where every distinct row count is a fresh M shape and the Triton autotuner re-tunes all MoE layers per CB step without it. Unset omits the key (padding disabled).

injection.payloadImage

必需

(私有)cacheblend-plugin 初始化容器镜像(repository / tag / pullPolicy)。必须设置 repository——继承自引擎镜像的默认值并非有效的负载镜像。

injection.imagePullSecrets

--

追加到 vLLM Pod 的私有负载镜像拉取密钥。

injection.targetContainer

第一个容器

要注入的 vLLM 容器的名称。

injection.cudagraph

eager

eager | piecewise | full_decode_only(不可使用 full)。

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:

  1. Builds a multi-key ConfigMap -- the <engine>-connection ConfigMap emits three keys so the same engine can serve all pod types:

    • kv-transfer-config.json -- bare LMCacheMPConnector (fallback for pods without a pd-role annotation; no NIXL).

    • kv-transfer-config-prefiller.json -- MultiConnector with kv_role=kv_producer.

    • kv-transfer-config-decoder.json -- MultiConnector with kv_role=kv_consumer.

  2. Injects the correct config via the webhook -- the webhook reads the lmcache.ai/pd-role annotation on each vLLM pod and injects the matching ConfigMap key as --kv-transfer-config.

  3. 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 from spec.pd.nixlSideChannelPort (default 5558). 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. prefiller 5557, decoder 5558).

PDSpec Fields#

字段

默认

描述

pd.nixlSideChannelPort

5558

Port the NIXL agent advertises for handshake negotiation. Injected by the webhook unless the pod pre-sets VLLM_NIXL_SIDE_CHANNEL_PORT. When using hostNetwork: true, set distinct values for prefiller and decoder directly in the pod env to avoid port conflicts.

pd.nixlLoadFailurePolicy

fail

fail -- abort the request if the NIXL transfer fails. ignore -- fall back to local prefill on failure.

pd.enforceHandshakeCompat

(omitted)

When set to false, disables strict NIXL version negotiation -- useful when prefiller and decoder run different vLLM builds. When omitted the key is not sent and NIXL uses its own default.

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 distinct VLLM_NIXL_SIDE_CHANNEL_PORT values 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 hostNetwork required.

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.refcoordinator.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 规格参考#

拓扑#

字段

默认

描述

replicas

1

协调器 Pod。注册表是每个进程的内存中,因此大于 1 仅在共享持久后端后面才有意义。必须大于或等于 0。

image.repository / image.tag / image.pullPolicy

共享引擎镜像

运行与引擎相同的 lmcache 二进制文件。

imagePullSecrets

--

镜像拉取密钥引用。

HTTP 服务器#

字段

默认

描述

host

0.0.0.0

协调器的 HTTP 服务器绑定的地址。

port

9300

HTTP 端口 (1--65535)。

成员资格与健康#

字段

默认

描述

instanceTimeout

30

在没有心跳的情况下,经过的秒数后,实例将被逐出。设置应高于引擎的 coordinator.heartbeatInterval

healthCheckInterval

10

健康检查扫描之间的秒数;0 禁用循环。

L2 配额逐出#

字段

默认

描述

evictionCheckInterval

5

L2 逐出清扫之间的秒数;0 禁用循环。

evictionRatio

0.2

每个周期逐出的跟踪键的比例(按数量计算),范围为 [0.0, 1.0]。

triggerWatermark

1.0

触发逐出的配额使用比例,(0.0, 1.0]。

全局 CacheBlend 目录#

字段

默认

描述

blendChunkSize

256

全局 CacheBlend 目录中每个块的令牌数(匹配单位)。必须等于 混合服务器使用的 LMCache 块大小。必须大于 0。

blendProbeStride

1

在匹配探针之间的位置。1 在每个偏移量处探测以获得完整的召回;提高它以在召回和协调器 CPU 之间进行权衡。必须大于 0。

Prometheus、调度与覆盖#

字段

默认

描述

prometheus.enabled

true

暴露指标容器端口。请参见下面的说明。

prometheus.port

9090

指标端口。

prometheus.serviceMonitor.enabled

false

创建一个 ServiceMonitor CR(和无头指标服务)。

prometheus.serviceMonitor.interval

30s

抓取间隔。

logLevel

INFO

DEBUG | INFO | WARNING | ERROR.

resourceOverrides

--

Pod 资源请求/限制(无自动计算;协调器轻量级 CPU/内存)。

nodeSelector / affinity / tolerations / priorityClassName

--

Pod 调度控制。

env / volumes / volumeMounts / podAnnotations / podLabels / serviceAccountName

--

标准的 Pod 形状字段。

extraArgs

--

额外的 CLI 标志(最后附加,可以覆盖任何自动生成的标志)。

备注

协调器进程尚未暴露/metrics端点。虽然存在Prometheus的连接,但只有在添加指标后才有用;serviceMonitor.enabled默认为false

创建协调器资源#

对于名为 my-coordinatorLMCacheCoordinator

资源

名称

目的

部署

my-coordinator

运行协调器 HTTP 服务器 Pod。

服务 (ClusterIP)

my-coordinator

在 HTTP 端口上进行全舰队发现。

无头服务

my-coordinator-metrics

serviceMonitor.enabled 时的 Prometheus 抓取目标。

ServiceMonitor

my-coordinator

Prometheus Operator 集成(当 serviceMonitor.enabled 时)。

其他组件用来访问协调器的状态 endpointhttp://<name>.<namespace>.svc:<port>(例如 http://my-coordinator.default.svc:9300)。

协调器状态与条件#

状态部分包括:

  • phase: PendingRunningDegradedFailed

  • 副本 / 就绪副本: 来自部署的 Pod 数量。

  • endpoint: 集群内用于访问协调器的 URL。

  • observedGeneration: 最近一次已调和的版本。

  • 条件

    • Available -- 至少一个副本已准备好。

    • 所有实例已准备好 -- 所有期望的实例均已准备好。

    • ConfigValid -- Spec 验证通过。

协调器验证规则#

字段

规则

port

必须在 [1, 65535] 之间。

replicas

必须 >= 0。

instanceTimeout

必须 > 0。

healthCheckInterval / evictionCheckInterval

必须 >= 0。

evictionRatio

必须在 [0.0, 1.0] 之间。

triggerWatermark

必须在 (0.0, 1.0] 之间。

blendChunkSize / blendProbeStride

必须 > 0。

Operator 与手动部署对比#

对比项

手动 DaemonSet

LMCacheEngine Operator

/dev/shm sharing (CUDA IPC)

必须手动设置

Auto-injected (hostPath mount; spec.hostIPC opt-in)

--host 0.0.0.0

必须手动设置

自动注入

服务发现

hostNetwork + status.hostIP

节点本地 ClusterIP 服务 + ConfigMap

vLLM 配置

将 JSON 复制到部署中

挂载 <name>-connection ConfigMap

资源大小调整

手动计算

l1.sizeGB 自动计算

Prometheus

手动 ServiceMonitor

serviceMonitor.enabled: true

验证

仅限运行时错误

kubectl apply 拒绝无效的规格

新的 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 privileged profile for the LMCache namespace -- the baseline and restricted profiles reject hostPath volumes and hostIPC alike.

  • 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