mirror of
https://github.com/hanzo-ml/kserve.git
synced 2026-10-30 20:53:49 +00:00
619 lines
23 KiB
Go
619 lines
23 KiB
Go
/*
|
|
Copyright 2025 The KServe Authors.
|
|
|
|
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 llmisvc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
|
|
"github.com/kserve/kserve/pkg/apis/serving/v1alpha1"
|
|
"github.com/kserve/kserve/pkg/apis/serving/v1alpha2"
|
|
"github.com/kserve/kserve/pkg/constants"
|
|
"github.com/kserve/kserve/pkg/credentials"
|
|
"github.com/kserve/kserve/pkg/credentials/s3"
|
|
kserveTypes "github.com/kserve/kserve/pkg/types"
|
|
"github.com/kserve/kserve/pkg/utils"
|
|
)
|
|
|
|
const CaBundleVolumeName = "cabundle-cert"
|
|
|
|
// storageDownloadPair is one uri→path pair for the storage-initializer (multi-arg entrypoint).
|
|
type storageDownloadPair struct {
|
|
uri string
|
|
path string
|
|
}
|
|
|
|
var tokenizerOnlyDownload = corev1.EnvVar{
|
|
Name: "STORAGE_ALLOW_PATTERNS",
|
|
Value: `["tokenizer.json", "tokenizer_config.json", "special_tokens_map.json", "vocab.json", "merges.txt", "config.json", "generation_config.json"]`,
|
|
}
|
|
|
|
// stripPriorControllerStorageInitializer removes the storage-initializer init container that would
|
|
// duplicate the one the controller is about to add: merged/user templates often already
|
|
// define "storage-initializer".
|
|
func stripPriorControllerStorageInitializer(podSpec *corev1.PodSpec) {
|
|
if podSpec == nil {
|
|
return
|
|
}
|
|
keptInit := podSpec.InitContainers[:0]
|
|
for _, ic := range podSpec.InitContainers {
|
|
if ic.Name == constants.StorageInitializerContainerName {
|
|
continue
|
|
}
|
|
keptInit = append(keptInit, ic)
|
|
}
|
|
podSpec.InitContainers = keptInit
|
|
}
|
|
|
|
// attachModelArtifacts configures a PodSpec to fetch and use a model from a provided URI in the LLMInferenceService.
|
|
// The storage backend (PVC, OCI, Hugging Face, or S3) is determined from the URI schema and the appropriate helper function
|
|
// is called to configure the PodSpec. This function will adjust volumes, container arguments, container volume mounts,
|
|
// add containers, and do other changes to the PodSpec to ensure the model is fetched properly from storage.
|
|
//
|
|
// Parameters:
|
|
// - ctx: The context for API calls and logging.
|
|
// - serviceAccount: service account associated with the LLMInferenceService.
|
|
// - llmSvc: The LLMInferenceService resource containing the model specification.
|
|
// - podSpec: The PodSpec to configure with the model artifact.
|
|
// - config: The configuration information for LLMInferenceServices.
|
|
//
|
|
// Returns:
|
|
//
|
|
// An error if the configuration fails, otherwise nil.
|
|
func (r *LLMISVCReconciler) attachModelArtifacts(ctx context.Context, serviceAccount *corev1.ServiceAccount, llmSvc *v1alpha2.LLMInferenceService, curr corev1.PodSpec, podSpec *corev1.PodSpec, config *Config, containerName string, modelPath string, attachLoRA bool) error {
|
|
modelUri := llmSvc.Spec.Model.URI.String()
|
|
schema, _, sepFound := strings.Cut(modelUri, "://")
|
|
|
|
if !sepFound {
|
|
return fmt.Errorf("invalid model URI: %s", modelUri)
|
|
}
|
|
|
|
// Check if storage-initializer is explicitly disabled
|
|
storageInitializerDisabled := llmSvc.Spec.StorageInitializer != nil &&
|
|
llmSvc.Spec.StorageInitializer.Enabled != nil &&
|
|
!*llmSvc.Spec.StorageInitializer.Enabled
|
|
if storageInitializerDisabled {
|
|
// Skip storage-initializer when explicitly disabled
|
|
return nil
|
|
}
|
|
|
|
// Rewrite model URI to use cached PVC when local model cache is active
|
|
if _, ok := llmSvc.Labels[constants.LocalModelLabel]; ok {
|
|
sourceUri, ok := llmSvc.Annotations[constants.LocalModelSourceUriAnnotationKey]
|
|
if !ok {
|
|
return fmt.Errorf("LLMInferenceService %s/%s: annotation %s not found", llmSvc.Namespace, llmSvc.Name, constants.LocalModelSourceUriAnnotationKey)
|
|
}
|
|
pvcName, ok := llmSvc.Annotations[constants.LocalModelPVCNameAnnotationKey]
|
|
if !ok {
|
|
return fmt.Errorf("LLMInferenceService %s/%s: annotation %s not found", llmSvc.Namespace, llmSvc.Name, constants.LocalModelPVCNameAnnotationKey)
|
|
}
|
|
subPath, _ := strings.CutPrefix(modelUri, sourceUri)
|
|
if !strings.HasPrefix(subPath, "/") {
|
|
subPath = "/" + subPath
|
|
}
|
|
storageKey := v1alpha1.GetStorageKey(sourceUri)
|
|
modelUri = "pvc://" + pvcName + "/models/" + storageKey + subPath
|
|
schema = "pvc"
|
|
}
|
|
|
|
var loraPairs []storageDownloadPair
|
|
if attachLoRA {
|
|
loraPairs = collectLoRADownloadPairs(config.ResolvedLoRAAdapters)
|
|
}
|
|
|
|
// Handle model artifact downloads based on URI scheme
|
|
switch schema + "://" {
|
|
case constants.PvcURIPrefix:
|
|
if err := r.attachPVCModelArtifact(modelUri, podSpec, containerName, modelPath); err != nil {
|
|
return err
|
|
}
|
|
if len(loraPairs) > 0 {
|
|
if err := r.attachMultiStorageDownloads(ctx, serviceAccount, llmSvc, curr, podSpec, config.StorageConfig, config.CredentialConfig, containerName, loraPairs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
case constants.OciURIPrefix:
|
|
// Check of OCI is enabled
|
|
if !config.StorageConfig.EnableOciImageSource {
|
|
return errors.New("OCI modelcars is not enabled")
|
|
}
|
|
|
|
if err := r.attachOciModelArtifact(modelUri, podSpec, config.StorageConfig, containerName, modelPath); err != nil {
|
|
return err
|
|
}
|
|
if len(loraPairs) > 0 {
|
|
if err := r.attachMultiStorageDownloads(ctx, serviceAccount, llmSvc, curr, podSpec, config.StorageConfig, config.CredentialConfig, containerName, loraPairs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
case constants.HfURIPrefix:
|
|
if len(loraPairs) == 0 {
|
|
if err := r.attachHfModelArtifact(ctx, serviceAccount, llmSvc, modelUri, curr, podSpec, config.StorageConfig, config.CredentialConfig, containerName, modelPath); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
pairs := append([]storageDownloadPair{{uri: modelUri, path: constants.DefaultModelLocalMountPath}}, loraPairs...)
|
|
if err := r.attachMultiStorageDownloads(ctx, serviceAccount, llmSvc, curr, podSpec, config.StorageConfig, config.CredentialConfig, containerName, pairs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
case constants.S3URIPrefix:
|
|
if len(loraPairs) == 0 {
|
|
if err := r.attachS3ModelArtifact(ctx, serviceAccount, llmSvc, modelUri, curr, podSpec, config.StorageConfig, config.CredentialConfig, containerName, modelPath); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
pairs := append([]storageDownloadPair{{uri: modelUri, path: constants.DefaultModelLocalMountPath}}, loraPairs...)
|
|
if err := r.attachMultiStorageDownloads(ctx, serviceAccount, llmSvc, curr, podSpec, config.StorageConfig, config.CredentialConfig, containerName, pairs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("unsupported schema in model URI: %s", modelUri)
|
|
}
|
|
|
|
// Attach LoRA adapters (PVC mounts + vLLM flag injection) after model downloads
|
|
if attachLoRA {
|
|
if err := r.attachLoRAAdapters(ctx, llmSvc, podSpec, config.ResolvedLoRAAdapters); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// attachOciModelArtifact configures a PodSpec to use a model stored in an OCI registry.
|
|
// It updates the "main" container in the PodSpec to use the model from OCI image. The
|
|
// required supporting volumes and volume mounts are added to the PodSpec.
|
|
//
|
|
// Parameters:
|
|
// - modelUri: The URI of the model in the OCI registry.
|
|
// - podSpec: The PodSpec to which the OCI model should be attached.
|
|
// - storageConfig: The storage initializer configuration.
|
|
//
|
|
// Returns:
|
|
//
|
|
// An error if the configuration fails, otherwise nil.
|
|
func (r *LLMISVCReconciler) attachOciModelArtifact(modelUri string, podSpec *corev1.PodSpec, storageConfig *kserveTypes.StorageInitializerConfig, containerName string, modelPath string) error {
|
|
if err := utils.ConfigureModelcarToContainer(modelUri, podSpec, containerName, modelPath, storageConfig, 0); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// attachPVCModelArtifact mounts a model artifact from a PersistentVolumeClaim (PVC) to the specified PodSpec.
|
|
// It adds the PVC as a volume and mounts it to the `main` container. The mount path is added to the arguments of the
|
|
// `main` container, assuming the model server expects a positional argument indicating the location of the model (which is the case of vLLM)
|
|
//
|
|
// Parameters:
|
|
// - modelUri: The URI of the model, expected to have a PVC prefix.
|
|
// - podSpec: The PodSpec to which the PVC volume and mount should be attached.
|
|
//
|
|
// Returns:
|
|
//
|
|
// An error if attaching the PVC model artifact fails, otherwise nil.
|
|
//
|
|
// TODO: For now, this supports only direct mount. Copying from PVC would come later (if it makes sense at all).
|
|
func (r *LLMISVCReconciler) attachPVCModelArtifact(modelUri string, podSpec *corev1.PodSpec, containerName string, modelPath string) error {
|
|
pvcName, pvcPath, err := utils.ParsePvcURI(modelUri)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
storageMountParams := utils.StorageMountParams{
|
|
MountPath: modelPath,
|
|
VolumeName: constants.PvcSourceMountName,
|
|
ReadOnly: true,
|
|
PVCName: pvcName,
|
|
SubPath: pvcPath,
|
|
}
|
|
|
|
if err := utils.AddModelMount(storageMountParams, containerName, podSpec); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// attachS3ModelArtifact configures a PodSpec to use a model stored in an S3-compatible object store.
|
|
// Model downloading is delegated to vLLM by passing the S3 URI and other required arguments.
|
|
//
|
|
// Parameters:
|
|
// - ctx: The context for API calls and logging.
|
|
// - serviceAccount: service account associated with the LLMInferenceService.
|
|
// - llmSvc: The LLMInferenceService resource containing the model specification.
|
|
// - modelUri: The URI of the model in the S3-compatible object store.
|
|
// - podSpec: The PodSpec to which the S3 model should be attached.
|
|
// - storageConfig: The storage initializer configuration.
|
|
// - credentialConfig: The credential configuration used for model downloads.
|
|
//
|
|
// Returns:
|
|
//
|
|
// An error if the configuration fails, otherwise nil.
|
|
func (r *LLMISVCReconciler) attachS3ModelArtifact(ctx context.Context, serviceAccount *corev1.ServiceAccount, llmSvc *v1alpha2.LLMInferenceService, modelUri string, curr corev1.PodSpec, podSpec *corev1.PodSpec, storageConfig *kserveTypes.StorageInitializerConfig, credentialConfig *credentials.CredentialConfig, containerName string, modelPath string) error {
|
|
if err := r.attachStorageInitializer(modelUri, curr, podSpec, storageConfig, llmSvc.Spec.Model.Confidential, containerName, modelPath); err != nil {
|
|
return err
|
|
}
|
|
if initContainer := utils.GetInitContainerWithName(podSpec, constants.StorageInitializerContainerName); initContainer != nil {
|
|
// If service account is nil, fetch the default service account
|
|
if serviceAccount == nil {
|
|
serviceAccount = &corev1.ServiceAccount{}
|
|
err := r.Get(ctx, types.NamespacedName{Name: constants.LLMISVCDefaultServiceAccountName, Namespace: llmSvc.Namespace}, serviceAccount)
|
|
if err != nil {
|
|
log.FromContext(ctx).Error(err, "Failed to find default service account", "namespace", llmSvc.Namespace)
|
|
injectCaBundle(llmSvc.Namespace, podSpec, initContainer, storageConfig)
|
|
return nil
|
|
}
|
|
}
|
|
// Check for AWS IAM Role for Service Account or AWS IAM User Credentials
|
|
credentialBuilder := credentials.NewCredentialBuilderFromConfig(r.Client, r.Clientset, *credentialConfig)
|
|
if err := credentialBuilder.CreateSecretVolumeAndEnvFromServiceAccount(
|
|
ctx,
|
|
serviceAccount,
|
|
llmSvc.Annotations,
|
|
initContainer,
|
|
&podSpec.Volumes,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
injectCaBundle(llmSvc.Namespace, podSpec, initContainer, storageConfig)
|
|
|
|
if containerName == tokenizerContainerName {
|
|
utils.AddEnvVars(initContainer, []corev1.EnvVar{*tokenizerOnlyDownload.DeepCopy()})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// attachHfModelArtifact configures a PodSpec to use a model stored in the hugging face hub.
|
|
// Model downloading is delegated to vLLM by passing the HF URI and other required arguments.
|
|
//
|
|
// Parameters:
|
|
// - ctx: The context for API calls and logging.
|
|
// - serviceAccount: service account associated with the LLMInferenceService.
|
|
// - llmSvc: The LLMInferenceService resource containing the model specification.
|
|
// - modelUri: The URI of the model in the S3-compatible object store.
|
|
// - podSpec: The PodSpec to which the S3 model should be attached.
|
|
// - storageConfig: The storage initializer configuration.
|
|
// - credentialConfig: The credential configuration used for model downloads.
|
|
//
|
|
// Returns:
|
|
//
|
|
// An error if the configuration fails, otherwise nil.
|
|
func (r *LLMISVCReconciler) attachHfModelArtifact(ctx context.Context, serviceAccount *corev1.ServiceAccount, llmSvc *v1alpha2.LLMInferenceService, modelUri string, curr corev1.PodSpec, podSpec *corev1.PodSpec, storageConfig *kserveTypes.StorageInitializerConfig, credentialConfig *credentials.CredentialConfig, containerName string, modelPath string) error {
|
|
if err := r.attachStorageInitializer(modelUri, curr, podSpec, storageConfig, llmSvc.Spec.Model.Confidential, containerName, modelPath); err != nil {
|
|
return err
|
|
}
|
|
if initContainer := utils.GetInitContainerWithName(podSpec, constants.StorageInitializerContainerName); initContainer != nil {
|
|
// If service account is nil, fetch the default service account
|
|
if serviceAccount == nil {
|
|
serviceAccount = &corev1.ServiceAccount{}
|
|
err := r.Get(ctx, types.NamespacedName{Name: constants.LLMISVCDefaultServiceAccountName, Namespace: llmSvc.Namespace}, serviceAccount)
|
|
if err != nil {
|
|
log.FromContext(ctx).Error(err, "Failed to find default service account", "namespace", llmSvc.Namespace)
|
|
return nil
|
|
}
|
|
}
|
|
// Check for service account with secret ref
|
|
credentialBuilder := credentials.NewCredentialBuilderFromConfig(r.Client, r.Clientset, *credentialConfig)
|
|
if err := credentialBuilder.CreateSecretVolumeAndEnvFromServiceAccount(
|
|
ctx,
|
|
serviceAccount,
|
|
llmSvc.Annotations,
|
|
initContainer,
|
|
&podSpec.Volumes,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
currentInitContainer := utils.GetInitContainerWithName(&curr, constants.StorageInitializerContainerName)
|
|
|
|
// Add HF default env vars when the init container is new or already has HF_ env vars
|
|
// from a previous reconciliation. Skip only when upgrading from an older operator version
|
|
// that didn't set these vars, to avoid unnecessary pod restarts.
|
|
if currentInitContainer == nil || slices.ContainsFunc(currentInitContainer.Env, func(e corev1.EnvVar) bool {
|
|
return strings.HasPrefix(e.Name, "HF_")
|
|
}) {
|
|
utils.AddDefaultHuggingFaceEnvVars(initContainer)
|
|
}
|
|
|
|
if containerName == tokenizerContainerName {
|
|
utils.AddEnvVars(initContainer, []corev1.EnvVar{*tokenizerOnlyDownload.DeepCopy()})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// attachStorageInitializer configures a PodSpec to use KServe storage-initializer for
|
|
// downloading a model from compatible storage.
|
|
//
|
|
// Parameters:
|
|
// - modelUri: The URI of the model in compatible object store.
|
|
// - podSpec: The PodSpec to which the storage-initializer container should be attached.
|
|
// - storageConfig: The storage initializer configuration.
|
|
//
|
|
// Returns:
|
|
//
|
|
// An error if the configuration fails, otherwise nil.
|
|
func (r *LLMISVCReconciler) attachStorageInitializer(modelUri string, curr corev1.PodSpec, podSpec *corev1.PodSpec, storageConfig *kserveTypes.StorageInitializerConfig, confidential *v1alpha2.ConfidentialSpec, containerName string, modelPath string) error {
|
|
stripPriorControllerStorageInitializer(podSpec)
|
|
|
|
containerArgs := []string{
|
|
modelUri,
|
|
constants.DefaultModelLocalMountPath,
|
|
}
|
|
storageMountParams := utils.StorageMountParams{
|
|
MountPath: constants.DefaultModelLocalMountPath,
|
|
VolumeName: constants.StorageInitializerVolumeName,
|
|
ReadOnly: false,
|
|
}
|
|
|
|
copied := *storageConfig
|
|
|
|
// Preserve the existing storage-initializer image from the current deployment
|
|
// to avoid unnecessary pod restarts during operator upgrades when the model
|
|
// hasn't changed.
|
|
for _, initContainer := range curr.InitContainers {
|
|
if initContainer.Name == constants.StorageInitializerContainerName {
|
|
copied.Image = initContainer.Image
|
|
}
|
|
}
|
|
|
|
initContainer := utils.CreateInitContainerWithConfig(&copied, containerArgs)
|
|
|
|
// Inject confidential env vars before appending to the pod spec
|
|
if confidential != nil && confidential.Enabled {
|
|
resourceId := ""
|
|
if confidential.ResourceId != nil {
|
|
resourceId = *confidential.ResourceId
|
|
}
|
|
utils.ApplyConfidentialContainerConfig(initContainer, resourceId)
|
|
}
|
|
|
|
podSpec.InitContainers = append(podSpec.InitContainers, *initContainer)
|
|
|
|
if err := utils.AddModelMount(storageMountParams, initContainer.Name, podSpec); err != nil {
|
|
return err
|
|
}
|
|
|
|
storageMountParams.ReadOnly = true
|
|
storageMountParams.MountPath = modelPath
|
|
if err := utils.AddModelMount(storageMountParams, containerName, podSpec); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// attachMultiStorageDownloads adds one storage-initializer init container with multiple
|
|
// src_uri dest_path pairs (see storage-initializer entrypoint) and mounts the shared
|
|
// emptyDir at the common parent of all destination paths.
|
|
func (r *LLMISVCReconciler) attachMultiStorageDownloads(
|
|
ctx context.Context,
|
|
serviceAccount *corev1.ServiceAccount,
|
|
llmSvc *v1alpha2.LLMInferenceService,
|
|
curr corev1.PodSpec,
|
|
podSpec *corev1.PodSpec,
|
|
storageConfig *kserveTypes.StorageInitializerConfig,
|
|
credentialConfig *credentials.CredentialConfig,
|
|
containerName string,
|
|
pairs []storageDownloadPair,
|
|
) error {
|
|
if len(pairs) == 0 {
|
|
return nil
|
|
}
|
|
stripPriorControllerStorageInitializer(podSpec)
|
|
|
|
paths := make([]string, len(pairs))
|
|
for i, p := range pairs {
|
|
paths[i] = p.path
|
|
}
|
|
parent := utils.FindCommonParentPath(paths)
|
|
if parent == "" {
|
|
parent = "/"
|
|
}
|
|
|
|
args := make([]string, 0, len(pairs)*2)
|
|
for _, p := range pairs {
|
|
args = append(args, p.uri, p.path)
|
|
}
|
|
|
|
copied := *storageConfig
|
|
for _, ic := range curr.InitContainers {
|
|
if ic.Name == constants.StorageInitializerContainerName {
|
|
copied.Image = ic.Image
|
|
break
|
|
}
|
|
}
|
|
|
|
initC := utils.CreateInitContainerWithConfig(&copied, args)
|
|
podSpec.InitContainers = append(podSpec.InitContainers, *initC)
|
|
iname := initC.Name
|
|
|
|
if err := utils.AddModelMount(utils.StorageMountParams{
|
|
MountPath: parent,
|
|
VolumeName: constants.StorageInitializerVolumeName,
|
|
ReadOnly: false,
|
|
}, iname, podSpec); err != nil {
|
|
return err
|
|
}
|
|
if err := utils.AddModelMount(utils.StorageMountParams{
|
|
MountPath: parent,
|
|
VolumeName: constants.StorageInitializerVolumeName,
|
|
ReadOnly: true,
|
|
}, containerName, podSpec); err != nil {
|
|
return err
|
|
}
|
|
|
|
initPtr := utils.GetInitContainerWithName(podSpec, constants.StorageInitializerContainerName)
|
|
if initPtr == nil {
|
|
return errors.New("storage-initializer init container not found after attachMultiStorageDownloads")
|
|
}
|
|
|
|
if serviceAccount == nil {
|
|
serviceAccount = &corev1.ServiceAccount{}
|
|
err := r.Get(ctx, types.NamespacedName{Name: constants.LLMISVCDefaultServiceAccountName, Namespace: llmSvc.Namespace}, serviceAccount)
|
|
if err != nil {
|
|
log.FromContext(ctx).Error(err, "Failed to find default service account", "namespace", llmSvc.Namespace)
|
|
injectCaBundle(llmSvc.Namespace, podSpec, initPtr, storageConfig)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
credentialBuilder := credentials.NewCredentialBuilderFromConfig(r.Client, r.Clientset, *credentialConfig)
|
|
if err := credentialBuilder.CreateSecretVolumeAndEnvFromServiceAccount(
|
|
ctx,
|
|
serviceAccount,
|
|
llmSvc.Annotations,
|
|
initPtr,
|
|
&podSpec.Volumes,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
needHF := slices.ContainsFunc(pairs, func(p storageDownloadPair) bool {
|
|
return strings.HasPrefix(p.uri, constants.HfURIPrefix)
|
|
})
|
|
currentInit := utils.GetInitContainerWithName(&curr, constants.StorageInitializerContainerName)
|
|
if needHF && (currentInit == nil || slices.ContainsFunc(currentInit.Env, func(e corev1.EnvVar) bool {
|
|
return strings.HasPrefix(e.Name, "HF_")
|
|
})) {
|
|
utils.AddDefaultHuggingFaceEnvVars(initPtr)
|
|
}
|
|
|
|
if containerName == tokenizerContainerName {
|
|
utils.AddEnvVars(initPtr, []corev1.EnvVar{*tokenizerOnlyDownload.DeepCopy()})
|
|
}
|
|
|
|
injectCaBundle(llmSvc.Namespace, podSpec, initPtr, storageConfig)
|
|
return nil
|
|
}
|
|
|
|
// caBundleConfig holds the configuration for CA bundle injection
|
|
type caBundleConfig struct {
|
|
configMapName string
|
|
volumeMountPath string
|
|
envVarExists bool
|
|
mountPathExists bool
|
|
}
|
|
|
|
// extractCaBundleConfig extracts and processes CA bundle configuration from environment variables in a single pass
|
|
func extractCaBundleConfig(initContainer *corev1.Container, storageConfig *kserveTypes.StorageInitializerConfig, namespace string) *caBundleConfig {
|
|
config := &caBundleConfig{
|
|
configMapName: storageConfig.CaBundleConfigMapName,
|
|
volumeMountPath: storageConfig.CaBundleVolumeMountPath,
|
|
}
|
|
|
|
// Set defaults
|
|
if namespace != constants.KServeNamespace {
|
|
config.configMapName = constants.DefaultGlobalCaBundleConfigMapName
|
|
}
|
|
if config.volumeMountPath == "" {
|
|
config.volumeMountPath = constants.DefaultCaBundleVolumeMountPath
|
|
}
|
|
|
|
// Single pass through environment variables to extract values and check existence
|
|
for _, envVar := range initContainer.Env {
|
|
switch envVar.Name {
|
|
case s3.AWSCABundleConfigMap:
|
|
config.configMapName = envVar.Value
|
|
case s3.AWSCABundle:
|
|
config.volumeMountPath = filepath.Dir(envVar.Value)
|
|
case constants.CaBundleConfigMapNameEnvVarKey:
|
|
config.envVarExists = true
|
|
case constants.CaBundleVolumeMountPathEnvVarKey:
|
|
config.mountPathExists = true
|
|
}
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
func injectCaBundle(namespace string, podSpec *corev1.PodSpec, initContainer *corev1.Container, storageConfig *kserveTypes.StorageInitializerConfig) bool { //nolint:unparam
|
|
// Inject CA bundle configMap if caBundleConfigMapName or constants.DefaultGlobalCaBundleConfigMapName annotation is set
|
|
if !needCaBundleMount(storageConfig.CaBundleConfigMapName, initContainer) {
|
|
return false
|
|
}
|
|
|
|
config := extractCaBundleConfig(initContainer, storageConfig, namespace)
|
|
|
|
// Add CA bundle env vars only if they don't already exist (could be customized by user)
|
|
if !config.envVarExists {
|
|
initContainer.Env = append(initContainer.Env, corev1.EnvVar{
|
|
Name: constants.CaBundleConfigMapNameEnvVarKey,
|
|
Value: config.configMapName,
|
|
})
|
|
}
|
|
|
|
if !config.mountPathExists {
|
|
initContainer.Env = append(initContainer.Env, corev1.EnvVar{
|
|
Name: constants.CaBundleVolumeMountPathEnvVarKey,
|
|
Value: config.volumeMountPath,
|
|
})
|
|
}
|
|
|
|
caBundleVolume := corev1.Volume{
|
|
Name: CaBundleVolumeName,
|
|
VolumeSource: corev1.VolumeSource{
|
|
ConfigMap: &corev1.ConfigMapVolumeSource{
|
|
LocalObjectReference: corev1.LocalObjectReference{
|
|
Name: config.configMapName,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
caBundleVolumeMount := corev1.VolumeMount{
|
|
Name: CaBundleVolumeName,
|
|
MountPath: config.volumeMountPath,
|
|
ReadOnly: true,
|
|
}
|
|
|
|
podSpec.Volumes = append(podSpec.Volumes, caBundleVolume)
|
|
initContainer.VolumeMounts = append(initContainer.VolumeMounts, caBundleVolumeMount)
|
|
|
|
return true
|
|
}
|
|
|
|
func needCaBundleMount(caBundleConfigMapName string, initContainer *corev1.Container) bool {
|
|
result := caBundleConfigMapName != ""
|
|
|
|
for _, envVar := range initContainer.Env {
|
|
if envVar.Name == s3.AWSCABundleConfigMap {
|
|
result = true
|
|
break
|
|
}
|
|
}
|
|
return result
|
|
}
|