Skip to main content

CRD reference

Noctaya exposes two resources in serving.noctaya.io/v1alpha1.

ResourceScopePurpose
LLMServiceNamespacedDeclares a model endpoint and its resources, scaling, cache, and cold-start behavior
InferenceRuntimeClusterDefines a reusable serving image and its accelerator, scheduling, health, and lifecycle integration

The API is alpha. Kubernetes may accept reserved fields that the controller cannot yet reconcile; those fields are marked not implemented below. The Go types in api/v1alpha1 and the generated CRDs remain authoritative.

Durations use Go/Kubernetes syntax such as 10s, 2m, and 5m. Resource quantities use Kubernetes syntax such as 500m, 32Gi, and 60Gi.

LLMService

The schema requires spec.model and spec.runtime. Set exactly one model source (source or the reserved catalogRef) and exactly one runtime selection (name or selector.vendor).

Model and runtime

FieldTypeDefault / constraintBehavior
spec.model.source.uristringRequired for reconciliationSupports hf:// (huggingface:// alias), modelscope://, digest-pinned oci://<registry>/<repository>@sha256:<digest>, and pvc://<claim>[/<subpath>]
spec.model.catalogRefstringNot implementedReserved for model-catalog lookup
spec.model.source.secretRefLocalObjectReferenceOptional for oci:// onlyReferences a kubernetes.io/dockerconfigjson Secret in the service namespace
spec.runtime.namestringExactly one runtime selectionPins one cluster-scoped InferenceRuntime
spec.runtime.selector.vendorstring arrayExactly one runtime selection; nvidia or ascendSelects the first available vendor in order, then the highest-priority runtime
spec.runtime.argsOverridestring arrayOptionalAppends arguments after the runtime's templated arguments

A pvc:// source mounts existing weights read-only at /models and performs no download. Use cache.strategy: None to avoid creating a separate cache PVC. An oci:// source must use an immutable SHA-256 digest and a persistent cache; Noctaya pulls its files with ORAS, rejects traversal and symbolic links, and exposes the cache path only after atomic promotion. The artifact filesystem root must contain the exact model directory expected by the runtime. Tags and s3:// are not supported.

Resources and scaling

FieldTypeDefault / constraintBehavior
spec.resources.acceleratorsinteger1; minimum 1Whole devices requested per backend replica
spec.resources.cpuquantityOptionalCPU request per backend replica
spec.resources.memoryquantityOptionalEqual memory request and limit per backend replica
spec.resources.fractionobjectNot implementedReserved for sub-device sharing
spec.scaling.mininteger0; minimum 0Minimum backend replicas; 0 enables scale-to-zero
spec.scaling.maxinteger1; minimum 1Maximum backend replicas; must be at least min
spec.scaling.metricstringqueueDepth; queueDepth or kvCacheUtilOnly queueDepth is implemented
spec.scaling.targetinteger10; minimum 1Queue-depth target per backend replica
spec.scaling.activationTimeoutpositive duration5mBounds a cold request wait or reject-mode activation lease
spec.scaling.scaleDownStabilizationduration5m; whole seconds, 0s..1hHPA stabilization and KEDA cooldown before scale-to-zero
spec.scaling.drainTimeoutnon-negative duration2mPre-stop drain time when the runtime enables preStopDrain
spec.scaling.externalScaler.tls.serverSecretNamestringRequired when tls is setMounts an existing Secret containing tls.crt, tls.key, and the client-verifying ca.crt into the ExternalScaler Pod
spec.scaling.externalScaler.tls.authenticationRef.namestringRequired when tls is setReferences the KEDA authentication object that supplies caCert, tlsClientCert, and tlsClientKey
spec.scaling.externalScaler.tls.authenticationRef.kindstringTriggerAuthentication; or ClusterTriggerAuthenticationSelects the KEDA authentication-object scope

KEDA owns backend replicas; min and max do not control gateway replicas. Noctaya consumes existing mTLS credentials but does not issue or rotate them. The server certificate must be valid for <llmservice>-scaler.<namespace>.svc.

Cache and endpoint

FieldTypeDefault / constraintBehavior
spec.cache.strategystringNodeLocalPVCImplements NodeLocalPVC, SharedPVC, HostPath, and None; BakedImage is not implemented
spec.cache.sizequantity50Gi controller defaultPVC request for NodeLocalPVC or SharedPVC
spec.cache.storageClassNamestringCluster defaultSelects the cache PVC StorageClass; SharedPVC requires ReadWriteMany support
spec.cache.prewarmbooleanfalseCreates one download Job for a persistent hub cache; OCI sources always create a staging Job
spec.endpoint.openAICompatiblebooleantrueInformational; the gateway currently always serves the OpenAI-compatible API
spec.endpoint.maxQueueinteger100; minimum 1Maximum admitted requests per gateway replica; overflow returns 429 with Retry-After
spec.endpoint.resources.requests.cpu / memoryquantityOptionalCPU and memory requested by each gateway replica
spec.endpoint.resources.limits.cpu / memoryquantityOptionalCPU and memory limits for each gateway replica
spec.endpoint.authentication.secretRef.namestringRequired when authentication is setReferences a Secret in the LLMService namespace
spec.endpoint.authentication.secretRef.keystringRequired when authentication is setSelects the Secret data key used as the accepted Bearer token
spec.endpoint.coldStart.modestringkeepalive; keepalive or rejectHolds streaming requests with SSE heartbeats or returns 503 with Retry-After
spec.endpoint.coldStart.heartbeatIntervalpositive duration10sKeepalive heartbeat interval during activation
spec.imagePullSecretsLocalObjectReference arrayOptionalApplied to backend, gateway, and prewarm Pods; Secrets must be in the service namespace

Cache PVCs and prewarm Jobs are create-once resources carrying an immutable-spec hash. A changed size, StorageClass, source, or prewarm specification fails reconciliation instead of silently reusing incompatible state. Delete the Job to retry or prewarm again; preserve needed data before replacing a PVC. SharedPVC uses ReadWriteMany; its prewarm Job writes the cache while serving containers mount a completed cache read-only. Without prewarm, serving containers retain write access.

Client authentication is opt-in. When configured, missing or invalid Authorization: Bearer <key> credentials on proxied inference routes return 401. The gateway rereads the mounted Secret file per request, so projected Secret rotation does not recreate the LLMService or gateway Pods. /healthz, /metrics, and /noctaya/queue remain unauthenticated and require network isolation.

LLMService status

FieldDescription
status.phaseSummary state: Pending, Loading, Ready, ScaledToZero, or Degraded.
status.resolvedRuntimeName of the InferenceRuntime selected by the controller.
status.replicasNumber of ready backend replicas. Gateway replicas are not included.
status.endpointURLIn-cluster OpenAI-compatible base URL, ending in /v1.
status.conditionsKubernetes conditions. Ready reports serving availability, Degraded reports a Kubernetes-observed backend activation failure, and AutoscalingReady reports whether KEDA External Push is configured. Every condition uses the reconciled ObservedGeneration.

Ready=False uses progress reasons such as Activating, Starting, ModelLoading, and SchedulingDelayed, or the active failure reason. Degraded=True uses stable failure reasons: ImagePullFailed, OOMKilled, CrashLoopBackOff, ContainerRestarting, or ProgressDeadlineExceeded. See Troubleshoot Noctaya for diagnosis and the distinction between these observations and the gateway's bounded activation_timeout.

InferenceRuntime

InferenceRuntime is reusable configuration, not a workload. Its controller is passive; the LLMService controller consumes it and requeues matching services when it changes.

Runtime and container

FieldTypeDefault / constraintBehavior
spec.familystringvllm; only vllmServing-engine family
spec.vendorstringRequired; nvidia or ascendSelects the registered backend adapter
spec.priorityinteger0Higher value wins within a selected vendor; equal top values are ambiguous
spec.container.imagestringRequiredServing image exposing an OpenAI-compatible API
spec.container.argsstring arrayOptionalTemplates using .Model.Path, .Service.Name, and .Service.Namespace
spec.container.envKubernetes EnvVar arrayOptionalCopied to the container; literal values support the same templates
spec.container.port.namestringhttpPort name used by Services and probes
spec.container.port.containerPortinteger8000; 1..65535Serving API port

spec.container and spec.container.port are required objects. Runtime arguments are rendered first; LLMService.spec.runtime.argsOverride is appended.

Accelerator and scheduling

FieldTypeDefault / constraintBehavior
spec.accelerator.resourceNamestringRequiredDevice-plugin resource, for example nvidia.com/gpu
spec.accelerator.sharing.supportedbooleanfalse; not implementedCapability placeholder; it does not enable fractional allocation
spec.accelerator.nodeSelectorstring mapOptionalCopied to backend and prewarm Pods
spec.accelerator.tolerationsKubernetes Toleration arrayOptionalCopied to backend and prewarm Pods
spec.accelerator.scheduler.namestringKubernetes default schedulerSets Pod schedulerName, for example volcano
spec.accelerator.scheduler.queuestringRequires scheduler.name: volcanoAdds the Volcano queue annotation

The runtime references existing drivers, device plugins, and schedulers; it does not install or manage them.

Health, lifecycle, and metrics

FieldDefault / constraintBehavior
spec.health.readinessController default: HTTP GET /healthGates backend traffic until the model is loaded
spec.health.startupController default: GET /health, 10-second period, 60 failuresAllows about 10 minutes for startup
spec.health.livenessNoneOptional post-start failure detection
spec.lifecycle.terminationGracePeriodSecondsOptional; minimum 1Base Pod shutdown budget
spec.lifecycle.preStopDrainfalseSleeps for the service drainTimeout; requires /bin/sh in the image
spec.metrics.path / port/metrics / httpExternal scrape metadata
spec.metrics.queueDepthRequired when metrics is setRuntime queue metric name
spec.metrics.kvCacheUtil / running / ttftOptionalAdditional external metric names

When drain is enabled, Noctaya widens the termination grace period to at least drainTimeout plus 10 seconds. Runtime metric names are metadata only; autoscaling uses gateway demand.

InferenceRuntime.status.conditions is declared but not populated because the controller is passive. Operational state is reported on each consuming LLMService. See the roadmap for planned features.