Skip to main content
Version: Next

Standalone Router (File-Based Resources)

Overview​

By default Kthena Router watches ModelRoute, ModelServer and ExternalModelProvider objects on the Kubernetes API server. With --config-source=file the router instead reads the very same manifests from a local directory, which lets it run without a cluster, for example for local development, benchmarking or a container-only deployment.

The APIs are unchanged: the files hold exactly the objects you would otherwise apply with kubectl, so a configuration can be moved between both modes.

Running the router with file-based resources​

kthena-router \
--config-source=file \
--config-dir=/etc/kthena/resources \
--config-sync-period=10s
FlagDefaultDescription
--config-sourcekubernetesWhere resources are read from. One of kubernetes or file.
--config-dir/etc/kthena/resourcesDirectory holding the manifests when --config-source=file.
--config-sync-period10sHow often the directory is re-read.

The router reads every .yaml, .yml and .json file directly inside the directory; sub-directories are ignored. A file may contain several documents separated by ---. Resources without metadata.namespace are placed in the default namespace.

Supported kinds are ModelRoute, ModelServer, ExternalModelProvider and Secret. Any other kind is ignored with a warning.

Because there is no API server in this mode, the admission webhook is disabled automatically and --enable-gateway-api cannot be used. The manifests are instead validated with the same rules when the directory is loaded; if any document is invalid, the whole snapshot is rejected and the router keeps serving the last good one.

Configuring serving instances​

Serving instances cannot be discovered as pods without an API server, so a ModelServer lists them in spec.endpoints:

apiVersion: networking.serving.volcano.sh/v1alpha1
kind: ModelServer
metadata:
name: deepseek-r1-1-5b
namespace: default
spec:
endpoints:
- name: vllm-0
address: 127.0.0.1
port: 8000
- name: vllm-1
address: 127.0.0.1
port: 8001
model: "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"
inferenceEngine: "vLLM"
FieldDescription
nameIdentifies the instance in scheduling, metrics and the debug endpoints as <ModelServer name>:<name>. Must be unique within the ModelServer.
addressIP address or DNS name of the instance.
portPort of the instance. Defaults to spec.workloadPort.port and is required when that port is unset.
labelsLabels matched by spec.workloadSelector.pdGroup to assign a prefill or decode role.

spec.endpoints and spec.workloadSelector.matchLabels are mutually exclusive. Static endpoints are ordinary serving instances for the rest of the router: they are scheduled, scraped for engine metrics and proxied to exactly like discovered pods. The field also works on a cluster, which is useful for routing to model servers that run outside of it.

Prefill/decode disaggregation​

To disaggregate statically configured instances, keep spec.workloadSelector for the pdGroup definition only and label each endpoint:

spec:
workloadSelector:
pdGroup:
groupKey: pd-group
prefillLabels:
role: prefill
decodeLabels:
role: decode
endpoints:
- name: prefill-0
address: 127.0.0.1
port: 8000
labels:
role: prefill
pd-group: group-a
- name: decode-0
address: 127.0.0.1
port: 8001
labels:
role: decode
pd-group: group-a

Updating resources​

The directory is re-read every --config-sync-period. Added, changed and removed manifests are applied to the running router, so no restart is needed. A manifest that fails to parse is reported in the router log and the last valid snapshot keeps serving traffic.

A complete example is available in examples/kthena-router/standalone.