A crucial feature of the Istio Service Mesh is that it grants you absolute control over how you want to route traffic to a service. Each service on the Istio service mesh has a unique network identity that it receives from the underlying host, i.e., Kubernetes. For example, a service named foo provisioned in a namespace named bar will have the FQDN (Fully Qualified Domain Name) foo.bar.svc.cluster.local, which also serves as its network identity. Other services within the cluster can use the network identity of foo to send requests to it, which will reach one of the pods executing an instance of the service.
For a service accessible to clients outside the cluster, the clients can use an address that resolves to the IP address of the Istio ingress gateway. After evaluating the request, the gateway will route the request to the destination service, thus abstracting the external client form the network identity of the destination service. For example, as depicted in the following diagram, the client of a hypothetical service, which is addressable at saturn.planet.net, is oblivious of the network identity of the service within the cluster.

Figure 1 Ingress Gateway acting as NLB
The previous diagram depicts the actual path of a request originating outside the cluster to the Saturn service. The FQDN, saturn.planet.net, used by the client, resolves to the IP address of the Istio ingress gateway. The gateway interacts with the Istio service registry to direct the incoming request to an instance of the Saturn service. Note that communication between the actual service and the gateway also involves a sidecar proxy, but it is excluded from the diagram for brevity.
The Challenge
Assigning a hostname to an internet-facing microservice brings indirection between the network identity of the service within the cluster and the address on which it is accessible to the external clients. With the indirection, a service can easily change its location (inside and outside the cluster) and its name without affecting its external clients.
The level of flexibility that services offer to external clients is unavailable to the internal clients since service to service communication within the cluster takes place using the network identity of the service. The lack of indirection means that developers can’t rename the services or namespaces without also affecting the clients. Moreover, porting services to infrastructure outside the cluster or moving them to another cluster also affects their name, which in turn requires changes to the clients of the services. Because of such issues, a system with many interconnected microservices needs to establish abstraction between the addresses and identities of services operating within the cluster.
Solution
A potential solution for systems that have all their services exposed to the internet via ingress gateway is to use the external endpoints of the services for communication. Using an external endpoint for service to service communication is a bad practice as such communication should still take place within the cluster for performance and security reasons. In networking, this form of communication is known as hairpinning, which in the context of Kubernetes, translates to service to service communication in which requests from a service leave the cluster and then re-enter the cluster to reach the destination service.
Today, we will discuss a potential solution to this problem by configuring the DNS used by Istio and the DNS used by Kubernetes, which is CoreDNS for both the systems. In Istio, you can add custom DNS records to the service registry using the ServiceEntry configuration resource. Envoy uses the service registry of Istio and Kubernetes to detect the location of any service in the cluster. Istio uses a CoreDNS plugin to read the service entries and associate the IP addresses of services to their host addresses. The DNS plugin is deployed to the cluster when you install Istio with the following installation option.
For a cluster that has both the Kubernetes CoreDNS and Istio CoreDNS services running, we can use the approach illustrated in the following diagram to assign a host address to service and use the host address to communicate with it within the cluster.

Figure 2 Resolving IP address of service
Let’s briefly discuss what is happening here. Istio CoreDNS, which is deployed as istiocoredns service in the cluster, registers all the service entries as DNS records. A limitation of the Istio CoreDNS plugin is that it ignores service entries that don’t have an associated IP address (see source). Therefore, our service bar must have a fixed cluster IP address. Since istiocoredns is responsible for service entry DNS records, we configure kube-dns (Kubernetes CoreDNS) to forward all resolve requests for domain names managed by Istio DNS to istiocoredns. Assuming that we want to assign the FQDN thebar.internal to the service bar, the following is how the service foo will communicate with the service bar within the cluster.
- A service entry record that maps the FQDN internal to the cluster IP of the bar service is applied to the mesh so that the DNS record is available to the istiocoredns service. Kubernetes DNS service kube-dns is configured to forward any resolve requests for domain name internal to istiocoredns.
- Service foo sends a request to the bar service at the address http://thebar.internal.
- Envoy sends a resolve request to the kube-dns service to resolve the IP address of the request.
- The kube-dns service forwards the request to istiocoredns.
- The istiocoredns service returns the IP address of the service to kube-dns.
- Envoy uses the resolved IP address to communicate with the service bar.
- Envoy sends the request to the bar
Let’s build a simple demo to illustrate this workflow.
Demo
Before I start with the demo, I would like to point you to the code repository for this sample which is located here.
The prerequisite of this demo is a Kubernetes cluster with Istio deployed to it. I assume that you are using CoreDNS in both Kubernetes and Istio, which is the default DNS resource in the recent versions of Kubernetes. I am using Docker Desktop for Windows with Kubernetes enabled for local development, but feel free to use whatever makes you happy.
I built a simple Nodejs REST API that returns a list of fruits available in a country based on the country code passed to it as an argument. To test the service on the dev box, execute the following command to create a container and bind it to port 3000 on the localhost.
- $ docker run -p 3000:3000 --name fruits-api istiosuccinctly/fruits-api:1.0.0
Use another terminal instance to send a request to the API to fetch fruits and their prices for the country Australia whose country code is au. Other country codes supported are ind and usa.
- $ curl http://localhost:3000/api/fruits/au
- {"nectarine":2.5,"mandarin":2.3,"lemon":1.1,"kiwi":2.6}
Let’s deploy this service to our cluster now. The first resource we will need is a namespace with the label istio-injection set to enabled so that Istio can inject a sidecar to all the service pods within this namespace. The following listing presents the definition of the namespace named micro-shake-factory.
- apiVersion: v1
- kind: Namespace
- metadata:
- name: micro-shake-factory
- labels:
- istio-injection: enabled
Next, we will create a deployment and a service for the fruits-api service using the following specification.
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: fruits-api-deployment-v1
- namespace: micro-shake-factory
- spec:
- selector:
- matchLabels:
- app: fruits-api
- replicas: 1
- minReadySeconds: 1
- progressDeadlineSeconds: 600
- template:
- metadata:
- labels:
- app: fruits-api
- version: "1"
- spec:
- containers:
- - name: fruits-api
- image: istiosuccinctly/fruits-api:1.0.0
- imagePullPolicy: IfNotPresent
- resources:
- limits:
- cpu: 1000m
- memory: 1024Mi
- requests:
- cpu: 100m
- memory: 100Mi
- ports:
- - name: http-fruits-api
- containerPort: 3000
- env:
- - name: app_version
- value: "1"
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: fruits-api-service
- namespace: micro-shake-factory
- spec:
- selector:
- app: fruits-api
- ports:
- - name: http-fruits-api-service
- port: 80
- targetPort: http-fruits-api
- clusterIP: 10.103.1.1

Join the conversation! Your thoughts help the community grow.