How to communicate with Microservices in Kubernetes? — Service Discovery

How to communicate with Microservices in Kubernetes? — Service Discovery

Imagine a busy juice center serving varieties of juices. Frontdesk receives the request from the customer and conveys the request to the respective juice agent. Once an agent is done with preparing the juice, returns it to the frontdesk. Frontdesk, in turn, serves the juice to the customer.
If we have to design a simple microservice application for this purpose, it may look like the following:

Screenshot 2022-01-28 at 6.48.04 AM.png

Here, frontdesk, mango, and apple are microservices. These are deployed using Deployment objects in the Kubernetes cluster. You can refer to this post for details regarding deployment in Kubernetes. With just deployment, our juice center is not fully functional. frontdesk needs to communicate with mango and apple services. Also, a user should be able to place a request with frontdesk and receive the order.

Service Discovery

Service Discovery in Kubernetes facilitates communication between microservices and also with the outside world. So, let’s explore service discovery along with implementing the above design for the juice center.

After receiving the request from a user, frontdesk needs to convey it to the corresponding juice agent. For this, frontdesk can use IP of mango/apple Pods. But IP addresses are bound to change as Pods get terminated and recreated. So, we need a reliable way of identifying Pods and hence Deployment objects.

ClusterIP

Kubernetes provides Service objects as a reliable interface for a deployment. That means each deployment can have its service object.

As with other Kubernetes objects, service objects too can be created with YAML configuration:

apiVersion: v1  
kind: Service # Indicates type of object.  
metadata:  
  name: apple-service # Name of the service, which is used to make api calls  
spec:  
  type: ClusterIP # Optional. ClusterIP is default value.  
  selector:  
    app: apple-app  
  ports:  
    - protocol: TCP  
      port: 80 # Port on which service will receive the traffic  
      targetPort: 8081 # Port exposed by pod onto which service delivers/receives the traffic

The above config defines a service for apple deployment. This service is of type ClusterIP. That means it is accessible only from within the cluster.

A service is mapped to deployments using labels. Here, it is apple-app labeled deployments. Labels make it a reliable way of identifying a group of Pods without worrying about their IP.

Service receives traffic on value specified for port. Then, it redirects the traffic onto the corresponding pod's targetPort value. Pod's targetPort is same as corresponding container's port.

If we get the service details, we see that Cluster-IP is assigned to mango-service.

We can access the service either using cluster IP or service name. Here, mango-service is the service name.

This service holds the information of all pods it is supposed to represent. These are called endpoints. We can see the endpoints and other details as follows:

Now, frontdesk can call apple service using service name, as follows:

You might be wondering how Kubernetes infer IP address from service name apple-service. Well, Kubernetes maintains a cluster-specific DNS service, which maintains the record of service names with their corresponding IP addresses. That's a topic for a separate discussion.

Similarly, frontdesk accesses mango-service as well.

frontdesk service is not visible to the outside world. Then, how a user can place a request with frontdesk? For this, we can use either NodePort or LoadBalancer type of services.

NodePort

A NodePort service exposes a defined Port from all nodes in the cluster.
That means the service can be accessed using that particular port if we have access to any node in the cluster.

Corresponding YAML configuration:

apiVersion: v1  
kind: Service  
metadata:  
  name: frontdesk-service  
spec:  
  type: NodePort  
  selector:  
    app: frontdesk-app  
  ports:  
    - port: 80  
      targetPort: 8080  
      # Optional field  
      # By default, Kubernetes assigns the nodeport from the range 30000-32767  
      nodePort: 30007 # Here, we are explicitly assigning the nodeport

To access the service, we need to know IP of node:

Now, with node IP and node port, we will be able to access the frontdesk:

LoadBalancer

What if a user doesn’t have access to the node in the cluster, which is the common scenario. Then, we have service type LoadBalancer.

apiVersion: v1  
kind: Service  
metadata:  
  name: juice-service  
spec:  
  selector:  
    app: frontdesk-app  
  ports:  
    - protocol: TCP  
      port: 9000  
      targetPort: 8080  
  type: LoadBalancer

Load balancers allow outside traffic into particular labeled deployments. Here, juice-service will be directing traffic into frontdesk-app labeled deployments.
Load balancer services expose an external IP for the outside world to connect. Then, it connects with the cluster using a NodePort.

Normally, load balancers are provided by cloud providers like AWS, Azure, GCP, etc.
In this example, we are using a load balancer provided by k3s for local use.

After the creation of load balancer service:

We can access the frontdesk using external ip and the port 9000.

Since we have already set up the ClusterIP services for apple and mango microservices, we can make a call to frontdesk to get appropriate juice.

Seems mango juice is in demand 😊

Sample application code is available here.

In summary, service objects in Kubernetes expose reliable service name which other services can use to communicate with deployments. Services map to deployments using labels. Services set the access level with different types such as ClusterIP, NodePort, and LoadBalancer.

I hope this is useful in clarifying service discovery in Kubernetes.
Let me know in the comments if you have any queries/suggestions.