DEV Community

loading...

Kubernetes Headless Service Building a headless service in Kubernetes

Eddie Hale
#googlecloud #aws #kubernetes
・3 min read

Hello! This is my first post to dev.to and excited to be a part of this great community.

A few weeks ago during the development of one of our apps we came across an issue where we needed to communicate with all associated pods instead of load-balancing. Our backend has 7 pods, all with the same image but different secrets to update databases at different locations.

Instead of writing a service for each location and creating a ton of overhead with repetitive yaml files, changing the deployed service to a headless service was the best way to go.


What is a headless service?

A headless service is a service with a service IP but instead of load-balancing it will return the IPs of our associated Pods. This allows us to interact directly with the Pods instead of a proxy. It's as simple as specifying None for .spec.clusterIP and can be utilized with or without selectors - you'll see an example with selectors in a moment.

Check out this sample config:

apiVersion: v1
kind: Service
metadata:
  name: my-headless-service
spec:
  clusterIP: None # <--
  selector:
    app: test-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000 
Enter fullscreen mode Exit fullscreen mode

See it in action

Create a deployment with five pods.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-deployment
  labels:
    app: api
spec:
  replicas: 5
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: eddiehale/hellonodeapi
        ports:
        - containerPort: 3000
Enter fullscreen mode Exit fullscreen mode

Create a regular service

apiVersion: v1
kind: Service
metadata:
  name: normal-service
spec:
  selector:
    app: api
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
Enter fullscreen mode Exit fullscreen mode

And a headless service

apiVersion: v1
kind: Service
metadata:
  name: headless-service
spec:
  clusterIP: None # <-- Don't forget!!
  selector:
    app: api
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
Enter fullscreen mode Exit fullscreen mode

Apply the yaml and verify everything deployed correctly:

$ kubectl apply -f deployment.yaml
$ kubectl get all
NAME                                 READY   STATUS    RESTARTS   AGE
pod/api-deployment-f457fbcf6-6j8f9   1/1     Running   0          5s
pod/api-deployment-f457fbcf6-9gvbp   1/1     Running   0          5s
pod/api-deployment-f457fbcf6-kqbds   1/1     Running   0          5s
pod/api-deployment-f457fbcf6-m76l9   1/1     Running   0          5s
pod/api-deployment-f457fbcf6-qzhxw   1/1     Running   0          5s

NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/headless-service   ClusterIP   None             <none>        80/TCP    5s
service/kubernetes         ClusterIP   10.96.0.1        <none>        443/TCP   45h
service/normal-service     ClusterIP   10.109.192.226   <none>        80/TCP    5s

NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/api-deployment   5/5     5            5           5s

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/api-deployment-f457fbcf6   5         5         5       5s
Enter fullscreen mode Exit fullscreen mode

Now that its all running, deploy a Pod and execute a few commands to test.

$ kubectl run --generator=run-pod/v1 --rm utils -it --image eddiehale/utils bash
If you don't see a command prompt, try pressing enter.
root@utils:/# 
Enter fullscreen mode Exit fullscreen mode

Lets run nslookup on each service to see what DNS entries exist. If we nslookup normal-service one DNS entry and IP is returned, where nslookup headless-service returns the list of associated Pod IPs with the service DNS:

root@utils:/# nslookup normal-service
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   normal-service.default.svc.cluster.local
Address: 10.109.192.226

root@utils:/# nslookup headless-service
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   headless-service.default.svc.cluster.local
Address: 10.1.0.41
Name:   headless-service.default.svc.cluster.local
Address: 10.1.0.39
Name:   headless-service.default.svc.cluster.local
Address: 10.1.0.38
Name:   headless-service.default.svc.cluster.local
Address: 10.1.0.40
Name:   headless-service.default.svc.cluster.local
Address: 10.1.0.37
Enter fullscreen mode Exit fullscreen mode

Clean up

Exit the utils pod:

root@utils:/# exit
exit
Session ended, resume using 'kubectl attach utils -c utils -i -t' command when 
the pod is running
pod "utils" deleted
Enter fullscreen mode Exit fullscreen mode

Delete the services and deployment

$ kubectl delete svc headless-service normal-service && kubectl delete deployment api-deployment
service "headless-service" deleted
service "normal-service" deleted
deployment.extensions "api-deployment" deleted
Enter fullscreen mode Exit fullscreen mode

Headless-services allow us to reach each Pod directly, rather than the service acting as a load-balancer or proxy. This can have many use cases and I'd love to hear your experiences and thoughts in the comments!

Discussion (9)

Collapse
satishrao84 profile image
satishrao84

Hey I still don't understand. "Headless-services allow us to reach each Pod directly"... how? If not through ClusterIP, then how do you reach a Pod directly? The only way to reach something is through their IP address right?
What are the use cases for Headless-Services?

Collapse
chukmunnlee profile image
chukmunnlee

Headless service is typically used with StatefulSets where the name of the pods are fixed. This is useful in situations like when you're settling up a MySQL cluster where you need to know the name of the master. StatefulSets appends an ordinal number to the name of the pod and it will always assign the same ordinal number of the pod is restarted or migrated by the scheduler.

In the case of a regular Deployment, ReplicaSet appends a hash to the pod's name making addressing specific pods difficult. The hash sort of anonomize the pods.

So if your application is stateless then you will just use with a 'regular' service because you don't care which pod you get. They're all the same.

Collapse
ahujadipak3 profile image
Dipak Ahuja • Edited

I have similar kind of use-case where I need to have these PODs accessible from IP:port externally. Were you able to access PODs outside of the cluster (external world)?

Collapse
kaoskater08 profile image
Eddie Hale Author

In this example, no, but you could access the pods by exposing your service or adding an ingress.

Collapse
mg40 profile image
MG40

Let's assume I do add an ingress, I get host unreachable. I see that the service & the ingress has endpoints.

Collapse
wangjunru profile image
charlie

The problem is you can not use the port 80 defined in service but only 3000 the pods exposed. Am I right? This's my experiment shown.

Collapse
luanau profile image
luanau

From the utility pod you can,
curl normal-service:80
curl 10.1.0.41:3000 #a headless service pod

The response is something like,
{"message":"Hello World from your container!","hostname":"api-deployment-f457fbcf6-6j8f9"}

The headless service has no dns name and no ip.

Collapse
taragrg6 profile image
taragurung

So clear!

Collapse
anarcher profile image
anarcher

I have a question about port with headless service.
If service port is not same as target port with headless service, Can l use service port with pod ip?