Skip to content

Commit 66b6202

Browse files
committed
Inference Graph: use plain text HTTP when part of Istio Mesh
In topologies using Istio mesh, applications can use plain-text HTTP to send traffic to other mesh-member workloads. Handling of TLS is delegated to Istio. Thus, when KServe workloads have the Istio sidecar (e.g. by using auto-injection), the Inference Graph router should send its traffic without TLS even if the schema of the service URL is specified as HTTPS. Istio would originate TLS when needed, and the double TLS is prevented (see https://istio.io/latest/docs/ops/common-problems/network-issues/#double-tls). These changes implement a detection of the Istio sidecar by querying a well known port that the sidecar is using. If the sidecar is found, inference requests are sent using plain-text HTTP. Signed-off-by: Edgar Hernández <[email protected]>
1 parent dd99f33 commit 66b6202

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

cmd/router/main.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ import (
2323
"fmt"
2424
"io"
2525
"net/http"
26+
"net/url"
2627
"os"
2728
"regexp"
2829
"strconv"
2930
"strings"
31+
"syscall"
3032
"time"
3133

3234
"github.com/kserve/kserve/pkg/constants"
@@ -45,9 +47,82 @@ import (
4547

4648
var log = logf.Log.WithName("InferenceGraphRouter")
4749

50+
// _isInMesh is an auxiliary global variable for isInIstioMesh function.
51+
var _isInMesh *bool
52+
53+
// isInIstioMesh checks if the InferenceGraph pod belongs to the mesh by
54+
// checking the presence of the sidecar. It is known that when the sidecar
55+
// is present, Envoy will be using port 15000 with standard HTTP. Thus, the
56+
// presence of the sidecar is assumed if this port responds with an HTTP 200 status
57+
// when doing a "GET /" request.
58+
//
59+
// The result of the check is cached in the _isInMesh global variable. Since a
60+
// pod cannot be modified, a single check is enough for the whole life of the pod.
61+
// The check cannot be done at start-up, because there is the possibility of
62+
// false negatives, since there is no guarantee that the Istio sidecar has already
63+
// started. So, the isInIstioMesh func should be used after the first inference
64+
// request is received when it is guaranteed that the Istio sidecar is in ready state.
65+
//
66+
// Reference:
67+
// - https://istio.io/latest/docs/ops/deployment/application-requirements/#ports-used-by-istio)
68+
func isInIstioMesh() (bool, error) {
69+
if _isInMesh != nil {
70+
return *_isInMesh, nil
71+
}
72+
73+
isInMesh := false
74+
client := http.Client{
75+
Timeout: time.Second * 3,
76+
}
77+
response, err := client.Get("http://localhost:15000")
78+
if err == nil {
79+
if response.StatusCode == http.StatusOK {
80+
isInMesh = true
81+
}
82+
} else if errors.Is(err, syscall.ECONNREFUSED) {
83+
// Assume no Istio sidecar. Thus, this pod is not
84+
// part of the mesh.
85+
err = nil
86+
}
87+
88+
if response != nil && response.Body != nil {
89+
err = response.Body.Close()
90+
}
91+
92+
_isInMesh = &isInMesh
93+
return *_isInMesh, err
94+
}
95+
4896
func callService(serviceUrl string, input []byte, headers http.Header) ([]byte, int, error) {
4997
defer timeTrack(time.Now(), "step", serviceUrl)
5098
log.Info("Entering callService", "url", serviceUrl)
99+
100+
parsedServiceUrl, parseServiceUrlErr := url.Parse(serviceUrl)
101+
if parseServiceUrlErr != nil {
102+
return nil, 500, parseServiceUrlErr
103+
}
104+
if parsedServiceUrl.Scheme == "https" {
105+
if isInMesh, isInMeshErr := isInIstioMesh(); isInMeshErr != nil {
106+
return nil, 500, isInMeshErr
107+
} else if isInMesh {
108+
// In this branch, it has been resolved that the Inference Graph is
109+
// part of the Istio mesh. In this case, even if the target service
110+
// is using HTTPS, it is better to use plain-text HTTP:
111+
// * If the target service is also part of the mesh, Istio will take
112+
// care of properly applying TLS policies (e.g. mutual TLS).
113+
// * If the target service is _not_ part of the mesh, it still is better
114+
// to let Istio manage TLS by configuring the sidecar to do TLS
115+
// origination and prevent double TLS (see: https://istio.io/latest/docs/ops/common-problems/network-issues/#double-tls)
116+
//
117+
// If the Inference Graph is not part of the mesh, the indicated
118+
// schema is used.
119+
parsedServiceUrl.Scheme = "http"
120+
serviceUrl = parsedServiceUrl.String()
121+
122+
log.Info("Using plain-text schema to let Istio manage TLS termination", "url", serviceUrl)
123+
}
124+
}
125+
51126
req, err := http.NewRequest("POST", serviceUrl, bytes.NewBuffer(input))
52127
if err != nil {
53128
log.Error(err, "An error occurred while preparing request object with serviceUrl.", "serviceUrl", serviceUrl)

pkg/controller/v1alpha1/inferencegraph/raw_ig.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func handleInferenceGraphRawDeployment(cl client.Client, clientset kubernetes.In
148148
reconciler, err := raw.NewRawKubeReconciler(cl, clientset, scheme, objectMeta, metav1.ObjectMeta{}, &componentExtSpec, desiredSvc, nil)
149149

150150
if err != nil {
151-
return nil, reconciler.URL, errors.Wrapf(err, "fails to create NewRawKubeReconciler for inference graph")
151+
return nil, nil, errors.Wrapf(err, "fails to create NewRawKubeReconciler for inference graph")
152152
}
153153
// set Deployment Controller
154154
for _, deployments := range reconciler.Deployment.DeploymentList {

0 commit comments

Comments
 (0)