Problem
Support attaching a LiteLLMInstance to an existing, externally-managed deployment
Ask
An opt-out that lets a LiteLLMInstance describe a LiteLLM deployment the operator did not create, so the entity CRDs can be used against an existing installation without handing the operator the workload.
We have this working today by blocking workload reconciliation with RBAC. It works well enough to be worth supporting properly — and badly enough to be adopted as a proper feature!
Use case
A LiteLLM proxy already exists and is managed by something else — a Helm chart, a GitOps pipeline, an internal platform, whatever. What's wanted from the operator is the entity CRDs: LiteLLMTeam, LiteLLMVirtualKey, LiteLLMBudget, LiteLLMModel. Declarative teams and keys reconciled from Kubernetes beats scripting the admin API.
What isn't wanted is for the operator to own the Deployment, as it already exists.
Today, the entity CRDs are unreachable, even though nothing about them depends on the operator owning a Deployment.
Observation
The entity controllers' entire dependency on the instance is resolveInstance (internal/controller/common.go), which needs three things:
status.ready
status.endpoint
- the master key from
spec.masterKey.secretRef
Both status fields are derived from the instance name, not from anything the operator created:
// updateInstanceStatus
var dep appsv1.Deployment
if err := r.Get(ctx, types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, &dep); err == nil {
instance.Status.Ready = dep.Status.ReadyReplicas > 0
}
instance.Status.Endpoint = fmt.Sprintf("%s://%s.%s.svc:%d", scheme, instance.Name, instance.Namespace, port)
The Get carries no ownership check and the endpoint is a formula rather than a lookup. So a LiteLLMInstance named after an existing Deployment and Service already resolves correctly.
The only thing in the way is that reconcileResources runs first and adopts those objects by name — reconcileDeployment and reconcileService both Get-then-overwrite rather than declining to touch a resource they didn't create.
What we validated
We have got this working with an existing deployment, by using RBAC to prevent the operator from being able to deploy the instance. We specified the CR with the existing deployment's name, port, key etc, and the workload reconciliation was blocked by RBAC, but other than logging the failure proceeded to then work fine:
status.ready: true and status.endpoint resolved against the existing deployment
- the existing Deployment, Service and ConfigMap were byte-identical before and after — same containers, same generation, no new ReplicaSet, no pod restart
- a
LiteLLMTeam reached Synced with a real team ID from the admin API
- deleting that CR propagated through to LiteLLM — recreating it produced a new ID
- health probing still worked, since it only needs the endpoint
So the operator is entirely capable of this. It just has no way to be told to do it.
It also reports failure permanently: reconcileResources fails on every loop, so the instance sits Degraded and logs a Forbidden per resource forever. status.ready is true while the Ready condition is False with a ReconcileError. This doesn't stop it working, it just doesn't look nice, especially to someone unfamiliar with this workaround.
Proposed Solution
Proposal
apiVersion: litellm.palena.ai/v1alpha1
kind: LiteLLMInstance
metadata:
name: litellm
spec:
workload:
managed: false # default true — operator provisions the workload as today
masterKey:
secretRef: {name: litellm-master-key, key: LITELLM_MASTER_KEY}
service:
port: 80
When managed: false, skip workload reconciliation and keep everything else:
var reconcileErr error
if instance.Spec.Workload == nil || instance.Spec.Workload.Managed {
reconcileErr = r.reconcileResources(ctx, &instance, labels, licenseSecretName, guardrails)
r.reconcileAutoRollback(ctx, &instance)
}
r.updateInstanceStatus(ctx, &instance, reconcileErr)
updateInstanceStatus still populates ready and endpoint, health probing still runs, and config sync is a separate controller making API calls, so none of it is affected. Status would describe the instance honestly instead of reporting Degraded about resources it doesn't manage.
Worth considering alongside
Resolving the endpoint from the instance name works, but it makes correctness depend on a naming coincidence — the CR has to be named after a Deployment and Service it doesn't own. Extend to specify the endpoint fully:
spec:
workload:
managed: false
endpoint: http://my-litellm.my-namespace.svc:80 # optional; defaults to today's formula
readinessRef: # optional; defaults to metadata.name
kind: Deployment
name: my-litellm
That would also allow attaching to a proxy that isn't a Deployment — a StatefulSet, something outside the cluster, or a Service fronting either.
Alternatives Considered
No response
Additional Context
No response
Problem
Support attaching a LiteLLMInstance to an existing, externally-managed deployment
Ask
An opt-out that lets a
LiteLLMInstancedescribe a LiteLLM deployment the operator did not create, so the entity CRDs can be used against an existing installation without handing the operator the workload.We have this working today by blocking workload reconciliation with RBAC. It works well enough to be worth supporting properly — and badly enough to be adopted as a proper feature!
Use case
A LiteLLM proxy already exists and is managed by something else — a Helm chart, a GitOps pipeline, an internal platform, whatever. What's wanted from the operator is the entity CRDs:
LiteLLMTeam,LiteLLMVirtualKey,LiteLLMBudget,LiteLLMModel. Declarative teams and keys reconciled from Kubernetes beats scripting the admin API.What isn't wanted is for the operator to own the Deployment, as it already exists.
Today, the entity CRDs are unreachable, even though nothing about them depends on the operator owning a Deployment.
Observation
The entity controllers' entire dependency on the instance is
resolveInstance(internal/controller/common.go), which needs three things:status.readystatus.endpointspec.masterKey.secretRefBoth status fields are derived from the instance name, not from anything the operator created:
The
Getcarries no ownership check and the endpoint is a formula rather than a lookup. So aLiteLLMInstancenamed after an existing Deployment and Service already resolves correctly.The only thing in the way is that
reconcileResourcesruns first and adopts those objects by name —reconcileDeploymentandreconcileServicebothGet-then-overwrite rather than declining to touch a resource they didn't create.What we validated
We have got this working with an existing deployment, by using RBAC to prevent the operator from being able to deploy the instance. We specified the CR with the existing deployment's name, port, key etc, and the workload reconciliation was blocked by RBAC, but other than logging the failure proceeded to then work fine:
status.ready: trueandstatus.endpointresolved against the existing deploymentLiteLLMTeamreachedSyncedwith a real team ID from the admin APISo the operator is entirely capable of this. It just has no way to be told to do it.
It also reports failure permanently:
reconcileResourcesfails on every loop, so the instance sitsDegradedand logs aForbiddenper resource forever.status.readyistruewhile theReadycondition isFalsewith aReconcileError. This doesn't stop it working, it just doesn't look nice, especially to someone unfamiliar with this workaround.Proposed Solution
Proposal
When
managed: false, skip workload reconciliation and keep everything else:updateInstanceStatusstill populatesreadyandendpoint, health probing still runs, and config sync is a separate controller making API calls, so none of it is affected. Status would describe the instance honestly instead of reportingDegradedabout resources it doesn't manage.Worth considering alongside
Resolving the endpoint from the instance name works, but it makes correctness depend on a naming coincidence — the CR has to be named after a Deployment and Service it doesn't own. Extend to specify the endpoint fully:
That would also allow attaching to a proxy that isn't a Deployment — a StatefulSet, something outside the cluster, or a Service fronting either.
Alternatives Considered
No response
Additional Context
No response