Skip to content

Commit

Permalink
Support for VirtualServices for InferenceLogger traffic (#332)
Browse files Browse the repository at this point in the history
* Generate KServe Inference Logger in conformance with DestinationRule and VirtualService

* Add VirtualService creation for models in the mesh

* Add permissions for VirtualServices

* Update manifests for VirtualServices

* Fix VirtualServiceName variable
  • Loading branch information
ruivieira authored Oct 18, 2024
1 parent fb54647 commit ddd093a
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 1 deletion.
12 changes: 12 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- networking.istio.io
resources:
- virtualservices
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
Expand Down
21 changes: 20 additions & 1 deletion controllers/inference_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ func (r *TrustyAIServiceReconciler) handleInferenceServices(ctx context.Context,
// patchKServe adds a TrustyAI service as an InferenceLogger to a KServe InferenceService
func (r *TrustyAIServiceReconciler) patchKServe(ctx context.Context, instance *trustyaiopendatahubiov1alpha1.TrustyAIService, infService kservev1beta1.InferenceService, namespace string, crName string, remove bool) error {

url := generateNonTLSServiceURL(crName, namespace)
url := generateKServeLoggerURL(crName, namespace)

if remove {
if infService.Spec.Predictor.Logger == nil || *infService.Spec.Predictor.Logger.URL != url {
Expand Down Expand Up @@ -295,6 +295,25 @@ func (r *TrustyAIServiceReconciler) patchKServe(ctx context.Context, instance *t
log.FromContext(ctx).Error(err, "InferenceService has service mesh annotation but DestinationRule CRD not found")
}

// Check if VirtualService CRD is present. If there's an error, don't proceed and return the error
exists, err = r.isVirtualServiceCRDPresent(ctx)
if err != nil {
log.FromContext(ctx).Error(err, "Error verifying VirtualService CRD is present")
return err
}

// Try to create the VirtualService, since CRD exists
if exists {
err := r.ensureVirtualService(ctx, instance)
if err != nil {
return fmt.Errorf("failed to ensure VirtualService: %v", err)
}
} else {
// VirtualService CRD does not exist. Do not attempt to create it and log error
err := fmt.Errorf("the VirtualService CRD is not present in this cluster")
log.FromContext(ctx).Error(err, "InferenceService has service mesh annotation but VirtualService CRD not found")
}

}

// Update the InferenceService
Expand Down
16 changes: 16 additions & 0 deletions controllers/templates/service/virtual-service.tmpl.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: {{ .VirtualServiceName }}
namespace: {{ .Namespace }}
spec:
hosts:
- {{ .Name }}.{{ .Namespace }}.svc.cluster.local
http:
- match:
- port: 80
route:
- destination:
host: {{ .Name }}.{{ .Namespace }}.svc.cluster.local
port:
number: 443
1 change: 1 addition & 0 deletions controllers/trustyaiservice_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type TrustyAIServiceReconciler struct {
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=get;list;watch;create;update;delete
//+kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;create;update
//+kubebuilder:rbac:groups=networking.istio.io,resources=destinationrules,verbs=create;list;watch;get;update;patch;delete
//+kubebuilder:rbac:groups=networking.istio.io,resources=virtualservices,verbs=create;list;watch;get;update;patch;delete
//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=list;watch;get

// Reconcile is part of the main kubernetes reconciliation loop which aims to
Expand Down
5 changes: 5 additions & 0 deletions controllers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,8 @@ func generateTLSServiceURL(crName string, namespace string) string {
func generateNonTLSServiceURL(crName string, namespace string) string {
return "http://" + crName + "." + namespace + ".svc"
}

// generateKServeLoggerURL generates an logger url for KServe Inference Loggers
func generateKServeLoggerURL(crName string, namespace string) string {
return "http://" + crName + "." + namespace + ".svc.cluster.local"
}
89 changes: 89 additions & 0 deletions controllers/virtual_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package controllers

import (
"context"
"fmt"
"reflect"

trustyaiopendatahubiov1alpha1 "github.com/trustyai-explainability/trustyai-service-operator/api/v1alpha1"
templateParser "github.com/trustyai-explainability/trustyai-service-operator/controllers/templates"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log"
)

const (
virtualServiceTemplatePath = "service/virtual-service.tmpl.yaml"
virtualServiceCDRName = "destinationrules.networking.istio.io"
)

// DestinationRuleConfig has the variables for the DestinationRule template
type VirtualServiceConfig struct {
Name string
Namespace string
VirtualServiceName string
}

// isVirtualServiceCRDPresent returns true if the DestinationRule CRD is present, false otherwise
func (r *TrustyAIServiceReconciler) isVirtualServiceCRDPresent(ctx context.Context) (bool, error) {
crd := &apiextensionsv1.CustomResourceDefinition{}

err := r.Get(ctx, types.NamespacedName{Name: virtualServiceCDRName}, crd)
if err != nil {
if !errors.IsNotFound(err) {
return false, fmt.Errorf("error getting "+virtualServiceCDRName+" CRD: %v", err)
}
// Not found
return false, nil
}

// Found
return true, nil
}

func (r *TrustyAIServiceReconciler) ensureVirtualService(ctx context.Context, instance *trustyaiopendatahubiov1alpha1.TrustyAIService) error {

virtualServiceName := instance.Name + "-redirect"

existingVirtualService := &unstructured.Unstructured{}
existingVirtualService.SetKind("VirtualService")
existingVirtualService.SetAPIVersion("networking.istio.io/v1beta1")

// Check if the DestinationRule already exists
err := r.Get(ctx, types.NamespacedName{Name: virtualServiceName, Namespace: instance.Namespace}, existingVirtualService)
if err == nil {
// DestinationRule exists
return nil
}

if !errors.IsNotFound(err) {
return fmt.Errorf("failed to check for existing VirtualService: %v", err)
}

virtualServiceConfig := VirtualServiceConfig{
Name: instance.Name,
Namespace: instance.Namespace,
VirtualServiceName: virtualServiceName,
}

var virtualService *unstructured.Unstructured
virtualService, err = templateParser.ParseResource[unstructured.Unstructured](virtualServiceTemplatePath, virtualServiceConfig, reflect.TypeOf(&unstructured.Unstructured{}))
if err != nil {
log.FromContext(ctx).Error(err, "could not parse the VirtualService template")
return err
}

if err := ctrl.SetControllerReference(instance, virtualService, r.Scheme); err != nil {
return err
}

err = r.Create(ctx, virtualService)
if err != nil {
return fmt.Errorf("failed to create VirtualService: %v", err)
}

return nil
}

0 comments on commit ddd093a

Please sign in to comment.