Use istio ServiceEntry resource to send traffic to internal kubernetes FQDN over external connection - kubernetes

CONTEXT:
I'm in the middle of planning a migration of kubernetes services from one cluster to another, the clusters are in separate GCP projects but need to be able to communicate across the clusters until all apps are moved across. The projects have VPC peering enabled to allow internal traffic to an internal load balancer (tested and confirmed that's fine).
We run Anthos service mesh (v1.12) in GKE clusters.
PROBLEM:
I need to find a way to do the following:
PodA needs to be migrated, and references a hostname in its ENV which is simply 'serviceA'
Running in the same cluster this resolves fine as the pod resolves 'serviceA' to 'serviceA.default.svc.cluster.local' (the internal kubernetes FQDN).
However, when I run PodA on the new cluster I need serviceA's hostname to actually resolve back to the internal load balancer on the other cluster, and not on its local cluster (and namespace), seen as serviceA is still running on the old cluster.
I'm using an istio ServiceEntry resource to try and achieve this, as follows:
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: serviceA
namespace: default
spec:
hosts:
- serviceA.default.svc.cluster.local
location: MESH_EXTERNAL
ports:
- number: 50051
name: grpc
protocol: GRPC
resolution: STATIC
endpoints:
- address: 'XX.XX.XX.XX' # IP Redacted
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: resources
namespace: default
spec:
hosts:
- 'serviceA.default.svc.cluster.local'
gateways:
- mesh
http:
- timeout: 5s
route:
- destination:
host: serviceA.default.svc.cluster.local
This doesn't appear to work and I'm getting Error: 14 UNAVAILABLE: upstream request timeout errors on PodA running in the new cluster.
I can confirm that running telnet to the hostname from another pod on the mesh appears to work (i.e. don't get connection timeout or connection refused).
Is there a limitation on what you can use in the hosts on a serviceentry? Does it have to be a .com or .org address?
The only way I've got this to work properly is to use a hostAlias in PodA to add a hostfile entry for the hostname, but I really want to try and avoid doing this as it means making the same change in lots of files, I would rather try and use Istio's serviceentry to try and achieve this.
Any ideas/comments appreciated, thanks.

Fortunately I came across someone with a similar (but not identical) issue, and the answer in this stackoverflow post gave me the outline of what kubernetes (and istio) resources I needed to create.
I was heading in the right direction, just needed to really understand how istio uses Virtual Services and Service Entries.
The end result was this:
apiVersion: v1
kind: Service
metadata:
name: serviceA
namespace: default
spec:
type: ExternalName
externalName: serviceA.example.com
ports:
- name: grpc
protocol: TCP
port: 50051
---
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: serviceA
namespace: default
spec:
hosts:
- serviceA.example.com
location: MESH_EXTERNAL
ports:
- number: 50051
name: grpc
protocol: TCP
resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: serviceA
namespace: default
spec:
hosts:
- serviceA.default.svc.cluster.local
http:
- timeout: 5s
route:
- destination:
host: serviceA.default.svc.cluster.local
rewrite:
authority: serviceA.example.com

Related

Accessing Jaeger /tracing on from k8s cluster returns index.html and 503 Service Unavailable

I have a Kubernetes cluster which runs with Istio as a service mesh and load balancing provided by Metallb. I have 4 Istio addons (Prometheus, Kiali, Grafana, and Jaeger) running on the cluster in the istio namespace, but running firefox on the virtual machine is relatively slow and I also don't want to rely on the "istioctl dashboard" command in order to access my monitoring tools.
I've successfully been able to access Kiali and Grafana by tunneling in with putty and utilizing Istio ingressgateway with Gateway/Virtual service resources similar to those found in istio documentation here - https://istio.io/latest/docs/tasks/observability/gateways/. The istio ingressgateway pod is listening on 10.10.1.10 and my putty tunnel is directed to 10.10.1.10:80 with a source port of 90. Everything is done in http for testing at this time
I've listed my specific configuration below -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: tracing-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http-tracing
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: tracing-vs
namespace: istio-system
spec:
hosts:
- "*"
gateways:
- tracing-gateway
http:
- route:
- destination:
host: tracing
port:
number: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: tracing
namespace: istio-system
spec:
host: tracing
trafficPolicy:
tls:
mode: DISABLE
---
Whenever I attempt to access Jaeger by hitting the /tracing , however, I always receive a 503 service unavailable error. I know that the application can be functional though because if I run the istioctl dashboard jaeger command I can access it through the VM's firefox browser. I'm wondering what I need to configure within Jaeger to allow me to access it
Initially, when working with Jaeger I attempted to use a gateway/virtualsservice configuration that was identical to what worked for Grafana and Kiali but replacing names/ports/prefixes. which is shown below -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: grafana-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: grafana
spec:
hosts:
- "*"
gateways:
- grafana-gateway
http:
- match:
- uri:
prefix: /grafana
route:
- destination:
host: grafana
port:
number: 3000
When running this for jaeger I only ever received HTTP 503 responses. After trying different combinations of ports I used the yaml definition from the Istio page listed in the link above, changing only the hosts line since I don't have a domain and everything is IP based.
At this point, when I navigate to /tracing using my putty tunnel, it returns a blank page which, if inspected, is the jaegers index.html page. Inspecting the page shows that it attempts to redirect to jaeger_tracing but returns the net::ERR_ABORTED 503 (Service Unavailable) code shown in the screenshot below /tracing_error_image
A workaround solution was found by running the kubectl port-forward command on port 16686 but the same can be done with the istioctl dashboard jaeger command.
I ran one of them in the background and tunneled to localhost using my putty instance using the url /jaeger_tracing which is defined in my jaeger manifest. From there I could hit jaeger from my local instance without the sluggish VM performance.

How can I proxy an external site through my Kubernetes(OpenShift) Ingress?

I have a website that needs to be proxied through my web app.
Traditionally we've accomplished it via apache proxy with proxy directives.
The proxy also rewrites some of the headers and adds a couple of new ones.
Now the app has moved to OpenShift (Kubernetes) and I'm trying to avoid deploying another pod with apache.
Can I perform this header rewriting and proxying via K8 ingress? or router?
I've tried this approach, but it didn't work.
I also don't know how to get OpenShift Ingress logs, nothing seems to happen in there.
I tried using an external name, but it doesn't work:
kind: Service
metadata:
name: es3
spec:
externalName: google.com
type: ExternalName
---
kind: Route
apiVersion: route.openshift.io/v1
spec:
host: host.my-cluster-url.net
to:
kind: Service
name: es3
port:
targetPort: es3
I also tried using Endpoints , same result
apiVersion: v1
kind: Service
metadata:
name: mysvc
spec:
ports:
- name: app
port: 80
protocol: TCP
targetPort: 80
clusterIP: None
type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
name: mysvc
subsets:
- addresses:
- ip: my.ip.address
ports:
- name: app
port: 80
protocol: TCP
you want to proxy non kubernetes service, right? if yes, use end point and create service from end point, I have used this with kubernetes will work with openshift too my wild guess
https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/

Can we trace external API calls with Istio behind proxy via Kiali?

We have a Nodejs based microservices running in our on-prem kubernetes v1.19 with Istio v1.8.0. What I would like to achieve is trace or display the external API calls in Kiali where we have Jaeger clients for each microservices and able to trace internal traffics.
But so far I could not able to trace any external API calls hits from any microservices.The only thing that I can see the traffic for proxy in Kiali's graph overview.
We have a cooperate proxy, and each container have env proxies set for both http_proxy, https_proxy.Any external service accessible via a cooperate proxy thus traffics should go through the our cooperate proxy first. We have a secured gateway with TLS and we do not have egressgateway where only have istio-ingressgateway.
So is there anyway to trace external traffics likewise the internal traffics inside cluster?If yes what might be the missing thing?
$ kubectl get pods -n dev
NAME READY STATUS RESTARTS AGE
api-dev-74896ff4f9-slxt5 3/3 Running 0 7h1m
auth-dev-98f77d487-qt5zd 3/3 Running 0 3d5h
backend-dev-bb7765464-b7bpr 2/2 Running 0 7d3h
mp-dev-86d6b8b978-slqp7 3/3 Running 0 5d9h
ui-dev-d5667946b-sdvlc 2/2 Running 0 5d4h
Here are the ServiceEntries and VirtualServices that I created where I would like to use the retry feature as well the calls for proxy and externalAPI
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: company-proxy
namespace: dev
spec:
hosts:
- foo-proxy.net
ports:
- number: PORT
name: tcp
protocol: TCP
location: MESH_EXTERNAL
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: proxy
namespace: dev
spec:
hosts:
- "foo-proxy.net"
http:
- name: "company-proxy"
match:
- uri:
prefix: "/"
route:
- destination:
host: "foo-proxy.com"
timeout: 90s
retries:
retryOn: "5xx"
attempts: 3
perTryTimeout: 30s
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: foo-example.com
namespace: dev
spec:
hosts:
- "foo-example.com"
ports:
- number: 80
name: http
protocol: HTTP
- number: 443
name: https
protocol: HTTPS
location: MESH_EXTERNAL
resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: foo-example.com
namespace: dev
spec:
hosts:
- "foo-example.com"
http:
- name: "developer-api"
match:
- uri:
prefix: "/"
route:
- destination:
host: "foo-example.com"
timeout: 90s
retries:
retryOn: "5xx"
attempts: 3
perTryTimeout: 30s
I am not sure why Istio doesn't automatically trace your calls to external APIs. Perhaps it requires an egress gateway to be used, I'm not sure. Note also that Istio creates traces for http(s) traffic, not TCP.
However, this is something you can still do programmatically. You can use any of the Jaeger client libraries to augment"the traces already created by Envoy by appending your own spans.
To do so, you need first to extract the trace context from the HTTP headers of the incoming request (assuming that your external API calls are consecutive to an incoming request), and then create a new span as child of that previous span context. A good idea would be to use OpenTracing semantic conventions when you tag your new span. Tools like Kiali will be able to leverage some information if it follows this convention.
I've found this blog post that explains how to do it with the nodejs jaeger client: https://rhonabwy.com/2019/01/06/adding-tracing-with-jaeger-to-an-express-application/

Is there a way to proxy calls to an ExternalName service thanks to an Istio VirtualService?

In a project I'm currently working on, I'd like to create a DNS alias for a Kubernetes service located in another namespace. To do so, I created an ExternalName service such as the following:
kind: Service
apiVersion: v1
metadata:
name: connector
namespace: test
spec:
type: ExternalName
externalName: gateway.eventing.svc.cluster.local
So far, so good. When I request the 'connector' DNS, I successfully hit the external name, i.e. gateway.eventing.svc.cluster.local.
Now, I would like to add headers to all http requests sent to the connector ExternalName service, so I created an Istio VirtualService to do so:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: connector
namespace: test
spec:
hosts:
- connector
- connector.test.svc.cluster.local
http:
- match:
- uri:
prefix: /
route:
- destination:
host: connector
port:
number: 80
#headers config ignored for brevity
The problem is that the VirtualService is never called. It seems it does not intercepts request made to the connector DNS or to its fully qualified name, i.e. connector.test.svc.cluster.local.
I figured, after reading the documentation, that this happens because the Istio VirtualService checks the service registry, and the ExternalName service is not part of it, it's just some kind of DNS alias.
I therefore attempted to create an Istio ServiceEntry such as the following:
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: connector
namespace: test
spec:
hosts:
- connector
endpoints:
- address: gateway.eventing.svc.cluster.local
ports:
- number: 80
name: http
protocol: HTTP
location: MESH_INTERNAL
resolution: DNS
It works, and I can see in Kiali that instead of calling the PassthroughCluster when requesting connector, it is the connector ServiceEntry which is called, which is to my understanding what should be happening.
However, my connector VirtualService is still not called. Why is that? Is there a way to make it happen?
If not, what can I do to alias in a given namespace (i.e. test) a service located in another (i.e. eventing) and proxy http request thanks to an Istio VirtualService?
Thanks in advance for your help!
EDIT:
Sidecar injection is enabled namespace-wide (i.e. test)
So, it turns out that all that was missing to make it work was to both specify and name the port on the ExternalName service.
Here's the updated yaml:
kind: Service
apiVersion: v1
metadata:
name: connector
namespace: test
spec:
type: ExternalName
externalName: gateway.eventing.svc.cluster.local
ports:
- name: http
port: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: connector
namespace: test
spec:
hosts:
- connector
- connector.test.svc.cluster.local
http:
- match:
- uri:
prefix: /
route:
- destination:
host: connector
port:
number: 80
#headers config ignored for brevity
Naming the port is absolutely required, as it lets Istio know of the application protocol to use, as defined by the VirtualService.
No need to add a ServiceEntry, it will work with the BYON host specified in the VirtualService.
Note that the answer supplied by #Christoph Raab works as well, but is unhappilly too verbose to be marked as my prefered answer.
Update
I didn't see that the ports list was missing and am not sure how you could apply the yml, because the list should be required.
Anyways, I leave my answer. Maybe it will help someone else in the further.
Original Post (slightly modified)
Docs are not clear, but I think the header manipulation be could done by the receiving sidecar. As far as I understand your setup, the resource behind the ServiceEntry does not have a sidecar, so if that would be true, the manipulation wouldn't work.
In order to add custom headers you can use a EnvoyFilter of type lua that is applied to the sender's sidecar and can manipulate the traffic on the fly.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: add-custom-header-filter
namespace: test
spec:
configPatches:
- applyTo: CLUSTER
match:
context: SIDECAR_OUTBOUND
cluster:
service: connector
patch:
operation: INSERT_BEFORE
value:
name: envoy.lua
typed_config:
"#type": "type.googleapis.com/envoy.config.filter.http.lua.v2.Lua"
inlineCode: |
function envoy_on_request(request_handle)
response_handle:logInfo("adding custom headers..."");
response_handle:headers():add("X-User-Header", "worked");
end
This filter is applied to every request to the service entry connector by every sidecar in the namespace test on outbound and adds a custom header before any other action done.

Can an istio service be for an arbitrary hostname?

For in-mesh networking (no ingress or egress in my simple example), it seems like the common practice to have the "host" of a virtual service match an existing kubernetes service:
hosts:
- reviews.default.svc.cluster.local
But is this required? If I wanted to create a virtual service for an entirely different host name, is that possible? For example:
hosts:
- review-service.my-servicemesh
And then call the service at http://review-service.my-servicemesh instead of http://reviews (or http://reviews.default) ?
Or to ask this another way: can I have my namespace of istio (virtual) services be entirely decoupled from the namespace of kubernetes services/namespaces?
Yes this can be acheived using istio Service Entry. You can create something like this
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: review-servicemesh
spec:
hosts:
- "review-service.my-servicemesh"
location: MESH_EXTERNAL
ports:
- number: 80
name: http
protocol: HTTP
resolution: STATIC [or even DNS]
endpoints:
- address: <your_endpoint_address>