Skip to content

Commit

Permalink
allow setting default deployment mode for Kserve in DSC (#864)
Browse files Browse the repository at this point in the history
* allow setting default deployment mode for Kserve in DSC

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* move kserve config logic to separate file + enhancements

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* revert dev image set in operator CSV

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* only setup kserve config if component is enabled

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* bug fix

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* address PR feedback

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* cleanup

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* fix lint error

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* set default value for Kserve defaultDeploymentMode to be empty

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* more pr feedback

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* update bundle

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* enhance documentation

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

* add readme for dev preview

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>

---------

Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>
  • Loading branch information
VedantMahabaleshwarkar authored Feb 27, 2024
1 parent 4ce4dce commit cebd287
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,17 @@ 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
enum:
- Serverless
- RawDeployment
pattern: ^(Serverless|RawDeployment)$
type: string
devFlags:
description: Add developer fields
properties:
Expand Down
91 changes: 26 additions & 65 deletions components/kserve/kserve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
)

Expand All @@ -34,13 +32,28 @@ 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 {
components.Component `json:""`
// 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 {
Expand Down Expand Up @@ -102,17 +115,17 @@ 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 {
return err
}
}

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 {
Expand All @@ -125,6 +138,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 {
Expand All @@ -144,6 +163,7 @@ func (k *Kserve) ReconcileComponent(ctx context.Context, cli client.Client, resC
return err
}
}

// CloudService Monitoring handling
if platform == deploy.ManagedRhods {
if enabled {
Expand All @@ -169,62 +189,3 @@ func (k *Kserve) Cleanup(_ client.Client, instance *dsciv1.DSCInitializationSpec

return k.removeServiceMeshConfigurations(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
}
175 changes: 175 additions & 0 deletions components/kserve/kserve_config_handler.go
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,17 @@ 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
enum:
- Serverless
- RawDeployment
pattern: ^(Serverless|RawDeployment)$
type: string
devFlags:
description: Add developer fields
properties:
Expand Down
2 changes: 1 addition & 1 deletion controllers/datasciencecluster/kubebuilder_rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,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;update;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
Expand Down
Loading

0 comments on commit cebd287

Please sign in to comment.