This document describes the Private LLM Operator's architecture, its components, and how they interact within the ApeiroRA Platform Mesh ecosystem.
The Private LLM Operator follows a layered architecture that separates concerns across three distinct planes:
graph TB
subgraph "Platform Mesh Control Plane (KCP)"
AE[APIExport<br>llm.privatellms.msp]
PM[ProviderMetadata]
CC[ContentConfiguration]
PW[Provider Workspace]
CW[Customer Workspace]
AB[APIBinding]
CW --> AB --> AE
PW --> AE
PW --> PM
PW --> CC
end
subgraph "MSP Cluster"
SA[Sync Agent]
OP[Private LLM Operator]
AS[Auth Server]
PR[PublishedResource<br>LLMInstance + APITokenRequest]
subgraph "Per LLMInstance"
DEP[Deployment<br>llama.cpp server]
SVC[Service :8000]
ING[Ingress /llm/slug]
MW1[Middleware<br>StripPrefix]
MW2[Middleware<br>ForwardAuth]
end
SA <--> PR
OP --> DEP
OP --> SVC
OP --> ING
OP --> MW1
OP --> MW2
MW2 --> AS
end
SA <-->|"bidirectional sync"| CW
CC -->|"portal UI loads"| ING
The core component. A Go-based Kubernetes operator built with Kubebuilder and controller-runtime.
Two controllers:
| Controller | Watches | Creates/Manages |
|---|---|---|
LLMInstanceReconciler |
LLMInstance |
Deployment, Service, Ingress, Traefik Middlewares |
APITokenRequestReconciler |
APITokenRequest, LLMInstance |
Secret (with OPENAI_API_KEY and OPENAI_API_URL) |
Key design decisions:
- Slug-based routing -- Each LLMInstance gets a random 12-character URL slug stored as an annotation. This avoids exposing namespace names in public URLs.
- Owner references -- All child resources (Deployment, Service, Ingress, Middleware) are owned by the LLMInstance CR, enabling garbage collection on deletion.
- Finalizers -- Both controllers use finalizers to ensure cleanup of associated resources.
- Health probing -- The operator probes the llama.cpp
/healthendpoint before marking an instance as Ready.
A lightweight HTTP server embedded in the operator binary, listening on :8090.
sequenceDiagram
participant Client
participant Traefik
participant AuthServer as Auth Server (:8090)
participant K8s as Kubernetes API
participant LLM as llama.cpp
Client->>Traefik: GET /llm/<slug>/v1/chat/completions<br>Authorization: Bearer <token>
Traefik->>AuthServer: GET /auth/verify?slug=<slug><br>Authorization: Bearer <token>
AuthServer->>K8s: List Secrets with label<br>llm.privatellms.msp/slug=<slug>
K8s-->>AuthServer: Matching Secrets
AuthServer->>AuthServer: Compare token with OPENAI_API_KEY
AuthServer-->>Traefik: 200 OK / 401 Unauthorized
Traefik->>LLM: Forward request (strip /llm/<slug> prefix)
LLM-->>Client: Response
The auth server validates tokens by:
- Extracting the slug from the query parameter or
X-Forwarded-Uriheader - Looking up Secrets labeled with
llm.privatellms.msp/slug=<slug> - Comparing the bearer token against the Secret's
OPENAI_API_KEYfield
The KCP API Sync Agent bridges resources between the Platform Mesh control plane (KCP) and the MSP cluster.
Published Resources:
| Resource | Direction | Related Resources |
|---|---|---|
LLMInstance |
KCP <-> MSP | None |
APITokenRequest |
KCP <-> MSP | Secret (synced from MSP to KCP) |
The sync agent uses namespace-per-workspace mapping: each KCP workspace's resources land in a dedicated namespace on the MSP cluster (named after the workspace's cluster name).
Three components enable the Platform Mesh portal UI:
-
Portal content server -- An nginx pod serving
pm-content.json, which defines the Luigi micro-frontend navigation structure for the portal UI (list views, create forms, field mappings). -
ProviderMetadata -- KCP resource providing display name, description, icon, and contact information for the marketplace.
-
ContentConfiguration -- KCP resource pointing the portal at the content server's URL to load the UI definition.
stateDiagram-v2
[*] --> Created: kubectl apply
Created --> SlugAssigned: Generate random slug annotation
SlugAssigned --> Provisioning: Create Deployment + Service + Ingress + Middlewares
Provisioning --> Provisioning: Requeue every 5s
Provisioning --> Ready: Deployment available + Endpoints ready + /health passes
Ready --> Provisioning: spec.model or spec.replicas changed
Ready --> Deleting: kubectl delete
Deleting --> [*]: Finalizer cleanup + ownerRef GC
Detailed provisioning steps:
- Slug generation -- A 12-char base64url slug is generated and stored as annotation
llm.privatellms.msp/slug - Deployment creation -- An init container downloads the GGUF model from HuggingFace, then the llama.cpp server starts
- Service creation -- ClusterIP service on port 8000
- Middleware creation -- Traefik StripPrefix (removes
/llm/<slug>) and ForwardAuth (validates bearer token) - Ingress creation -- Routes
<PUBLIC_HOST>/llm/<slug>to the service - Readiness evaluation -- Checks deployment replicas, endpoint readiness, and HTTP health probe
- Status update -- Sets
status.phase=Readyandstatus.endpoint
stateDiagram-v2
[*] --> Created: kubectl apply
Created --> Pending: LLMInstance not found or not Ready
Pending --> Pending: Requeue every 10s
Created --> SecretCreated: LLMInstance is Ready
Pending --> SecretCreated: LLMInstance becomes Ready
SecretCreated --> TokenReady: Secret with OPENAI_API_KEY + OPENAI_API_URL
TokenReady --> [*]: Delete cleans up Secret
The token controller:
- Validates that the referenced
LLMInstanceexists and is Ready - Generates a cryptographically random 32-byte token (base64url encoded)
- Creates a Secret containing
OPENAI_API_KEY(the token) andOPENAI_API_URL(the instance endpoint) - Labels the Secret with the instance's slug for auth server lookup
- Touches the APITokenRequest annotation on Secret updates to trigger sync agent re-sync
┌─────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌─────────────────────────────┐ │
│ │ private-llm-operator ns │ │
│ │ │ │
│ │ Operator + Auth Server │ │
│ │ Traefik (optional) │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ user namespace │ │
│ │ │ │
│ │ LLMInstance CRs │ │
│ │ APITokenRequest CRs │ │
│ │ llama.cpp Deployments │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
┌────────────────────┐ ┌──────────────────────────────────────────┐
│ KCP Control │ │ MSP Cluster (Gardener shoot) │
│ Plane │ │ │
│ │ │ ┌─ private-llm-operator ns ──────────┐ │
│ Provider WS: │ │ │ Operator + Auth Server │ │
│ APIExport │◄───►│ │ Portal Integration (nginx) │ │
│ ProviderMetadata │ │ │ Sync Agent │ │
│ ContentConfig │ │ │ PublishedResources │ │
│ │ │ └────────────────────────────────────-┘ │
│ Customer WS: │ │ │
│ LLMInstance │ │ ┌─ workspace namespace ──────────────┐ │
│ APITokenRequest │ │ │ LLMInstance (synced from KCP) │ │
│ Secret (synced) │ │ │ llama.cpp Deployment + Service │ │
│ │ │ │ Ingress + Middlewares │ │
│ │ │ │ APITokenRequest + Secret │ │
│ │ │ └────────────────────────────────────-┘ │
└────────────────────┘ └──────────────────────────────────────────┘
The Private LLM operator provides the backend inference endpoints that the Chat UI operator connects to. The flow is:
- User creates an
LLMInstance-- the Private LLM operator provisions a llama.cpp server - User creates an
APITokenRequest-- getsOPENAI_API_KEYandOPENAI_API_URL - User creates a Chat UI instance referencing those credentials -- Chat UI connects to the LLM endpoint
The two operators are independent: Private LLM knows nothing about Chat UI, and Chat UI simply uses the OpenAI-compatible API endpoint exposed by Private LLM.
The operator integrates OpenTelemetry for distributed tracing:
- OTLP export -- When
OTEL_EXPORTER_OTLP_ENDPOINTis set, traces are exported via OTLP/HTTP - Stdout fallback -- Without an OTLP endpoint, traces are printed to stdout (development mode)
- Trace context -- Each reconcile loop creates a span with trace/span IDs logged for correlation
- Prometheus metrics -- Standard controller-runtime metrics exposed on
:8080 - Health probes --
/healthzand/readyzon:8081
- Release Flow -- how releases are cut and delivered
- Versioning -- version numbering and compatibility policy