skip to content

What does setting clusterIP: None on a Kubernetes Service do, and when would you choose that over a Service with a normal virtual IP?

level: middleimportance: should knowfreq 52%

answer

  1. clusterIP: None -> no VIP, no proxying
  2. DNS returns pod IPs, one A record per ready pod
  3. <pod>.<svc>.<ns>.svc.cluster.local for StatefulSet peers
  4. gRPC/HTTP2 client-side LB needs the full address list
  5. clients must re-resolve; stale DNS = dialling dead pods

basics

~20 s

It makes the Service headless: no virtual IP is allocated and no load balancing happens. Cluster DNS instead returns the individual pod IPs for the Service name, letting clients see and address each backing pod directly.

solid answer

~50 s

`clusterIP: None` creates a **headless** Service. Kubernetes allocates no virtual IP, programs no forwarding rules, and does no load balancing. What remains is the selector-to-backend mapping plus DNS: a lookup of the Service name returns **A/AAAA records for the ready pod IPs** rather than a single VIP. You choose it when the client, not the cluster dataplane, must control which instance it talks to: - **StatefulSet peers.** The governing headless Service gives each pod a stable per-pod name `<pod>.<svc>.<ns>.svc.cluster.local`, which is how quorum systems (databases, brokers, coordination services) address specific members. - **Client-side load balancing.** gRPC and similar clients want the full address list so they can hold connections to every backend and balance per-request, which an L4 VIP cannot do for long-lived HTTP/2 connections. - **Discovery.** Anything that needs to enumerate peers. A headless Service with **no selector** and `type: ExternalName`-like usage is different again; a selector-less headless Service just serves whatever backends you publish manually.

code

yaml · 31 lines
yaml
apiVersion: v1
kind: Service
metadata:
  name: db
spec:
  clusterIP: None
  selector:
    app: db
  ports:
    - name: pg
      port: 5432
      targetPort: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: db
spec:
  serviceName: db
  replicas: 3
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - name: pg
          image: postgres:17

go deeper

for a junior

Know that clusterIP: None means no virtual IP and that DNS returns the pod IPs instead.

for a middle

Add the StatefulSet per-pod name pattern and the fact that no load balancing or failover happens at the Service layer.

for a senior

Bring the operational consequences: gRPC/HTTP2 client-side balancing, DNS caching and re-resolution, publishNotReadyAddresses for peer bootstrap, and large record sets.

for a principal

Frame it as where load-balancing responsibility should live - dataplane, client library, or mesh - and what that implies for client library standards and failure behaviour across the platform.

## What "headless" removes A normal Service is two things: a **name** and a **virtual IP with load balancing**. Setting `spec.clusterIP: None` keeps the first and removes the second. No VIP is allocated (`kubectl get svc` shows `None` under CLUSTER-IP), no dataplane rules are programmed for it, and no request is ever rewritten by the node's proxy layer. The selector still runs, so the Service still tracks which pods back it; that information is simply exposed through DNS instead of through packet rewriting. Cluster DNS answers a query for a headless Service name with one A (or AAAA) record **per ready backing pod IP**. A client resolving the name therefore receives the whole set. If no pod is ready, there are no records at all, so unlike a VIP Service, an empty backend set is visible at resolution time. ## Per-pod DNS names The feature that makes headless Services central to stateful workloads is the per-pod record. When a pod is created by a StatefulSet whose `spec.serviceName` names a headless Service, the pod also gets an individually addressable name: `<pod-name>.<service-name>.<namespace>.svc.cluster.local` Because StatefulSet pod names are stable and ordinal (`db-0`, `db-1`, `db-2`), the addresses are stable too, surviving restarts and rescheduling. That is what lets clustered software written before Kubernetes - which expects a fixed list of peers in its config - run unmodified: seed lists like `db-0.db,db-1.db,db-2.db` remain correct forever, even though the pod IPs behind them change. Headless Services also publish SRV records for their named ports, which peer-discovery libraries use to enumerate members and their ports in one query. ## When a VIP is the wrong tool Three recurring situations. **Individual addressing.** A quorum-based store needs to contact a *specific* replica - the leader, or a particular shard owner. A VIP that spreads connections randomly makes that impossible. **Client-side load balancing.** L4 Service load balancing chooses a backend per **connection**, not per request. With HTTP/2 or gRPC, a client opens one long-lived connection and multiplexes thousands of requests over it, so all of them land on whichever pod won the initial coin flip; scaling up the deployment does not move existing traffic. Pointing a gRPC client at a headless Service (`dns:///svc.ns.svc.cluster.local`) gives it every pod IP so it can open a subchannel to each and balance per request, and re-resolve as membership changes. **Enumeration.** Sharded caches, peer gossip, and operators that need to talk to each replica in turn. ## Costs and caveats Headless Services push responsibility to the client, which brings real costs. - **DNS caching.** Many clients and language runtimes cache resolution results, sometimes forever. A client that resolves once at startup will keep dialling dead pod IPs after a rollout. Clients used with headless Services must re-resolve, honour TTLs, and handle connection failure by re-resolving rather than retrying the same IP. - **No failover for you.** With a VIP, the dataplane simply stops sending traffic to unready pods. With headless, if your client picked a pod that has since gone away, nothing intervenes. - **Record set size.** A headless Service in front of hundreds of pods returns a large answer; some resolvers truncate to UDP limits and force TCP retries. - **`publishNotReadyAddresses`.** Clustered software often must discover peers *before* readiness, chicken-and-egg style; setting this to true makes DNS return all pods, ready or not. It is exactly wrong for request-serving traffic. ## Headless without a selector A headless Service may also omit the selector. Then no pod tracking happens and DNS serves whatever backends you publish by hand - a way to give an out-of-cluster set of IPs a stable in-cluster name without a proxy hop. Compared with `type: ExternalName`, this returns A records for IPs you control rather than a CNAME to a foreign hostname. ## Quick decision rule Ask who should choose the instance. If the answer is "anyone, I just need one healthy backend", use a normal ClusterIP Service. If the answer is "the client, because identity matters or because per-request balancing matters", make it headless - and make sure the client re-resolves DNS.

  • Why do gRPC clients often perform badly against a normal ClusterIP Service, and how does a headless Service help?
    Service load balancing is per connection at L4, while gRPC multiplexes many requests over one long-lived HTTP/2 connection. Every request therefore lands on the single pod chosen when the connection was established, so scaling out does not redistribute load. Resolving a headless Service gives the client all pod IPs, letting it open a subchannel per backend and balance per request, re-resolving as pods change.
  • A pod resolves a headless Service once at startup and caches the addresses. What goes wrong, and what is the fix?
    After a rollout the cached IPs point at pods that no longer exist, so the client keeps failing while healthy pods sit idle - nothing in the cluster corrects this, because there is no VIP or proxy in the path. The fix is on the client: honour DNS TTLs, re-resolve periodically or on connection failure, and use a client library with a DNS-refreshing name resolver rather than resolving once into a static address list.

A normal Service is a receptionist who forwards your call to whoever is free. A headless Service is the staff directory: you get everyone's direct line and decide who to call - and it is on you to notice when someone has left.

saying these in an interview costs you the question

  • Thinking a headless Service still load-balances, just without an external IP.
  • Believing per-pod DNS names work for any Deployment rather than requiring stable pod identities from a StatefulSet.
  • Assuming clients automatically pick up membership changes without re-resolving DNS.
  • Reaching for headless as a general performance optimisation for plain HTTP/1.1 services.
  • Confusing a headless Service with an ExternalName Service.

context