diff --git a/OWNERS b/OWNERS index 6bc58b1b309..5379ac7c5e5 100644 --- a/OWNERS +++ b/OWNERS @@ -1,6 +1,14 @@ approvers: - - etirelli - - VaishnaviHire + - platform reviewers: - - etirelli - - VaishnaviHire + - platform + - serving + - ide + - dashboard + - datasciencepipelines + - modelregistry + - servicemesh + - distributedworkloads + - aiexplainability + + diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES new file mode 100644 index 00000000000..a84bc43f532 --- /dev/null +++ b/OWNERS_ALIASES @@ -0,0 +1,41 @@ +aliases: + platform: + - AjayJagan + - ajaypratap003 + - asanzgom + - etirelli + - LaVLaS + - mattmahoneyrh + - VaishnaviHire + - ykaliuta + - zdtsw + aiexplainability: + - RobGeada + - ruivieira + dashboard: + - andrewballantyne + - lucferbux + datasciencepipelines: + - gmfrasca + - HumairAK + distributedworkloads: + - astefanutti + - dimakis + ide: + - atheo89 + - harshad16 + modelregistry: + - dhirajsb + - rareddy + - tarilabs + servicemesh: + - aslakknutsen + - bartoszmajsak + - cam-garrison + serving: + - israel-hdez + - VedantMahabaleshwarkar + + + + diff --git a/bundle/manifests/datasciencecluster.opendatahub.io_datascienceclusters.yaml b/bundle/manifests/datasciencecluster.opendatahub.io_datascienceclusters.yaml index 45c687adb48..9608c6de4aa 100644 --- a/bundle/manifests/datasciencecluster.opendatahub.io_datascienceclusters.yaml +++ b/bundle/manifests/datasciencecluster.opendatahub.io_datascienceclusters.yaml @@ -178,6 +178,18 @@ spec: before enable component Does not support enabled ModelMeshServing at the same time properties: + defaultDeploymentMode: + description: Configures the default deployment mode for Kserve. + This can be set to 'Serverless' or 'RawDeployment'. The + value specified in this field will be used to set the default + deployment mode in the 'inferenceservice-config' configmap + for Kserve If no default deployment mode is specified, Kserve + will use Serverless mode + enum: + - Serverless + - RawDeployment + pattern: ^(Serverless|RawDeployment)$ + type: string devFlags: description: Add developer fields properties: diff --git a/bundle/manifests/rhods-operator.clusterserviceversion.yaml b/bundle/manifests/rhods-operator.clusterserviceversion.yaml index b5a312b7396..8de96754492 100644 --- a/bundle/manifests/rhods-operator.clusterserviceversion.yaml +++ b/bundle/manifests/rhods-operator.clusterserviceversion.yaml @@ -594,6 +594,7 @@ spec: - get - list - patch + - update - watch - apiGroups: - "" diff --git a/components/kserve/kserve.go b/components/kserve/kserve.go index b3b415df22e..739a9345d28 100644 --- a/components/kserve/kserve.go +++ b/components/kserve/kserve.go @@ -7,7 +7,6 @@ import ( "path/filepath" "strings" - "github.com/hashicorp/go-multierror" operatorv1 "github.com/openshift/api/operator/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" @@ -18,7 +17,6 @@ import ( infrav1 "github.com/opendatahub-io/opendatahub-operator/v2/infrastructure/v1" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" - "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/monitoring" ) @@ -34,6 +32,16 @@ var ( // Verifies that Kserve implements ComponentInterface. var _ components.ComponentInterface = (*Kserve)(nil) +// +kubebuilder:validation:Pattern=`^(Serverless|RawDeployment)$` +type DefaultDeploymentMode string + +var ( + // Serverless will be used as the default deployment mode for Kserve. This requires Serverless and ServiceMesh operators configured as dependencies. + Serverless DefaultDeploymentMode = "Serverless" + // RawDeployment will be used as the default deployment mode for Kserve. + RawDeployment DefaultDeploymentMode = "RawDeployment" +) + // Kserve struct holds the configuration for the Kserve component. // +kubebuilder:object:generate=true type Kserve struct { @@ -41,6 +49,11 @@ type Kserve struct { // Serving configures the KNative-Serving stack used for model serving. A Service // Mesh (Istio) is prerequisite, since it is used as networking layer. Serving infrav1.ServingSpec `json:"serving,omitempty"` + // Configures the default deployment mode for Kserve. This can be set to 'Serverless' or 'RawDeployment'. + // The value specified in this field will be used to set the default deployment mode in the 'inferenceservice-config' configmap for Kserve + // If no default deployment mode is specified, Kserve will use Serverless mode + // +kubebuilder:validation:Enum=Serverless;RawDeployment + DefaultDeploymentMode DefaultDeploymentMode `json:"defaultDeploymentMode,omitempty"` } func (k *Kserve) OverrideManifests(_ string) error { @@ -101,6 +114,10 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, resC return err } } else { + // Configure dependencies + if err := k.configureServerless(cli, dscispec); err != nil { + return err + } if k.DevFlags != nil { // Download manifests and update paths if err = k.OverrideManifests(string(platform)); err != nil { @@ -108,10 +125,6 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, resC } } - if err := k.configureServerless(cli, dscispec); err != nil { - return err - } - // Update image parameters only when we do not have customized manifests set if (dscispec.DevFlags == nil || dscispec.DevFlags.ManifestsUri == "") && (k.DevFlags == nil || len(k.DevFlags.Manifests) == 0) { if err := deploy.ApplyParams(Path, k.SetImageParamsMap(imageParamMap), false); err != nil { @@ -124,6 +137,12 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, resC return err } + if enabled { + if err := k.setupKserveConfig(ctx, cli, dscispec); err != nil { + return err + } + } + // For odh-model-controller if enabled { if err := cluster.UpdatePodSecurityRolebinding(cli, dscispec.ApplicationsNamespace, "odh-model-controller"); err != nil { @@ -143,6 +162,7 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, resC return err } } + // CloudService Monitoring handling if platform == deploy.ManagedRhods { if enabled { @@ -163,61 +183,3 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, resC func (k *Kserve) Cleanup(_ client.Client, instance *dsciv1.DSCInitializationSpec) error { return k.removeServerlessFeatures(instance) } - -func (k *Kserve) configureServerless(cli client.Client, instance *dsciv1.DSCInitializationSpec) error { - switch k.Serving.ManagementState { - case operatorv1.Unmanaged: // Bring your own CR - fmt.Println("Serverless CR is not configured by the operator, we won't do anything") - - case operatorv1.Removed: // we remove serving CR - fmt.Println("existing Serverless CR (owned by operator) will be removed") - if err := k.removeServerlessFeatures(instance); err != nil { - return err - } - - case operatorv1.Managed: // standard workflow to create CR - switch instance.ServiceMesh.ManagementState { - case operatorv1.Unmanaged, operatorv1.Removed: - return fmt.Errorf("ServiceMesh is need to set to 'Managed' in DSCI CR, it is required by KServe serving field") - } - - // check on dependent operators if all installed in cluster - dependOpsErrors := checkDependentOperators(cli).ErrorOrNil() - if dependOpsErrors != nil { - return dependOpsErrors - } - - serverlessFeatures := feature.ComponentFeaturesHandler(k, instance, k.configureServerlessFeatures()) - - if err := serverlessFeatures.Apply(); err != nil { - return err - } - } - return nil -} - -func (k *Kserve) removeServerlessFeatures(instance *dsciv1.DSCInitializationSpec) error { - serverlessFeatures := feature.ComponentFeaturesHandler(k, instance, k.configureServerlessFeatures()) - - return serverlessFeatures.Delete() -} - -func checkDependentOperators(cli client.Client) *multierror.Error { - var multiErr *multierror.Error - - if found, err := deploy.OperatorExists(cli, ServiceMeshOperator); err != nil { - multiErr = multierror.Append(multiErr, err) - } else if !found { - err = fmt.Errorf("operator %s not found. Please install the operator before enabling %s component", - ServiceMeshOperator, ComponentName) - multiErr = multierror.Append(multiErr, err) - } - if found, err := deploy.OperatorExists(cli, ServerlessOperator); err != nil { - multiErr = multierror.Append(multiErr, err) - } else if !found { - err = fmt.Errorf("operator %s not found. Please install the operator before enabling %s component", - ServerlessOperator, ComponentName) - multiErr = multierror.Append(multiErr, err) - } - return multiErr -} diff --git a/components/kserve/kserve_config_handler.go b/components/kserve/kserve_config_handler.go new file mode 100644 index 00000000000..39fdd62f8f9 --- /dev/null +++ b/components/kserve/kserve_config_handler.go @@ -0,0 +1,175 @@ +package kserve + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/hashicorp/go-multierror" + operatorv1 "github.com/openshift/api/operator/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" +) + +const ( + KserveConfigMapName string = "inferenceservice-config" +) + +func (k *Kserve) setupKserveConfig(ctx context.Context, cli client.Client, dscispec *dsciv1.DSCInitializationSpec) error { + // as long as Kserve.Serving is not 'Removed', we will setup the dependencies + + switch k.Serving.ManagementState { + case operatorv1.Managed, operatorv1.Unmanaged: + if k.DefaultDeploymentMode == "" { + // if the default mode is empty in the DSC, assume mode is "Serverless" since k.Serving is Managed + if err := k.setDefaultDeploymentMode(ctx, cli, dscispec, Serverless); err != nil { + return err + } + } else { + // if the default mode is explicitly specified, respect that + if err := k.setDefaultDeploymentMode(ctx, cli, dscispec, k.DefaultDeploymentMode); err != nil { + return err + } + } + case operatorv1.Removed: + if k.DefaultDeploymentMode == Serverless { + return fmt.Errorf("setting defaultdeployment mode as Serverless is incompatible with having Serving 'Removed'") + } + if k.DefaultDeploymentMode == "" { + fmt.Println("Serving is removed, Kserve will default to rawdeployment") + } + if err := k.setDefaultDeploymentMode(ctx, cli, dscispec, RawDeployment); err != nil { + return err + } + } + return nil +} + +func (k *Kserve) setDefaultDeploymentMode(ctx context.Context, cli client.Client, dscispec *dsciv1.DSCInitializationSpec, defaultmode DefaultDeploymentMode) error { + inferenceServiceConfigMap := &corev1.ConfigMap{} + err := cli.Get(ctx, client.ObjectKey{ + Namespace: dscispec.ApplicationsNamespace, + Name: KserveConfigMapName, + }, inferenceServiceConfigMap) + if err != nil { + return fmt.Errorf("error getting configmap 'inferenceservice-config'. %w", err) + } + + // set data.deploy.defaultDeploymentMode to the model specified in the Kserve spec + var deployData map[string]interface{} + if err = json.Unmarshal([]byte(inferenceServiceConfigMap.Data["deploy"]), &deployData); err != nil { + return fmt.Errorf("error retrieving value for key 'deploy' from configmap %s. %w", KserveConfigMapName, err) + } + modeFound := deployData["defaultDeploymentMode"] + if modeFound != string(defaultmode) { + deployData["defaultDeploymentMode"] = defaultmode + deployDataBytes, err := json.MarshalIndent(deployData, "", " ") + if err != nil { + return fmt.Errorf("could not set values in configmap %s. %w", KserveConfigMapName, err) + } + inferenceServiceConfigMap.Data["deploy"] = string(deployDataBytes) + + var ingressData map[string]interface{} + if err = json.Unmarshal([]byte(inferenceServiceConfigMap.Data["ingress"]), &ingressData); err != nil { + return fmt.Errorf("error retrieving value for key 'ingress' from configmap %s. %w", KserveConfigMapName, err) + } + if defaultmode == RawDeployment { + ingressData["disableIngressCreation"] = true + } else { + ingressData["disableIngressCreation"] = false + } + ingressDataBytes, err := json.MarshalIndent(ingressData, "", " ") + if err != nil { + return fmt.Errorf("could not set values in configmap %s. %w", KserveConfigMapName, err) + } + inferenceServiceConfigMap.Data["ingress"] = string(ingressDataBytes) + + if err = cli.Update(ctx, inferenceServiceConfigMap); err != nil { + return fmt.Errorf("could not set default deployment mode for Kserve. %w", err) + } + + // Restart the pod if configmap is updated so that kserve boots with the correct value + podList := &corev1.PodList{} + listOpts := []client.ListOption{ + client.InNamespace(dscispec.ApplicationsNamespace), + client.MatchingLabels{ + "app.opendatahub.io/kserve": "true", + "control-plane": "kserve-controller-manager", + }, + } + if err := cli.List(ctx, podList, listOpts...); err != nil { + return fmt.Errorf("failed to list pods: %w", err) + } + for _, pod := range podList.Items { + pod := pod + if err := cli.Delete(ctx, &pod); err != nil { + return fmt.Errorf("failed to delete pod %s: %w", pod.Name, err) + } + } + } + + return nil +} + +func (k *Kserve) configureServerless(cli client.Client, instance *dsciv1.DSCInitializationSpec) error { + switch k.Serving.ManagementState { + case operatorv1.Unmanaged: // Bring your own CR + fmt.Println("Serverless CR is not configured by the operator, we won't do anything") + + case operatorv1.Removed: // we remove serving CR + fmt.Println("existing Serverless CR (owned by operator) will be removed") + if err := k.removeServerlessFeatures(instance); err != nil { + return err + } + + case operatorv1.Managed: // standard workflow to create CR + switch instance.ServiceMesh.ManagementState { + case operatorv1.Unmanaged, operatorv1.Removed: + return fmt.Errorf("ServiceMesh is need to set to 'Managed' in DSCI CR, it is required by KServe serving field") + } + + // check on dependent operators if all installed in cluster + dependOpsErrors := checkDependentOperators(cli).ErrorOrNil() + if dependOpsErrors != nil { + return dependOpsErrors + } + + serverlessFeatures := feature.ComponentFeaturesHandler(k, instance, k.configureServerlessFeatures()) + + if err := serverlessFeatures.Apply(); err != nil { + return err + } + } + return nil +} + +func (k *Kserve) removeServerlessFeatures(instance *dsciv1.DSCInitializationSpec) error { + serverlessFeatures := feature.ComponentFeaturesHandler(k, instance, k.configureServerlessFeatures()) + + return serverlessFeatures.Delete() +} + +func checkDependentOperators(cli client.Client) *multierror.Error { + var multiErr *multierror.Error + + if found, err := deploy.OperatorExists(cli, ServiceMeshOperator); err != nil { + multiErr = multierror.Append(multiErr, err) + } else if !found { + err = fmt.Errorf("operator %s not found. Please install the operator before enabling %s component", + ServiceMeshOperator, ComponentName) + multiErr = multierror.Append(multiErr, err) + } + + if found, err := deploy.OperatorExists(cli, ServerlessOperator); err != nil { + multiErr = multierror.Append(multiErr, err) + } else if !found { + err = fmt.Errorf("operator %s not found. Please install the operator before enabling %s component", + ServerlessOperator, ComponentName) + multiErr = multierror.Append(multiErr, err) + } + return multiErr +} diff --git a/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml b/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml index fd4c96d290b..a8517593a73 100644 --- a/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml +++ b/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml @@ -179,6 +179,18 @@ spec: before enable component Does not support enabled ModelMeshServing at the same time properties: + defaultDeploymentMode: + description: Configures the default deployment mode for Kserve. + This can be set to 'Serverless' or 'RawDeployment'. The + value specified in this field will be used to set the default + deployment mode in the 'inferenceservice-config' configmap + for Kserve If no default deployment mode is specified, Kserve + will use Serverless mode + enum: + - Serverless + - RawDeployment + pattern: ^(Serverless|RawDeployment)$ + type: string devFlags: description: Add developer fields properties: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index bbe029f5342..2f07b94eb07 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -352,6 +352,7 @@ rules: - get - list - patch + - update - watch - apiGroups: - "" diff --git a/controllers/datasciencecluster/datasciencecluster_controller.go b/controllers/datasciencecluster/datasciencecluster_controller.go index 72e119636f4..b856800b089 100644 --- a/controllers/datasciencecluster/datasciencecluster_controller.go +++ b/controllers/datasciencecluster/datasciencecluster_controller.go @@ -331,6 +331,10 @@ var configMapPredicates = predicate.Funcs{ if e.ObjectNew.GetName() == "prometheus" && e.ObjectNew.GetNamespace() == "redhat-ods-monitoring" { return false } + // Do not reconcile on kserver's inferenceservice-config CM updates, for rawdeployment + if e.ObjectNew.GetName() == "inferenceservice-config" && (e.ObjectNew.GetNamespace() == "redhat-ods-applications" || e.ObjectNew.GetNamespace() == "opendatahub") { + return false + } return true }, } diff --git a/controllers/datasciencecluster/kubebuilder_rbac.go b/controllers/datasciencecluster/kubebuilder_rbac.go index dd8c02699b5..ab7994513fb 100644 --- a/controllers/datasciencecluster/kubebuilder_rbac.go +++ b/controllers/datasciencecluster/kubebuilder_rbac.go @@ -185,7 +185,7 @@ package datasciencecluster // +kubebuilder:rbac:groups="core",resources=endpoints,verbs=watch;list;get;create;update;delete // +kubebuilder:rbac:groups="core",resources=configmaps/status,verbs=get;update;patch;delete -// +kubebuilder:rbac:groups="core",resources=configmaps,verbs=get;create;watch;patch;delete;list +// +kubebuilder:rbac:groups="core",resources=configmaps,verbs=get;create;watch;patch;delete;list;update // +kubebuilder:rbac:groups="core",resources=clusterversions,verbs=watch;list // +kubebuilder:rbac:groups="config.openshift.io",resources=clusterversions,verbs=watch;list diff --git a/docs/Dev-Preview.md b/docs/Dev-Preview.md index c0586dfc3cd..b817b5546f8 100644 --- a/docs/Dev-Preview.md +++ b/docs/Dev-Preview.md @@ -108,3 +108,28 @@ EOF - Currently on integration of ODH [core components](https://opendatahub.io/docs/tiered-components/) are available with the Operator. - Tier 1 and Tier 2 components can be deployed manually using [kustomize build](https://kubectl.docs.kubernetes.io/references/kustomize/cmd/build/) + + +## Preview Features + +### Kserve + +#### Changing the Default Deployment Mode for Kserve + +Kserve can now support `Serverless` and `RawDeployment` as the default deployment modes. The default deployment mode for Kserve can be set in the DSC as follows : +``` +kserve: + defaultDeploymentMode: RawDeployment + managementState: Managed + serving: + ingressGateway: + certificate: + type: SelfSigned + managementState: Removed + name: knative-serving +``` + +Notes : +- If a value for defaultDeploymentMode is not provided, it is assumed to be `Serverless` as long as kserve.serving.managementState is not `Removed` +- If kserve.serving.managementState is `Removed`, the default deployment mode is assumed to be `RawDeployment` if no value is provided. +- Explicitly setting defaultDeploymentMode to `Serverless` with kserve.serving.managementState set to `Removed` will result in an expected error due to incompatible options. diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go index 14686da2ee5..a7b872760ef 100644 --- a/pkg/deploy/deploy.go +++ b/pkg/deploy/deploy.go @@ -289,6 +289,11 @@ func manageResource(ctx context.Context, cli client.Client, obj *unstructured.Un return nil } + // do not reconcile kserve resource with annotation "opendatahub.io/managed: false" + if found.GetAnnotations()["opendatahub.io/managed"] == "false" && componentName == "kserve" { + return nil + } + // Preserve app.opendatahub.io/ labels of previous versions of existing objects foundLabels := make(map[string]string) for k, v := range found.GetLabels() { diff --git a/pkg/feature/feature.go b/pkg/feature/feature.go index 21aec158343..d3bf6b67066 100644 --- a/pkg/feature/feature.go +++ b/pkg/feature/feature.go @@ -52,7 +52,7 @@ func (f *Feature) Apply() (err error) { return nil } - if trackerErr := f.createFeatureTracker(); err != nil { + if trackerErr := f.createFeatureTracker(); trackerErr != nil { return trackerErr }