loading...

Kubernetes Headless Service Building a headless service in Kubernetes

kaoskater08 profile image Eddie Hale ・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 

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

Create a regular service

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

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

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

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:/# 

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

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

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

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!

Posted on by:

kaoskater08 profile

Eddie Hale

@kaoskater08

#googlecloud #aws #kubernetes

Discussion

markdown guide
 

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)?

 

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

 

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

 

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?

 

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.

 
 

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.

 

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?