
Continuing from our previous cloud bits post about service configuration, we will explore the topic of service discovery in this post. Before even diving into the formal definition lets try to understand the need for service discovery through an example.
Setting the Stage
Consider yourself as the only engineer in a startup which excitedly jumped on the microservice bandwagon to build their MVP(Never do this). The product consists of a checkout service, a payment service & a notification service. Whenever a customer places an order, the checkout service calls the payment service to charge the customer’s payment method & then finally send a notification to the customer. This works perfectly for the MVP as we know what ports all 3 services are running in. The MVP turned out to be a success & you start getting more customer signups on your product. Now you notice that your simple architecture cannot handle the scale & you will need to split the traffic into multiple instances. Once you start splitting the traffic, your checkout service needs to keep track of all instances of your payment service. Instances can go down & come back up or they can be spun up on a new host. How will the checkout service know which instance went down & which nodes are up. This is lot of dynamic information that needs to be tracked in order for your application to work.
This is what paves the way for the concept of service discovery. Your services should not be concerned about how to discover their dependencies. They should rather focus on the core business workflow while you use a solution that just works out of the box to solve this problem. So in formal words, service discovery is a mechanism of automatic detection of services in a network. It consists of two operations:
- Discovery of the dependency service
- Registration of a new instance of a service
The expected end state is that the checkout service should just be able to say that I want to communicate to payment service. It should not care about:
- Where the payment service is deployed?
- How many instances of payment service are deployed?
- Whether a particular instance of payment service goes down or a new instance is brought up?
Diving into the internals
So if we have established that we don’t want our services to be concerned about keeping track of other services then how do they essentially know where these services are deployed & how do they communicate with them? This work is done by service discovery server & client.
The discovery server is responsible for registering new service deployments while discovery client can be used by a services to route request to one of the deployment instances of the dependency service.
Service Discovery in Action
Now that we have the basic understanding of service discovery in place, lets do some hands on demo. We will pick up the over-complicated example which we discussed in the last post consisting of a webapp
which serves a greet
endpoint to send out a greeting in a specified language. In order to process this request, webapp
service communicates with a greeting-service
that provides translation of hello
in three different languages & a name-service
which just provides the name(which is my name as I am the one writing the post :P)

Our expectation here is that the webapp
service should be able to communicate with name-service
& greeting-service
without needing to figure out which ports they are deployed to or how many instances of each service are in the network. To achieve this we will solve this problem using three different methods.
Service Discovery using Spring Eureka
Eureka is a service discovery server & client from Netflix Eureka project which is now part of Spring Cloud project. We start by creating a Eureka server which will be used by services to register their instances after getting deployed. So both name-service
as well as the greeting-service
will register with the Eureka server while webapp
service will make use of discovery client to connect with service instances. The discovery client is configured as below:
@Configuration
public class RestConfig {
@Bean
@LoadBalanced
public RestClient.Builder loadBalancedRestClientBuilder() {
return RestClient.builder();
}
@Bean
public RestClient loadBalancedRestClient(RestClient.Builder builder) {
return builder.build();
}
}
The above RestClient
can be used by the webapp
service to communicate with the dependency services by just providing the service name as below:
public String buildGreeting() {
String name = restClient.get()
.uri("http://name-service" + NAME_SERVICE_ENDPOINT)
.retrieve()
.body(String.class);
String greeting = restClient.get()
.uri("http://greeting-service" + GREETING_SERVICE_ENDPOINT)
.retrieve()
.body(String.class);
return greeting + " " + name;
}
For demo, we have set the server.port = 0
so that the Spring framework specifies a random port for our services as this is a good test that service discovery mechanism is working correctly. We first start up our Eureka server & then spin up one instance of name-service
& two instances of greeting-service
followed by our webapp
service. We can see in the demo below that all our services register with the Eureka server on startup & then we are successfully able to invoke the endpoints for webapp
service which communicates with the downstream services.
You can find all the code for this demo on this Github branch.
Service Discovery using Consul
Consul is a cloud-native solution created by HashiCorp that provides you with service discovery mechanism similar to Eureka server though this is independent of the Spring framework & can be used across different tech stacks. For demo purposes, we spin up a Docker container for running Consul locally as below:
version: '3.8'
services:
consul:
image: hashicorp/consul:latest
container_name: consul-server
ports:
- "8300:8300"
- "8301:8301"
- "8302:8302"
- "8500:8500"
- "8600:8600/udp" # DNS
command: agent -server -bootstrap -ui -client=0.0.0.0
networks:
- consul-network
networks:
consul-network:
driver: bridge
We now start our services & a nice part about using Spring framework is that it provides a great abstraction while using discovery client as the client config that we saw in case of Eureka server remains unchanged. We just add the dependency for Spring Cloud Consul & the framework takes care of the rest. Lets see this in action
Consul also provides with a helpful dashboard to track service health for all the services you have deployed. You can find all the code for this demo on this Github branch.

Service Discovery using Kubernetes
Now we enter the ecosystem of Kubernetes where we don’t need to rely upon any external projects such as Eureka or Consul for service discovery. Kubernetes provides support for service discovery by using a built-in DNS server & routes request to services using kube-proxy. Regardless of how many instances you are running for your service, Kubernetes exposes a single outward facing endpoint to communicate with the service while balancing the load across all pods.
For this demo, we first deploy our services to Kubernetes using the following manifest. A sample manifest for greeting-service
looks as below(Similar manifest applies to other services). We deploy 3 replicas for each service & set the service type to ClusterIP
for both greeting-service
& name-service
while use LoadBalancer
service type for webapp
service to expose it to outside world.
# deployment/greeting-service-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: greeting-service
namespace: service-discovery
spec:
replicas: 3
selector:
matchLabels:
app: greeting-service
template:
metadata:
labels:
app: greeting-service
spec:
containers:
- name: greeting-service
image: varunu2892/greeting-service:latest
ports:
- containerPort: 8002
---
apiVersion: v1
kind: Service
metadata:
name: greeting-service
namespace: service-discovery
spec:
selector:
app: greeting-service
ports:
- protocol: TCP
port: 80
targetPort: 8002
type: ClusterIP
Let us now see this in action. We have set a custom namespace service-discovery
for our demo & all our services will be running in this same namespace. Once the services are deployed & we have pods in healthy state, we enable port forwarding for webapp
service to send requests to the endpoint. As the services are within the same namespace, webapp
service is able to communicate with the dependency services using the service name. Here is a demo which shows the above workflow in action. You can find the code for this demo on this Github branch.
Service discovery is a powerful concept in microservices architecture that can assist you in scaling your services without having to worry about dependency configuration. Hope you were able to learn something out of this post. As part of the next post, I will cover concepts around API gateway. Till then happy learning.