1. Overview

A Service is a Kubernetes resource that provides a single entry point to one or more pods. Specifically, it exposes a group of pods to the network through a single domain name and IP address. When a request hits the service, the Kubernetes kube-proxy redirects the request to one of the backing pods.

In this tutorial, we’ll learn about a specialized form of a Service resource, known as the headless service, which allows us to obtain the IP addresses of specific backing pods. This capability enables interesting use cases that aren’t possible with the normal Service resource.

We’ll cover its basic configuration and then see a practical application using a hands-on demonstration.

2. Headless Service

In Kubernetes, a headless service refers to a Service resource that does not have a cluster IP address assigned.

To define a headless service, we set the spec.clusterIP field to None in its resource definition:

$ cat headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: headless-svc
spec:
  clusterIP: None
  selector:
    app: web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

After we’ve created the resource on our cluster, we can inspect the details using the kubectl get svc command:

$ kubectl get svc -o go-template='{{ .spec.clusterIPs }}' headless-svc
[None]

The command above uses the -o go-template option to extract the assigned clusterIP field of the service. As expected, the Kubernetes control plane will not assign an IP address to the headless service resource.

3. Characteristics of a Headless Service

When we resolve the domain name of a typical service, the DNS returns a single IP address, which is the cluster IP of the service assigned by the control plane. However, a DNS query of a headless service’s name returns a list of IP addresses that belong to the backing pods.

This capability allows certain use cases that are not possible with normal services. For example, a monitoring service can use a headless service to find out the IP addresses of all the pods to send health check requests. With a regular service, there’s no guarantee as to which backing pod will receive the request.

4. Headless Service in Action

Let’s set up the necessary resources to demonstrate the working of a headless service resource.

First, let’s create a headless service:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: headless-svc-stateful
spec:
  clusterIP: None
  selector:
    app: web-stateful
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
EOF

Next, let’s create a StatefulSet resource that deploys three pods onto the cluster:

$ kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: app-stateful
  labels:
    app: server-stateful
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-stateful
  serviceName: headless-svc-stateful
  template:
    metadata:
      labels:
        app: web-stateful
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
EOF

Critically, the StatefulSet requires a value for the serviceName field. We must point the serviceName to the headless service that’s responsible for exposing the StatefulSet to the network. In our case, it’ll be the headless-svc-stateful resource.

After applying the manifest, the Kubernetes control plane will create three pods:

$ kubectl get pods -l app=web-stateful
NAME             READY   STATUS    RESTARTS   AGE
app-stateful-0   1/1     Running   0          32m
app-stateful-1   1/1     Running   0          31m
app-stateful-2   1/1     Running   0          31m

4.1. Creating an Ephemeral Container

An ephemeral container is a convenient way for us to sideload a container with different images on the pods. This is typically done using an image with more powerful utility tools so that we can debug issues in the pod. In the context of our demonstration, we’ll create an ephemeral container with DNS-related utils onto an existing pod so that we can test out the various behaviors of our headless service.

Using the kubectl debug command, let’s create an ephemeral container:

$ kubectl debug -it app-stateful-0 --image=slongstreet/bind-utils:latest -- bash
Defaulting debug container name to debugger-2crmp.
$ 

The command above starts an ephemeral container using the image slongstreet/bind-utils in the app-stateful-0 pod. We use the slongstreet/bind-utils image because it contains the nslookup command that we’ll use for resolving the service domain names. Then, we drop into the container’s shell using the bash command.

4.2. Resolving the Headless Service’s IP Address

Within the ephemeral container, let’s resolve the IP address of the headless-svc-stateful using the nslookup command:

$ nslookup headless-svc-stateful
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   headless-svc-stateful.default.svc.cluster.local
Address: 10.244.0.11
Name:   headless-svc-stateful.default.svc.cluster.local
Address: 10.244.0.12
Name:   headless-svc-stateful.default.svc.cluster.local
Address: 10.244.0.10

The query returns a list of three IP addresses for that domain name, each of which is the IP address of the pod that’s targeted by the service.

One caveat with the response is that we don’t know which pods each of the IP addresses belongs to. Fortunately, it’s a simple task with a headless service to figure it out.

4.3. Resolving a Specific Pod’s IP Address

To resolve the IP address of a specific backing pod of the headless service, we add the pod’s name as a subdomain to the headless service’s domain name. Specifically, we can look at the IP address of the app-stateful-1 pod using the domain name app-stateful-1.headless-svc-stateful:

$ nslookup app-stateful-1.headless-svc-stateful
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   app-stateful-1.headless-svc-stateful.default.svc.cluster.local
Address: 10.244.0.11

Instead of returning the IP address of all the backing pods, the command returns just the IP address for that specific pod, app-stateful-1.

5. Conclusion

In this tutorial, we’ve briefly looked at the Service resource in the context of Kubernetes and learned that it’s essentially a Service resource without a cluster IP. Due to their lack of a cluster IP address, we learn that headless services behave differently from the usual Service resource.

Finally, we’ve worked through an example that shows how a headless service can be useful in Statefulset pods to figure out the IP address of a specific pod.