31 KiB
LMCache Kubernetes Operator Design
Overview
LMCache multiprocess mode runs as a separate server process that vLLM instances connect to for KV cache offloading. Today this is deployed manually via raw K8s manifests (a DaemonSet for LMCache + a Deployment for vLLM). A K8s operator would automate lifecycle management, enforce best practices (hostIPC, resource sizing), and expose connection info for vLLM discovery.
Current Pain Points the Operator Solves
- Manual
hostIPCsetup and node-local service discovery - No automated connection info propagation to vLLM
- No Prometheus ServiceMonitor integration
- No validation of configuration parameters
How the Operator Addresses These
The operator introduces a single CRD (LMCacheEngine) that declaratively captures the full LMCache server configuration. The controller reconciles this CR into the underlying K8s resources (DaemonSet, Service, ConfigMap, ServiceMonitor), automatically injecting the required pod-level settings that are easy to forget in hand-written manifests.
Auto-injected pod settings eliminate manual boilerplate. The controller always sets hostIPC: true and --host 0.0.0.0 — settings that the current lmcache-daemonset.yaml requires users to specify by hand. Getting any of these wrong (e.g., forgetting hostIPC) causes silent connectivity failures or CUDA IPC errors that are hard to debug.
A node-local Service with connection ConfigMap provides a stable discovery contract for vLLM. The operator creates a ClusterIP Service with internalTrafficPolicy=Local, which ensures kube-proxy routes traffic only to the LMCache pod on the same node. A ConfigMap (<name>-connection) contains the kv-transfer-config JSON pointing to this service's cluster DNS name. vLLM deployments simply mount the ConfigMap — no downward API or shell substitution needed. When the LMCache CR changes, the ConfigMap updates automatically and vLLM pods pick up the new values on restart.
Prometheus integration is declarative. When prometheus.serviceMonitor.enabled is set, the operator creates a ServiceMonitor CR that the Prometheus Operator discovers automatically. Without the operator, users must manually create ServiceMonitor resources and keep labels/ports in sync with the DaemonSet.
CRD validation catches misconfigurations at apply time. OpenAPI schema validation and a validating webhook enforce constraints (e.g., l1.sizeGB > 0, eviction.triggerWatermark in (0.0, 1.0]) before any pods are created. Today, invalid CLI flags only surface as runtime crashes inside the container.
Resource sizing is auto-computed from the L1 cache size. The operator derives memoryRequest (l1.sizeGB + 5 GiB) and memoryLimit (1.5x request) automatically, eliminating the mental math that currently leads to either OOM kills (under-provisioned) or wasted node capacity (over-provisioned). Users can override with explicit values.
API Group & CRD
apiVersion: lmcache.ai/v1alpha1
kind: LMCacheEngine
- Group
lmcache.ai - v1alpha1 — alpha maturity; shape will evolve as L2 backends stabilize
- LMCacheEngine — represents the per-node cache engine. Future CRDs can
cover other concerns (e.g.,
LMCacheKeyManagerfor global key management,LMCacheMonitorfor engine state monitoring)
CRD Spec (Complete)
apiVersion: lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: string
namespace: string
spec:
# -- Container image --
image:
repository: string # default: "lmcache/standalone"
tag: string # default: "nightly"
pullPolicy: string # default: IfNotPresent
imagePullSecrets: []LocalObjectReference
# -- Server config (maps to server.py argparse) --
server:
port: int # default: 5555
chunkSize: int # default: 256 tokens
maxWorkers: int # default: 1
hashAlgorithm: string # default: blake3 (builtin | sha256_cbor | blake3)
# -- L1 cache (maps to L1MemoryManagerConfig + L1ManagerConfig) --
# Internal tuning knobs (useLazy, alignBytes, writeTTLSeconds,
# readTTLSeconds) use server defaults and can be overridden via the
# env escape hatch if needed.
l1:
sizeGB: float # REQUIRED
# -- Eviction (maps to EvictionConfig) --
eviction:
policy: string # default: "LRU" (only supported value)
triggerWatermark: float # default: 0.8 (range 0.0-1.0)
evictionRatio: float # default: 0.2 (range 0.0-1.0)
# -- Monitoring (maps to PrometheusConfig from mp_observability/config.py) --
# Note: the CRD uses `enabled: true` by default; the CLI equivalent is
# the absence of the `--disable-prometheus` flag.
prometheus:
enabled: bool # default: true (CLI: omit --disable-prometheus)
port: int # default: 9090
serviceMonitor:
enabled: bool # default: false
interval: string # default: "30s"
labels: map[string]string
# ServiceMonitor is a CRD from the Prometheus Operator (kube-prometheus-stack).
# When enabled, the operator creates a ServiceMonitor resource that tells
# Prometheus to automatically discover and scrape LMCache metrics endpoints.
# Without it, you'd need to manually configure Prometheus scrape targets.
# If you don't use the Prometheus Operator, leave serviceMonitor.enabled=false
# and use the pod annotations (prometheus.io/scrape, prometheus.io/port) instead.
# -- L2 storage backend (single adapter) --
# Currently only one L2 adapter is supported at a time.
# LMCache MP mode supports multiple adapters, but this is not yet
# fully tested. Once validated, the operator will support multiple.
# Exactly one of resp or raw must be set.
l2Backend:
# Option A: Native RESP (Redis/Valkey) adapter
resp:
host: string # REQUIRED
port: int # REQUIRED, 1-65535
numWorkers: int # default: 8
maxCapacityGB: float # default: 0 (disabled)
authSecretRef: # optional, Secret with "username"/"password" keys
name: string
# Option B: Raw escape hatch for other adapter types
raw:
type: string # adapter type name (nixl_store, fs, mock, raw_block, etc.)
config: map[string]any # type-specific config as free-form map
# -- Connection-injection webhook defaults (optional) --
# Read by the LMCache mutating webhook for pods bound to this engine. When
# unset, the webhook wires only the connection (--kv-transfer-config, hostIPC,
# PYTHONHASHSEED). Set injection.payloadImage to ALSO stage an lmcache code
# tree into the vLLM container (emptyDir + init container + readOnly mount +
# PYTHONPATH=/lmcache-payload), reusing the CacheBlend payload-staging
# mechanism so the vLLM client and the engine server run one lmcache build.
injection:
payloadImage: # SEPARATE image: ships lmcache under /payload
repository: string # REQUIRED (no valid default)
tag: string # default: latest
pullPolicy: string # default: IfNotPresent
imagePullSecrets: []LocalObjectReference # for a private payload image
targetContainer: string # default: first container
# -- Resources (auto-computed, no user input needed) --
# The operator derives resource requests/limits from l1.sizeGB:
# memoryRequest = ceil(l1.sizeGB + 5) Gi
# memoryLimit = ceil(memoryRequest * 1.5) Gi
# cpuRequest = "4"
# To override, use the resourceOverrides escape hatch below.
resourceOverrides: ResourceRequirements # optional, raw K8s resources override
# -- Logging --
logLevel: string # default: INFO (DEBUG|INFO|WARNING|ERROR)
# -- Scheduling --
nodeSelector: map[string]string
affinity: Affinity
tolerations: []Toleration
# nodeSelector determines which nodes get an LMCache instance.
# Use nodeSelector: {nvidia.com/gpu.present: "true"} to target all GPU
# nodes. When new GPU nodes join the cluster, the DaemonSet controller
# automatically schedules an LMCache pod on them.
# -- Overrides --
env: []EnvVar # additional environment variables
volumes: []Volume # additional volumes (e.g. for L2 disk backend)
volumeMounts: []VolumeMount
podAnnotations: map[string]string
podLabels: map[string]string
serviceAccountName: string
priorityClassName: string
# -- Security --
privileged: bool # default: false; run the engine container in
# privileged mode. Needed only on clusters where
# the engine cannot see the node GPUs otherwise
# (see "Auto-managed Pod Settings" below).
# -- Extra CLI flags --
extraArgs: []string # appended last, can override any auto-generated flag
Auto-managed Pod Settings (not in CRD spec)
The operator always injects these into the pod spec:
hostIPC: true— required for CUDA IPC between LMCache and vLLM. LMCache usesCudaIPCWrapperwhich calls PyTorch's_share_cuda_()to get a GPU driver-level IPC handle. The handle is serialized and sent over ZMQ TCP. The receiving process reconstructs the tensor viacudaIpcOpenMemHandleat the driver level. This call requires both processes to share the same IPC namespace — withouthostIPC: true,cudaIpcOpenMemHandlefails withcudaErrorMapBufferObjectFailed. Both the LMCache pods and vLLM pods must havehostIPC: true.runtimeClassName: nvidia— uses the NVIDIA container runtime, which injects the host's NVIDIA driver libraries and device files into the container. This is required for CUDA to function inside the pod.NVIDIA_VISIBLE_DEVICES=all— gives the engine access to all GPUs on the node for CUDA IPC and custom data transfer kernels without claiming any vianvidia.com/gpuresource requests (which would make those GPUs unavailable to the serving engine). The combination ofruntimeClassName: nvidia+NVIDIA_VISIBLE_DEVICES=alllets the container see all GPUs without consuming device-plugin resources, so the serving engine (e.g., vLLM) can still request all GPUs on the node. On most clusters this is sufficient; on some, the engine cannot see the GPUs unless the pod is also privileged — setspec.privileged: trueto run the engine container privileged (defaultfalse).NVIDIA_VISIBLE_DEVICES=allandNVIDIA_DRIVER_CAPABILITIES=all— env vars that instruct the NVIDIA container runtime to expose all GPUs and all driver capabilities to the container.--host 0.0.0.0— always passed as a container arg. The server defaults to--host localhostwhich only binds to loopback; the server must bind to all interfaces so the node-local Service can route traffic to it.- No
hostNetwork— the operator does not usehostNetwork. Instead, it creates a ClusterIP Service withinternalTrafficPolicy=Local. kube-proxy ensures that traffic to the service is routed only to the LMCache pod on the same node. This avoids occupying host ports and reduces the privileged surface area. - No
/dev/shmemptyDir mount — the operator intentionally does not mount an emptyDir at/dev/shm. WithhostIPC: true, the container already sees the host's/dev/shm. Mounting an emptyDir would shadow the host's/dev/shmwith a private tmpfs, breaking CUDA IPC (cudaIpcOpenMemHandlefails because IPC handles written by one pod are invisible to others). If your workload needs a larger/dev/shmfor non-IPC purposes, add it viaspec.volumes/spec.volumeMounts.
Security implications: The LMCache pods run with
hostIPC: true(required for CUDA IPC), which exposes the host's IPC namespace. Whenspec.privileged: trueis set they additionally run privileged, granting full device access to the container. Only deploy in trusted environments. Clusters using Pod Security Standards must allow theprivilegedprofile for the LMCache namespace wheneverhostIPC/privilegedare in use — thebaselineandrestrictedprofiles reject these settings.
Validation Rules
| Field | Rule |
|---|---|
l1.sizeGB |
Required, must be > 0 |
eviction.policy |
Must be "LRU" (if set) |
eviction.triggerWatermark |
Must be in (0.0, 1.0] |
eviction.evictionRatio |
Must be in (0.0, 1.0] |
server.port |
Must be in [1024, 65535] |
CRD Status
status:
phase: Pending | Running | Degraded | Failed
observedGeneration: int64
desiredInstances: int
readyInstances: int
# Per-node connection info (for kubectl visibility)
endpoints:
- nodeName: string
hostIP: string
podName: string
port: int
metricsPort: int
ready: bool
# Standard conditions
conditions:
- type: Available # at least one instance ready
- type: AllInstancesReady # all desired instances ready
- type: ConfigValid # spec validation passed
# Connection ConfigMap is always named <metadata.name>-connection;
# no need to store the ref in status.
Examples
Minimal Deployment
apiVersion: lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: my-cache
spec:
l1:
sizeGB: 60
This deploys a DaemonSet with 60GB L1 cache, LRU eviction, blake3 hashing, port 5555, auto-computed 65Gi memory request / 98Gi limit, Prometheus on 9090, and a connection ConfigMap for vLLM. The operator auto-injects hostIPC and --host 0.0.0.0, and creates a node-local Service for vLLM discovery.
Production Deployment (all GPU nodes)
apiVersion: lmcache.ai/v1alpha1
kind: LMCacheEngine
metadata:
name: production-cache
namespace: llm-serving
spec:
# Target all GPU nodes — new GPU nodes automatically get an LMCache pod
nodeSelector:
nvidia.com/gpu.present: "true"
image:
repository: lmcache/standalone
tag: v0.1.0
pullPolicy: IfNotPresent
server:
port: 6555
chunkSize: 256
maxWorkers: 4
hashAlgorithm: blake3
l1:
sizeGB: 60
eviction:
triggerWatermark: 0.8
evictionRatio: 0.2
prometheus:
enabled: true
port: 9090
serviceMonitor:
enabled: true
labels:
release: kube-prometheus-stack
logLevel: INFO
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
priorityClassName: system-node-critical
Resources Created by the Operator
For LMCacheEngine named production-cache:
| Resource | Name | Purpose |
|---|---|---|
| DaemonSet | production-cache |
Runs LMCache server pods |
Service (ClusterIP, internalTrafficPolicy=Local) |
production-cache |
Node-local service discovery for vLLM |
| ConfigMap | production-cache-connection |
kv-transfer-config JSON pointing to the lookup Service |
| Service (headless) | production-cache-metrics |
Prometheus scrape target |
| ServiceMonitor (optional) | production-cache |
Prometheus Operator integration |
ConfigMap Content (for vLLM discovery)
{
"kv_connector": "LMCacheMPConnector",
"kv_connector_module_path": "lmcache.integration.vllm.lmcache_mp_connector",
"kv_role": "kv_both",
"kv_connector_extra_config": {
"lmcache.mp.host": "tcp://<name>.<namespace>.svc.cluster.local",
"lmcache.mp.port": "<spec.server.port, default 5555>"
}
}
The ConfigMap uses the lookup Service's cluster DNS name. Because the Service has internalTrafficPolicy=Local, kube-proxy routes traffic only to the LMCache pod on the same node as the vLLM pod. vLLM pods mount this ConfigMap and pass the JSON to --kv-transfer-config — no downward API or shell variable substitution required. The explicit kv_connector_module_path makes the external LMCache MP connector path load-bearing and avoids silent fallback to an older vendored builtin connector path.
Auto-injected Pod Template Details
In addition to the auto-managed pod settings above (hostIPC, --host 0.0.0.0),
the operator injects:
- Container command:
/opt/venv/bin/lmcache server - Container args: serialized from spec fields
- Env:
LMCACHE_LOG_LEVELfromspec.logLevel - Probes:
- Startup: TCP on server port,
initialDelay=5s,period=5s,failureThreshold=30 - Liveness: TCP on server port,
period=10s - Readiness: TCP on server port,
period=5s
- Startup: TCP on server port,
Reconciliation Logic
OnEvent(LMCacheEngine create/update/delete):
1. VALIDATE spec -> set condition ConfigValid
2. COMPUTE derived values (unless overridden):
- memoryRequest = ceil(l1.sizeGB + 5) Gi
- memoryLimit = ceil(memoryRequest * 1.5) Gi
- containerArgs from all spec fields
3. RECONCILE DaemonSet (CreateOrUpdate, ownerRef)
- Always inject: hostIPC, runtimeClassName: nvidia, NVIDIA_VISIBLE_DEVICES=all, --host 0.0.0.0 (privileged only when spec.privileged=true)
4. RECONCILE node-local lookup Service (internalTrafficPolicy=Local)
5. RECONCILE headless Service for metrics
6. RECONCILE connection ConfigMap
7. RECONCILE ServiceMonitor (if enabled)
8. UPDATE status:
- Query workload for ready/desired counts
- Enumerate pods -> build endpoints list
- Set phase: Running | Degraded | Pending | Failed
- Set conditions, observedGeneration
Secondary watches: DaemonSet, Pods (readiness changes → update endpoints), Nodes (new GPU node → DaemonSet auto-schedules)
Deletion / cleanup: every child resource the operator creates
(DaemonSet, lookup Service, metrics Service, connection ConfigMap,
managed RESP auth Secret, optional ServiceMonitor) carries an
ownerReference to the LMCacheEngine, so Kubernetes garbage
collection cascade-deletes them when the CR goes away. No finalizer
is used. An earlier design added a lmcache.ai/cleanup finalizer
to mirror that GC behavior, but it was a no-op that only created
deadlocks when the controller pod was not running (e.g. during
cluster issues or a single-step kubectl delete -k config/default).
The reconciler now actively strips that legacy finalizer from any CR
it sees, so migration from older operator versions is automatic.
Finalizers will return when we need to clean up state K8s GC cannot
reach (Redis L2 keys, federation deregistration, etc.).
CacheBlend: CacheBlendEngine CRD + Injection Webhook
CacheBlend reuses cached KV at shifted positions. It has two halves the operator manages together: a GPU-resident blend engine (server side) and a vLLM-side plugin that must be loaded into the serving container. The operator ships both as a second CRD plus a mutating admission webhook.
This implements what was previously deferred as a future
blend.enabledfield onLMCacheEngine. It is instead a separateCacheBlendEnginekind (with its own controller) plus an injection webhook — cleaner separation, and no behavior change toLMCacheEngine.
CacheBlendEngine CRD
Group lmcache.lmcache.ai, v1alpha1, kind CacheBlendEngine (shortName cbe).
The spec mirrors LMCacheEngineSpec (image, server, l1, eviction, prometheus,
l2Backend, scheduling, overrides, imagePullSecrets) and adds:
blend.checkLayer(default 1) andblend.recompRatio(default 0.15) — CB tunables fed to the vLLM connector.injection— what the webhook injects into vLLM pods:payloadImage(anImageSpec—repository/tag/pullPolicy, likespec.image— for the privatelmcache-cacheblendinit-container image; setrepositoryexplicitly, the inherited engine-image default is not a valid payload),imagePullSecrets(appended to the vLLM pod so the private payload image can pull — the Secret must exist in the vLLM pod's namespace),targetContainer(default: first container), andcudagraph(eager|piecewise|full_decode_only, defaulteager).server.chunkSizedefaults to 256 and is validated to equal 256 (the blend matcher requireschunk_size == vLLM --block-size * 4).
The blend engine (controller)
CacheBlendEngineReconciler mirrors LMCacheEngineReconciler and reconciles a
DaemonSet running lmcache server --engine-type blend (plus
--l1-align-bytes 16777216), a node-local lookup Service, a metrics Service, and
a <name>-connection ConfigMap. GPU model is identical to LMCacheEngine:
runtimeClassName: nvidia + NVIDIA_VISIBLE_DEVICES=all + hostIPC: true (and
privileged when spec.privileged=true), with no nvidia.com/gpu
device-plugin claim — the engine
shares the vLLM GPU rather than reserving one, because the blend server scatters
re-RoPE'd KV directly into vLLM's paged KV over same-device CUDA IPC. The
engine resource builders are the same name/spec-keyed cores used by
LMCacheEngine.
The <name>-connection ConfigMap carries the CBKVConnector
kv-transfer-config (vs LMCacheMPConnector for LMCacheEngine) — same node-local
tcp:// host/port shape, plus the cb.* tunables:
{
"kv_connector": "CBKVConnector",
"kv_connector_module_path": "lmcache_cacheblend.connector",
"kv_role": "kv_both",
"kv_connector_extra_config": {
"lmcache.mp.host": "tcp://<name>.<namespace>.svc.cluster.local",
"lmcache.mp.port": "<server.port>",
"cb.check_layer": <blend.checkLayer>,
"cb.recomp_ratio": <blend.recompRatio>
}
}
Co-location works exactly like LMCacheEngine: one engine per GPU node
(DaemonSet), and the node-local Service (internalTrafficPolicy: Local) routes a
vLLM pod to the same-node engine. The control-plane RPC is TCP via that Service;
the data-plane KV write is CUDA IPC on the shared GPU.
The injection webhook
A mutating admission webhook (/mutate--v1-pod, CREATE, failurePolicy: Ignore)
injects the lmcache-cacheblend plugin into opted-in pods so a stock vLLM image
needs no rebuild. A pod opts in with label lmcache.ai/cacheblend-inject: "true"
and binds to an engine with annotation lmcache.ai/cacheblend-engine: <name>. The
webhook then applies:
| Mutation | What |
|---|---|
pod hostIPC: true |
required for CUDA IPC with the node-local engine |
cb-plugin emptyDir + payload init container |
the busybox payload cp -a's the pure-Python plugin tree onto the shared volume |
readOnly mount + PYTHONPATH=/cb-plugin on the vLLM container |
vLLM discovers the plugin via its vllm.general_plugins entry point |
| append required vLLM args | --attention-backend CUSTOM, --kv-transfer-config <from the connection ConfigMap>, --block-size 64, --pipeline-parallel-size 1, --no-enable-chunked-prefill, --no-async-scheduling, --enforce-eager (or the configured cudagraph) |
append injection.imagePullSecrets |
so the private payload image can pull |
stamp lmcache.ai/cacheblend-injected: "true" |
idempotency guard |
The webhook skips (stamping lmcache.ai/cacheblend-skip-reason) when: the
target container overrides command (a sh -c wrapper — appended args wouldn't
reach vllm serve); the user already supplies --kv-transfer-config (not
clobbered); the named engine's connection ConfigMap doesn't exist; the engine's
injection.payloadImage resolves to an empty reference (payload-image-unset);
or the requested targetContainer/cacheblend-container annotation names a
container that does not exist on the pod (target-container-not-found). It does
not gate on engine readiness — like LMCacheEngine, the connector connects
when the engine comes up. Args are emitted in two-token form
(--attention-backend CUSTOM); the replace-not-duplicate dedup still recognizes a
user-supplied --flag=value.
Shared with the LMCache injector. The emptyDir + payload init container + readOnly mount +
PYTHONPATHstaging (rows 2–3 above) is generic — the standardLMCacheEngineinjector (/mutate-lmcache--v1-pod) reuses it (underinternal/webhook/payload_staging.go) to optionally stage anlmcachebuild whenLMCacheEngine.spec.injection.payloadImageis set. There the staging is additive and optional: the connection wiring always runs, and an unset (or absent)injection.payloadImagesimply stages nothing — it never skips the whole injection. The LMCache staging uses its own names (lmcache-payloadvolume,/lmcache-payloadmount) so both injectors can fire on one pod.
Prerequisites
- cert-manager — the webhook's serving cert is a cert-manager
Issuer+Certificate(caBundle injected viainject-ca-from); install it beforemake deploy. make deploy, notmake run—make runsetsENABLE_WEBHOOKS=falseand installs noMutatingWebhookConfiguration; it is controller-only. The webhook needs the operator running as an in-cluster pod.- Pod Security Standards — the injected
hostIPC/privilegedis rejected by thebaseline/restrictedprofiles, so the engine's and the vLLM pod's namespaces must be labeledpod-security.kubernetes.io/enforce=privileged.
Resources created (for a CacheBlendEngine named cb)
| Resource | Name | Purpose |
|---|---|---|
| DaemonSet | cb |
lmcache server --engine-type blend on GPU nodes |
| Service (node-local) | cb |
same-node discovery for vLLM (CBKVConnector) |
| Service (headless) | cb-metrics |
Prometheus scrape target |
| ConfigMap | cb-connection |
CBKVConnector kv-transfer-config |
| MutatingWebhookConfiguration | (operator-wide) | injects the plugin into opted-in vLLM pods |
Coordinator: LMCacheCoordinator CRD
The coordinator (lmcache/v1/mp_coordinator/) is the fleet-level HTTP service
that engine servers register/heartbeat against, and which drives L2 quota
eviction and global CacheBlend lookups. Unlike the engines (one DaemonSet pod per
GPU node), the coordinator is a single fleet-wide service, so LMCacheCoordinator
reconciles a Deployment + ClusterIP Service instead of a DaemonSet. The
controller carries no finalizer — owner-reference GC cascade-deletes the children.
LMCacheCoordinatorSpec
The spec mirrors MPCoordinatorConfig (lmcache/v1/mp_coordinator/config.py); the
controller renders each field into the matching lmcache coordinator CLI flag:
host, port (9300), instanceTimeout (30), healthCheckInterval (10),
evictionCheckInterval (5), evictionRatio (0.2), triggerWatermark (1.0),
blendChunkSize (256), blendProbeStride (1). It also carries replicas,
image, prometheus, and the usual pod-shaping fields (resources, scheduling,
env, etc.).
The global-CacheBlend knobs (blendChunkSize, blendProbeStride) render into the
--blend-chunk-size / --blend-probe-stride flags and default to 256 / 1. Note
blendChunkSize must equal the blend servers' chunk size.
Metrics caveat: the coordinator process exposes only
/healthz, not a Prometheus/metricsendpoint. ServiceMonitor support is wired for parity but defaults disabled; enabling it is only useful once a metrics endpoint is added to the coordinator app.
Connecting engines to a coordinator
LMCacheEngineSpec and CacheBlendEngineSpec gained a coordinator block
(CoordinatorConnectionSpec) that maps to the server's coordinator-client flags
(lmcache/v1/multiprocess/config.py: add_coordinator_args):
ref— name of anLMCacheCoordinatorin the same namespace. The controller resolves it to the coordinator's Service URL (http://<name>.<ns>.svc:<port>) before building the DaemonSet, soBuildContainerArgsstays a pure function.url— explicit escape hatch for a coordinator the operator does not manage. Exactly one ofref/urlis required (enforced in validation).advertiseIP— do not set this in almost every case. It defaults to the pod IP via the downward API envLMCACHE_COORDINATOR_ADVERTISE_IP, which is correct for normal in-cluster deployments. Only override it if you know exactly what you are doing (e.g. the coordinator runs outside the cluster and must reach the server through a specific externally-routable address); an incorrect value silently breaks coordinator-to-server connectivity.heartbeatInterval,l2EventReporting,l2EventFlushInterval.
When coordinator is unset, the server emits no --coordinator-url and does not
register (unchanged behavior).
Resources created (for an LMCacheCoordinator named coord)
| Resource | Name | Purpose |
|---|---|---|
| Deployment | coord |
lmcache coordinator HTTP server (port 9300) |
| Service (ClusterIP) | coord |
fleet-wide discovery endpoint for engines |
| Service (headless) | coord-metrics |
Prometheus scrape target (only if prometheus.serviceMonitor.enabled) |
| ServiceMonitor | coord |
Prometheus scrape config (gated, disabled by default) |
Future Extensibility
- L2 backends: The RESP (Redis/Valkey) adapter is natively supported with typed CRD fields and Secret-based auth injection. Other adapter types (nixl_store, fs, mock, mooncake_store, raw_block) can be configured via the
rawescape hatch. Currently only a single L2 adapter is supported at a time. LMCache MP mode is designed to support multiple adapters in cascade, but this is not yet fully tested — once validated, the operator will support multiple adapters. - Blend mode: Implemented as the separate
CacheBlendEngineCRD + injection webhook — see CacheBlend above. (This supersedes the earlier idea of ablend.enabledfield onLMCacheEngine.) - Update strategy: Future
spec.updateStrategyfield forRollingUpdate/OnDeletecontrol on the DaemonSet. - Coordinator: Implemented as the separate
LMCacheCoordinatorCRD (Deployment + Service) with engine-sidecoordinatorconnection wiring — see Coordinator above. - Additional CRDs:
LMCacheKeyManager(global key management),LMCacheMonitor(engine state monitoring),LMCacheFederation(cross-cluster P2P topology).
Key Source Files Referenced
| File | What it defines |
|---|---|
lmcache/v1/distributed/config.py |
L1MemoryManagerConfig, L1ManagerConfig, EvictionConfig, StorageManagerConfig, argparse |
lmcache/v1/mp_observability/config.py |
PrometheusConfig, add_prometheus_args, parse_args_to_prometheus_config |
lmcache/v1/multiprocess/server.py |
MPCacheServer, server CLI entry point, argparse (lines 629–653) |
lmcache/v1/multiprocess/http_server.py |
HTTP server with /healthcheck endpoint (FastAPI + ZMQ) |
lmcache/v1/mp_coordinator/config.py |
MPCoordinatorConfig (coordinator knobs mapped by LMCacheCoordinator) |
lmcache/cli/commands/coordinator.py |
lmcache coordinator CLI (flags rendered into the coordinator Deployment) |
lmcache/v1/multiprocess/config.py |
add_coordinator_args (engine coordinator connection flags) |
lmcache/v1/distributed/l2_adapters/config.py |
L2 adapter registry pattern, L2AdapterConfigBase, L2AdaptersConfig |
examples/multi_process/lmcache-daemonset.yaml |
Reference DaemonSet manifest |
examples/multi_process/vllm-deployment.yaml |
Reference vLLM deployment with kv-transfer-config |