This type of service does not perform any load-balancing and only implements DNS Service Discovery, based on the Kubernetes DNS Spec. Although this is the simplest and the most basic type of Service, its use is mainly limited to stateful applications like databases and clusters. In these use case the assumption is that clients have some prior knowledge about the application they’re going to be communicating with, e.g. number of nodes, naming structure, and can handle failover and load-balancing on their own.
Some typical examples of stateful applications that use this kind of service are:
The only thing that makes a service “Headless” is the clusterIP: None which, on the one hand, tells dataplane agents to ignore this resource and, on the other hand, tells the DNS plugin that it needs special type of processing. The rest of the API parameters look similar to any other Service:
The corresponding Endpoints resources are still creates for every healthy backend Pod, with the only notable distinction being the absence of hash in Pods name and presence of the hostname field.
In order to optimise the work of kube-proxy and other controllers that may need to read Endpoints, their Controller annotates all objects with the service.kubernetes.io/headless label.
Implementation
This type of service is implemented entirely within a DNS plugin. The following is a simplified version of the actual code from CoreDNS’s kubernetes plugin:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CoreDNS builds an internal representation of Services, containing only the information that may be relevant to DNS (IPs, port numbers) and dropping all of the other details. This information is later used to build a DNS response.
Lab
Assuming that the lab is already setup, we can install a stateful application (consul) with the following command:
make headless
Check that the consul statefulset has been deployed:
$ kubect get sts
NAME READY AGE
consul-server 3/3 25m
Now we should be able to see one Headless Services in the default namespace:
$ kubect get svc consul-server
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
consul-server ClusterIP None <none> 8500/TCP,8301/TCP,8301/UDP,8302/TCP,8302/UDP,8300/TCP,8600/TCP,8600/UDP 29m
To interact with this service, we can do a DNS query from any of the net-tshoot Pods:
kubectl exec -it net-tshoot-8kqh6 -- dig consul-server +search
; <<>> DiG 9.16.11 <<>> consul-server +search
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2841
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: fe116ac7ab444725 (echoed)
;; QUESTION SECTION:
;consul-server.default.svc.cluster.local. IN A
;; ANSWER SECTION:
consul-server.default.svc.cluster.local. 13 IN A 10.244.2.8
consul-server.default.svc.cluster.local. 13 IN A 10.244.1.8
consul-server.default.svc.cluster.local. 13 IN A 10.244.0.6
;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sat Jun 05 15:30:09 UTC 2021
;; MSG SIZE rcvd: 245
Application interacting with this StatefulSet can make use of DNS SRV lookup to find individual hostnames and port numbers exposed by the backend Pods: