What is Headless Service in Kubernetes?

adil
5 min readJul 31, 2023

Kubernetes has a penchant towards rebranding conventional practices with fancy new titles.

The headless service is actually a DNS load balancer in the cluster.

Photo by CHUTTERSNAP on Unsplash

What Does a Service Do in Kubernetes?

The service may have been called “Load Balancer for Pods

When you create a regular service in Kubernetes, the traffic is distributed among the pods. The regular service serves as a load balancer. When you query the DNS address of a regular service, the DNS server will provide the IP address of the service.

What is Headless Service?

If you create a Headless service, the clients will reach out directly to the pods, bypassing the service load balancer. So if you query the DNS address of the headless service, the DNS server will return the IP addresses of all pods.

The headless service may have been called “DNS Load Balancing Service For Pods

How do I create a headless service?

You will need to set clusterIP: none in the service creation yaml file.

Let’s deploy two containers that will reply with their hostnames:

01-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: show-hostname-deployment
spec:
replicas: 2
selector:
matchLabels:
app: show-hostname-pod
template:
metadata:
labels:
app: show-hostname-pod
spec:
containers:
- name: show-hostname-container
image: stenote/nginx-hostname
ports:
- containerPort: 80
➜  ~ kubectl create -f 01-deployment.yml
deployment.apps/show-hostname-deployment created

Check that the pods are operating and take note of their IP addresses:

➜  ~ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
show-hostname-deployment-54674db7b4-7tbs5 1/1 Running 0 20m 192.168.57.84 ip-192-168-57-8.eu-west-1.compute.internal <none> <none>
show-hostname-deployment-54674db7b4-dqwcd 1/1 Running 0 20m 192.168.63.71 ip-192-168-57-8.eu-west-1.compute.internal <none> <none>

The pods’ IP addresses: 192.168.57.84 and 192.168.63.71

02-service-headless.yaml

apiVersion: v1
kind: Service
metadata:
labels:
app: show-hostname-headless
name: show-hostname-headless
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: show-hostname-pod
clusterIP: None

03-service-regular.yaml

apiVersion: v1
kind: Service
metadata:
labels:
app: show-hostname-regular
name: show-hostname-regular
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: show-hostname-pod

Create both services:

➜ ~ kubectl create -f 02-service-headless.yml
service/show-hostname-headless created
➜ ~ kubectl create -f 03-service-regular.yml
service/show-hostname-regular created

Let’s check the services:

➜  ~ kubectl get svc -o wide -l app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
show-hostname-headless ClusterIP None <none> 80/TCP 3m1s app=show-hostname-pod
show-hostname-regular ClusterIP 10.100.54.41 <none> 80/TCP 100s app=show-hostname-pod

Please notice that the show-hostname-headless service has no a ClusterIP, the other one has a ClusterIP (10.100.54.41).

Create a container that has DNS tools:

kubectl run dns-client -it --image=tutum/dnsutils

Let’s check the DNS records of each service.

The headless service’s DNS record:

➜  ~ kubectl exec -it dns-client -- /bin/bash
root@dns-client:/# dig show-hostname-headless +search +short
192.168.63.71
192.168.57.84

Please notice that the headless service’s DNS records are actually the pods’ IP addresses.

The regular service’s DNS result:

➜  ~ kubectl exec -it dns-client -- /bin/bash
root@dns-client:/# dig show-hostname-regular +search +short
10.100.54.41

Please notice that the regular service’s DNS record is actually the service’s IP address.

As you probably know that you can’t ping a regular service’s IP address:

root@dns-client:/# ping show-hostname-regular -c 4
PING show-hostname-regular.default.svc.cluster.local (10.100.54.41) 56(84) bytes of data.
--- show-hostname-regular.default.svc.cluster.local ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3065ms

However, you can ping a headless service:

root@dns-client:/# ping show-hostname-headless -c4
PING show-hostname-headless.default.svc.cluster.local (192.168.57.84) 56(84) bytes of data.
64 bytes from 192-168-57-84.show-hostname-regular.default.svc.cluster.local (192.168.57.84): icmp_seq=1 ttl=63 time=0.130 ms
64 bytes from 192-168-57-84.show-hostname-regular.default.svc.cluster.local (192.168.57.84): icmp_seq=2 ttl=63 time=0.068 ms
64 bytes from 192-168-57-84.show-hostname-regular.default.svc.cluster.local (192.168.57.84): icmp_seq=3 ttl=63 time=0.057 ms
64 bytes from 192-168-57-84.show-hostname-regular.default.svc.cluster.local (192.168.57.84): icmp_seq=4 ttl=63 time=0.057 ms
--- show-hostname-headless.default.svc.cluster.local ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3068ms
rtt min/avg/max/mdev = 0.057/0.078/0.130/0.030 ms

In fact, you’re not pinging a headless service, you’re pinging one of the pods. Because in Kubernetes you can ping a pod but not a service. Since the DNS records of the headless service are actually the IP address of the pods, we think we are pinging the headless service.

The pros and cons of using the headless service

The default TTL of a DNS record is 5 second in the DNS component of Kubernetes (CoreDNS). But imagine you enable a headless service and one of your microservices code is not updating its DNS cache. All requests from that microservice will go to the same Pod. It can be a scalability issue for your infrastructure.

You may not need a regular service if you have only 1 pod (e.g.: database pod).

You may need a headless service if all your pods need to be in sync with each other. So they can get IP address of all pods with DNS result of headless service.

A headless service can be used for service discovery. Because when you add/remove a new pod, the IP addresses will also be added/removed in DNS:

➜  ~ kubectl scale --replicas=3 deployment/show-hostname-deployment
deployment.apps/show-hostname-deployment scaled

List of pods:

➜  ~ kubectl get pods -o wide -l app
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
show-hostname-deployment-54674db7b4-7tbs5 1/1 Running 0 56m 192.168.57.84 ip-192-168-57-8.eu-west-1.compute.internal <none> <none>
show-hostname-deployment-54674db7b4-dqwcd 1/1 Running 0 56m 192.168.63.71 ip-192-168-57-8.eu-west-1.compute.internal <none> <none>
show-hostname-deployment-54674db7b4-p2dgd 1/1 Running 0 37s 192.168.53.3 ip-192-168-57-8.eu-west-1.compute.internal <none> <none>

The new IP address (192.168.53.3) added to DNS records:

root@dns-client:/# dig show-hostname-headless +search +short
192.168.63.71
192.168.53.3
192.168.57.84

--

--