Files
2026-07-13 12:24:33 +08:00

301 lines
9.4 KiB
Go

/*
Copyright 2026.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resources
import (
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
lmcachev1alpha1 "github.com/LMCache/LMCache/api/v1alpha1"
)
const (
// nvidiaRuntimeClass is the RuntimeClass name registered by the NVIDIA GPU
// Operator; engine pods request it when gpuVendor is nvidia.
nvidiaRuntimeClass = "nvidia"
// lmcacheServerBinary is the entrypoint binary for the LMCache server inside
// the engine image.
lmcacheServerBinary = "/opt/venv/bin/lmcache"
// serverSubcommand is the `lmcache server` subcommand that starts the engine.
serverSubcommand = "server"
// serverPortName is the name of the engine's serving port on the container
// and the node-local Service.
serverPortName = "server"
)
// BuildDaemonSet constructs a DaemonSet for the given LMCacheEngine.
func BuildDaemonSet(engine *lmcachev1alpha1.LMCacheEngine) *appsv1.DaemonSet {
return buildDaemonSetCore(engine.Name, engine.Namespace, &engine.Spec, BuildContainerArgs(&engine.Spec), "lmcache/vllm-openai")
}
// buildDaemonSetCore constructs the DaemonSet shared by the LMCacheEngine and
// CacheBlendEngine controllers. It is the single source of truth for the
// GPU/security pod-template scaffolding (hostIPC, runtimeClassName, optional
// privileged (default false, via spec.Privileged), NVIDIA_VISIBLE_DEVICES,
// resources without a device-plugin GPU claim) so those settings cannot drift
// between the two engines.
//
// Parameters:
// - name, namespace: the owning object's identity, used for labels and metadata.
// - spec: the engine spec (LMCacheEngine and CacheBlendEngine reuse the same
// shared sub-structs, so callers project the CacheBlendEngine spec into an
// *LMCacheEngineSpec before calling).
// - containerArgs: the fully serialized server CLI args (callers append any
// engine-specific flags such as --engine-type before passing them in).
// - defaultImageRepo: the container image repository to use when spec.Image
// does not set one.
func buildDaemonSetCore(
name, namespace string,
spec *lmcachev1alpha1.LMCacheEngineSpec,
containerArgs []string,
defaultImageRepo string,
) *appsv1.DaemonSet {
selectorLabels := SelectorLabels(name)
podLabels := MergeLabels(StandardLabels(name), spec.PodLabels)
podAnnotations := spec.PodAnnotations
gpuVendor := derefString(spec.GPUVendor, lmcachev1alpha1.GPUVendorNvidia)
var runtimeClassName *string
if gpuVendor == lmcachev1alpha1.GPUVendorNvidia {
rc := nvidiaRuntimeClass
runtimeClassName = &rc
}
privileged := derefBool(spec.Privileged, false)
serverPort := derefInt32(getServerPort(spec), 5555)
imgRepo := defaultImageRepo
imgTag := "latest"
imgPullPolicy := corev1.PullIfNotPresent
if spec.Image != nil {
imgRepo = derefString(spec.Image.Repository, imgRepo)
imgTag = derefString(spec.Image.Tag, imgTag)
switch derefString(spec.Image.PullPolicy, "IfNotPresent") {
case "Always":
imgPullPolicy = corev1.PullAlways
case "Never":
imgPullPolicy = corev1.PullNever
default:
imgPullPolicy = corev1.PullIfNotPresent
}
}
// Build env vars
envVars := make([]corev1.EnvVar, 0, 5+len(spec.Env))
envVars = append(envVars,
corev1.EnvVar{
Name: "LMCACHE_LOG_LEVEL",
Value: derefString(spec.LogLevel, "INFO"),
},
)
if gpuVendor == lmcachev1alpha1.GPUVendorNvidia {
// Expose all GPUs without consuming device plugin resources.
// LMCache needs GPU visibility for CUDA IPC, not compute ownership.
envVars = append(envVars,
corev1.EnvVar{
Name: "NVIDIA_VISIBLE_DEVICES",
Value: "all",
},
corev1.EnvVar{
Name: "NVIDIA_DRIVER_CAPABILITIES",
Value: "all",
},
)
}
// Inject RESP auth credentials from Secret as env vars so they
// don't appear in container args or kubectl describe output.
// The DaemonSet references the local (same-namespace) managed copy
// created by the controller via reconcileRESPAuthSecret.
if spec.L2Backend != nil && spec.L2Backend.RESP != nil && spec.L2Backend.RESP.AuthSecretRef != nil {
secretName := RESPAuthSecretName(name)
optional := true
envVars = append(envVars,
corev1.EnvVar{
Name: "LMCACHE_RESP_PASSWORD",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: secretName},
Key: "password",
},
},
},
corev1.EnvVar{
// Optional: if the managed secret has no "username" key
// (password-only auth), this env var is simply not set.
Name: "LMCACHE_RESP_USERNAME",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: secretName},
Key: "username",
Optional: &optional,
},
},
},
)
}
// Inject the pod IP as the coordinator advertise address (downward API)
// when registration is enabled and no explicit advertiseIP is set, so the
// coordinator can reach this server. The server's --coordinator-advertise-ip
// flag falls back to this env var.
if spec.Coordinator != nil && derefString(spec.Coordinator.URL, "") != "" &&
(spec.Coordinator.AdvertiseIP == nil || *spec.Coordinator.AdvertiseIP == "") {
envVars = append(envVars, corev1.EnvVar{
Name: "LMCACHE_COORDINATOR_ADVERTISE_IP",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "status.podIP",
},
},
})
}
envVars = append(envVars, spec.Env...)
// No emptyDir /dev/shm mount — hostIPC: true exposes the host's /dev/shm
// directly. An emptyDir mount would shadow it and break CUDA IPC between
// LMCache and vLLM pods (cudaIpcOpenMemHandle requires shared /dev/shm).
volumes := append([]corev1.Volume{}, spec.Volumes...)
volumeMounts := append([]corev1.VolumeMount{}, spec.VolumeMounts...)
// Build container args. Auth credentials are handled via env vars
// (LMCACHE_RESP_USERNAME / LMCACHE_RESP_PASSWORD) injected above,
// so no shell wrapper is needed.
containerCommand := []string{
lmcacheServerBinary,
serverSubcommand,
}
// Probes
tcpProbe := &corev1.TCPSocketAction{
Port: intstr.FromInt32(serverPort),
}
startupProbe := &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
TCPSocket: tcpProbe,
},
InitialDelaySeconds: 5,
PeriodSeconds: 5,
FailureThreshold: 30,
}
livenessProbe := &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
TCPSocket: tcpProbe,
},
PeriodSeconds: 10,
}
readinessProbe := &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
TCPSocket: tcpProbe,
},
PeriodSeconds: 5,
}
// Container ports
httpPort := getHTTPPort(spec)
containerPorts := []corev1.ContainerPort{
{
Name: serverPortName,
ContainerPort: serverPort,
Protocol: corev1.ProtocolTCP,
},
{
Name: "http",
ContainerPort: httpPort,
Protocol: corev1.ProtocolTCP,
},
}
// Add metrics port if prometheus is enabled
promEnabled := true
promPort := int32(9090)
if spec.Prometheus != nil {
promEnabled = derefBool(spec.Prometheus.Enabled, true)
promPort = derefInt32(spec.Prometheus.Port, 9090)
}
if promEnabled {
containerPorts = append(containerPorts, corev1.ContainerPort{
Name: "metrics",
ContainerPort: promPort,
Protocol: corev1.ProtocolTCP,
})
}
ds := &appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: StandardLabels(name),
},
Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: selectorLabels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: podLabels,
Annotations: podAnnotations,
},
Spec: corev1.PodSpec{
HostIPC: true,
HostNetwork: derefBool(spec.HostNetwork, false),
RuntimeClassName: runtimeClassName,
ServiceAccountName: spec.ServiceAccountName,
PriorityClassName: spec.PriorityClassName,
NodeSelector: spec.NodeSelector,
Affinity: spec.Affinity,
Tolerations: spec.Tolerations,
ImagePullSecrets: spec.ImagePullSecrets,
Containers: []corev1.Container{
{
Name: "lmcache",
Image: fmt.Sprintf("%s:%s", imgRepo, imgTag),
ImagePullPolicy: imgPullPolicy,
Command: containerCommand,
Args: containerArgs,
Ports: containerPorts,
Env: envVars,
Resources: ComputeResources(spec),
SecurityContext: &corev1.SecurityContext{
Privileged: &privileged,
},
VolumeMounts: volumeMounts,
StartupProbe: startupProbe,
LivenessProbe: livenessProbe,
ReadinessProbe: readinessProbe,
},
},
Volumes: volumes,
},
},
},
}
if derefBool(spec.HostNetwork, false) {
ds.Spec.Template.Spec.DNSPolicy = corev1.DNSClusterFirstWithHostNet
}
return ds
}