A Kubernetes-native cache plane for LLM inference.
One operator, split across two control-plane binaries (controller + server) plus the operator CLI and the CRDs.
CRDs — the API
api/v1alpha1/— Go types (CacheBackend;CachePolicy,CacheTenant,PromptTemplate,PDTopology,CacheIndexas they land) + generated deepcopyconfig/— generated CRD, RBAC, and sample manifests
inferencecache-controller (cmd/controller) — watches CRDs and provisions cache backends
cmd/controller/— controller-runtime manager entrypointinternal/controller/— reconcilerspkg/adapters/runtime/—KVCacheRuntimeAdapters render the cache-server pod/service and inject engine/router pod config; the reconciler drives them through the in-packageRegistry
inferencecache-server (cmd/server) — gRPC policy server + cache-state index + metrics
cmd/server/— gRPC + HTTP server entrypointpkg/server/— gRPC service (LookupRoute,RenderTemplate, …), health, metricsproto/(+ generated stubs) — the gRPC contractpkg/index/— cache-state aggregator (CacheIndex)pkg/render/— mutable-slot prompt rendering engine (the wedge); importable librarypkg/adapters/engine/— engine KV-event hook (feeds the index)
inferencecache (cmd/inferencecache) — operator CLI; doctor runs a read-only pre-flight diagnostic
cmd/inferencecache/— cobra entrypointpkg/cli/doctor/— diagnostic checks + output formatters (seedocs/cli/doctor.md)
Shared — pkg/version/, hack/, dockerfiles/, .githooks/
make proto-gen
make build
make testRun the server locally:
bin/server \
--grpc-bind-address=:9090 \
--http-bind-address=:8080 \
--snapshot-bind-address=:8081 \
--insecure-disable-auth # local-dev only; production passes --allowed-controller-sa instead
curl -i http://localhost:8080/healthz # liveness
curl -i http://localhost:8080/readyz # readiness
curl -s http://localhost:8080/metrics # Prometheus metrics (inferencecache_*)
curl -s http://localhost:8081/snapshot # internal aggregate (auth-gated in production)
curl -i -XPOST http://localhost:8081/policy -d '{"version":3,"policies":[]}' # controller push (auth-gated in production)
curl -i -XPOST http://localhost:8081/probe \
-H 'Content-Type: application/json' \
-d '{"backend":"local/dev","model":"dev-model","hashScheme":"vllm"}' # functional self-test (auth-gated in production)The server fails closed by default: omitting both --allowed-controller-sa and
--insecure-disable-auth causes it to exit 2 with a stderr message. That keeps
an operator who forgets the flag from accidentally shipping unauthenticated
/snapshot, /policy, and /probe endpoints on a real cluster.
The controller serves admission webhooks — defaulting + validation for
CacheBackend, plus a mutating Pod webhook that auto-injects the LMCache
engine configuration into pods labeled to match a CacheBackend's
spec.engineSelector — over TLS, so deploying it requires cert-manager
v1.0+ in the target cluster. The default install (config/default)
provisions a self-signed Issuer plus a Certificate for the webhook
serving cert, and relies on cert-manager's cert-manager.io/inject-ca-from
annotation to inject the CA bundle into the generated
MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yamlThe default Kustomize overlay brings up both control-plane components:
inference-cache-controller-manager— the reconciler + admission webhooks.inference-cache-server— the gRPC policy server (InferenceCache) and the HTTP/healthz,/readyz,/metricsprobe surface, plus a dedicated controller-facing listener carrying/snapshot(controller read),/policy(controller write), and/probe(functional self-test driven by the CacheBackend reconciler), all gated by ServiceAccount bearer auth + aNetworkPolicy. Fronted by aClusterIPServiceinference-cache-serverin theinference-cache-systemnamespace with named portsgrpc:9090(gRPC API),http:8080(probes / metrics), andsnapshot:8081(controller-only/snapshot+/policy+/probe). The controller's CacheIndex poller scrapeshttp://inference-cache-server:8081/snapshot, the CachePolicy reconciler POSTs tohttp://inference-cache-server:8081/policy, and the CacheBackend reconciler POSTs tohttp://inference-cache-server:8081/probe(default for the--server-probe-urlflag — set empty to disable the gate);/snapshot+/probesend the controller-audience projected ServiceAccount token, while/policysends the write-side policy-audience token. Once both pods are Readykubectl get cacheindexreports live cluster-wide cache state.FunctionalProbeOKappears on each managedCacheBackendonly after it clears the upstream KV-event readiness gate (i.e. real engine pods have published at least one KV event for it) — see docs/design/cachebackend-api.md#functional-probe-gate; it is intentionally not visible on a default install with no engine workload.
kubectl apply -k config/default
kubectl -n inference-cache-system wait --for=condition=Available deployment --all --timeout=180s
kubectl get cacheindex cluster-default -o yamlBoth binaries expose Prometheus metrics on their pod's :8080/metrics
(prefixed inferencecache_*) — the server binary's series cover the
in-memory index and gRPC handlers; the controller binary's series cover
the reconcilers (e.g. inferencecache_backend_probe_result_total,
inferencecache_backend_server_restart_cascades_total). A default
alert bundle for the operational silent-failure patterns this code has
hit in production ships under
config/observability/ and is not included
in config/default — the alerts are opt-in so that installs without
prometheus-operator CRDs are not affected by an unknown apiVersion.
For prometheus-operator / kube-prometheus installs:
kubectl apply -k config/observabilityThis ships THREE resources: a ServiceMonitor (so Prometheus scrapes
inference-cache-server:8080/metrics), a PodMonitor (so Prometheus
scrapes the controller pod's :8080/metrics — required for the
controller-side alerts like ServerProbeFail to have a series to
evaluate), and the PrometheusRule carrying the alerts.
Caveat — Prometheus Operator selectors. All three CRs carry example labels (
prometheus: k8s, plusrole: alert-ruleson the PrometheusRule) that match the upstream kube-prometheus stack (defaultPrometheusnamedk8s). Thekube-prometheus-stackHelm chart uses a different convention (release: <release-name>, noprometheus:label) — its rule / serviceMonitor / podMonitor selectors do not match what's shipped here. If yourPrometheusCR'sruleSelector/serviceMonitorSelector/podMonitorSelectoruses a different label set (release: my-prom, etc.),kubectl apply -ksucceeds but Prometheus silently ignores the resources. The YAML comments next to each label spell out the introspection command (kubectl get prometheus -A -o jsonpath=...); seedocs/observability/alerts.mdfor the full discussion.
Four of the five Stage 1 alerts (IndexEmpty, LookupRouteDegenerate,
LookupRouteHighTimeout, IndexEvictionsSpike) become active as soon
as the operator is installed AND the selectors match — they remain
quiet on a healthy or idle install (each rule is gated by traffic/
rate/eviction thresholds; see for: + the rate floors in the alert
expressions) and only fire when the conditions are met.
The fifth alert needs a vLLM scrape this bundle does NOT ship.
LMCacheT2NoHitsreadsvllm:external_prefix_cache_*from vLLM engine pods directly. The shippedServiceMonitorcovers onlyinference-cache-server. To make that alert effective, add a separatePodMonitorfor your vLLM Deployment (orkubernetes_sd_configs: podfor vanilla Prometheus) so engine/metricsis scraped with bothnamespaceandpodlabels attached. See alerts.md "How to enable" for the requirement.
For vanilla Prometheus, ConfigMap mounts, or Helm prometheus.serverFiles,
use the flat alerting-rules.yaml.
You must also configure scraping yourself, for BOTH the server AND
the controller pod. The server's :8080 exposes the index, lookup,
and auth series; the controller pod's :8080 exposes the per-stage
probe-result counter (inferencecache_backend_probe_result_total)
and the cache-server restart-cascade counter — the controller-side
alerts (ServerProbeFail today) load against the controller's
series, so a server-only scrape leaves them inert.
For multi-install or per-install isolation, use Kubernetes service
discovery (kubernetes_sd_configs: pod or endpoints) with
relabel_configs: that copies __meta_kubernetes_namespace to
namespace — the alerts scope per install by that label. A pair of
static DNS scrapes (e.g.
inference-cache-server.inference-cache-system.svc.cluster.local:8080
PLUS pod-IP discovery for the controller manager pods, which have no
Service in front of them) is acceptable for a single install but
loses per-install isolation; do NOT use it if you scrape multiple
inference-cache installs into one Prometheus. Without a working
scrape on both endpoints, the rules load but fire on nothing (server
alerts) or load but never have a series to evaluate (controller
alerts like ServerProbeFail).
Per-alert runbooks (causes, triage steps, example PromQL): see
docs/observability/alerts.md. For the
underlying metric surface, see
docs/reference/metrics.md.
Create a kind cluster for controller development:
make dev-clusterBy default this creates or reuses a cluster named inference-cache. You can override the name and node image:
make dev-cluster KIND_CLUSTER=cache-dev KIND_NODE_IMAGE=kindest/node:v1.31.0make build: build the controller, server, kvevent-subscriber, and inferencecache binariesmake test: run unit testsmake lint: run gofmt and go vetmake sbom,make sbom-release,make sbom-images,make sbom-registry-images: generate SPDX JSON SBOMs (seedocs/operations/sbom.md)make ci-lint: run golangci-lintmake verify-prometheus: lint + unit-test the Prometheus alerting rules underconfig/observability/make proto-gen: regenerate protobuf Go codemake generate: regenerate Kubernetes deepcopy codemake manifests: regenerate CRD, RBAC, and webhook manifestsmake image-build: build controller, server, and kvevent-subscriber images
Design docs live under docs/:
docs/design/cachebackend-api.md— theCacheBackendCRD contractdocs/design/grpc-contract.md— theInferenceCachegRPC service contract (B4)docs/design/policy-crds.md— policy CRDs (CachePolicy,CacheTenant,PromptTemplate,PDTopology,CacheIndex)
Contributor guide: CONTRIBUTING.md (layout, naming rule, push/PR gates).
Apache-2.0