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!
Top comments (10)
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.
Nice explaination. I just want to add one practicle example where I am usingn headless service. Have redis cluster with sentinel mode and using java service with redission lib to connect it. Connection string to redission needs all : of redis-sentinel pods.
As Redis cluster is being deployed in kubernetes, a headless service to sentinel nodes is craeted and connections string refers headless service.
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.
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.
So clear!
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?