Ravindra BagaleCourses & study guides मराठी

Chapter 62: Kubernetes Services, Networking and Load Balancing: CNI, ClusterIP, NodePort, LoadBalancer, DNS and NetworkPolicy

62.5 ExternalName and headless Services: DNS-only Services and StatefulSet addresses

Why: the problem

Not every Service needs a virtual IP. Two special kinds of Service exist only in DNS, and both show up in real projects. The first is ExternalName. Suppose Zomato's order service talks to a payments database on Amazon RDS. The RDS hostname is long and differs per environment. If you hard-code it in every app, moving to a new database means changing every Deployment. An ExternalName Service named payments-db returns a DNS CNAME pointing to the RDS hostname. Apps always connect to payments-db, and only one object changes when the database moves. There is no proxying and no ClusterIP; it is just a DNS alias. The second is a headless Service, with clusterIP: None. Normal Services hide pods behind one IP. Sometimes you want the opposite. A Kafka or PostgreSQL cluster in a StatefulSet needs each member to be reachable by a stable name like pg-0.pg.data.svc.cluster.local, and clients like a gRPC library may want the full list of pod IPs to balance on their own. A headless Service returns the pod IPs directly from DNS, and with a StatefulSet each pod gets its own DNS record. In this lesson we create an ExternalName alias, prove it is a CNAME, build a headless Service for a 3-replica StatefulSet, resolve each pod by name, and see the common mistakes, such as expecting ports or TLS names to change.

What: every term in plain English

  • ExternalName — A Service whose DNS name is a CNAME to an outside hostname.
  • CNAME — A DNS record that says 'this name is an alias for that name'.
  • Headless Service — A Service with clusterIP: None; DNS returns pod IPs.
  • A record — A DNS record mapping a name to an IPv4 address.
  • StatefulSet — A controller giving pods stable names like pg-0, pg-1.
  • serviceName — StatefulSet field naming its governing headless Service.
  • Pod DNS name — pod.service.namespace.svc.cluster.local, for StatefulSet pods.
  • SRV record — DNS record carrying port numbers for named ports.
  • publishNotReadyAddresses — Lists pods in DNS even before they are Ready.
  • Client-side load balancing — The client picks among pod IPs itself.
Create an ExternalName Service for a database host. Resolve it from a pod. Create a headless Service and a StatefulSet. Resolve the Service name. Resolve each pod name. Compare with a normal Service. Clean up.
ExternalName and headless: Services that live only in DNS

How: step by step

  1. Create an ExternalName Service for a database host. payments-db.
  2. Resolve it from a pod. See the CNAME.
  3. Create a headless Service and a StatefulSet. 3 replicas.
  4. Resolve the Service name. Many A records.
  5. Resolve each pod name. pg-0, pg-1, pg-2.
  6. Compare with a normal Service. One ClusterIP.
  7. Clean up. Keep the net namespace.

Example 1: Alias an outside database with ExternalName

Goal. Alias an outside database with ExternalName.

payments-db.yaml

apiVersion: v1
kind: Service
metadata:
  name: payments-db
  namespace: net
spec:
  type: ExternalName
  externalName: payments.c9xk2abcd.ap-south-1.rds.amazonaws.com

What each line does

  • type: ExternalName — No selector, no ClusterIP, no endpoints.
  • externalName — The real hostname, without a scheme or port.
  • DNS answer — payments-db.net.svc.cluster.local CNAME to the RDS name.
  • Ports — Clients still use the real port, for example 5432.
  • Moving the DB — Change one field; apps keep their config.

Green output

$ kubectl apply -f payments-db.yaml
$ kubectl get svc -n net payments-db
NAME          TYPE           CLUSTER-IP   EXTERNAL-IP                                          PORT(S)
payments-db   ExternalName   <none>       payments.c9xk2abcd.ap-south-1.rds.amazonaws.com      <none>
$ kubectl exec -n net curl -- nslookup -type=cname payments-db.net.svc.cluster.local
payments-db.net.svc.cluster.local   canonical name = payments.c9xk2abcd.ap-south-1.rds.amazonaws.com

Expected result. DNS returns a CNAME. On kind the RDS name does not exist, so the final lookup fails, but the alias itself is visible. On EKS it would resolve to the database.

Example 2: A headless Service with a StatefulSet gives each pod a stable name

Goal. A headless Service with a StatefulSet gives each pod a stable name.

pg.yaml

apiVersion: v1
kind: Service
metadata:
  name: pg
  namespace: net
spec:
  clusterIP: None
  selector:
    app: pg
  ports:
    - name: tcp
      port: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: pg
  namespace: net
spec:
  serviceName: pg
  replicas: 3
  selector:
    matchLabels:
      app: pg
  template:
    metadata:
      labels:
        app: pg
    spec:
      containers:
        - name: echo
          image: hashicorp/http-echo:1.0
          args: ["-listen=:8080", "-text=member"]
          ports:
            - name: tcp
              containerPort: 8080

What each line does

  • clusterIP: None — Makes the Service headless.
  • serviceName: pg — Links the StatefulSet to the headless Service so pods get DNS names.
  • http-echo — Stands in for real database pods to keep the lab light.
  • pg-0.pg — Short form inside the namespace.
  • Order — Pods start one by one: pg-0, then pg-1, then pg-2.

Green output

$ kubectl apply -f pg.yaml
$ kubectl rollout status sts/pg -n net
partitioned roll out complete: 3 new pods have been updated...
$ kubectl get svc -n net pg
NAME   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)
pg     ClusterIP   None         <none>        8080/TCP
$ kubectl exec -n net curl -- nslookup pg-1.pg.net.svc.cluster.local
Name:   pg-1.pg.net.svc.cluster.local
Address: 10.244.40.71

Expected result. pg-1 has its own DNS name. If pg-1 is recreated with a new IP, the name stays the same and DNS updates.

Example 3: Compare DNS answers: headless vs normal Service

Goal. Compare DNS answers: headless vs normal Service.

compare.sh

kubectl expose sts pg -n net --name pg-vip --port 8080
kubectl exec -n net curl -- nslookup pg.net.svc.cluster.local
kubectl exec -n net curl -- nslookup pg-vip.net.svc.cluster.local
kubectl exec -n net curl -- nslookup -type=srv _tcp._tcp.pg.net.svc.cluster.local

What each line does

  • pg — Headless: three A records, one per pod.
  • pg-vip — Normal: one A record, the ClusterIP.
  • SRV — Named port tcp gives port and pod name.
  • Who balances — Headless: the client. Normal: kube-proxy.
  • Use case — Headless for databases, gRPC client balancing, peer discovery.

Green output

$ bash compare.sh
service/pg-vip exposed
Name:   pg.net.svc.cluster.local
Address: 10.244.40.70
Address: 10.244.40.71
Address: 10.244.21.12
Name:   pg-vip.net.svc.cluster.local
Address: 10.96.77.140
_tcp._tcp.pg.net.svc.cluster.local  service = 0 33 8080 pg-0.pg.net.svc.cluster.local.
_tcp._tcp.pg.net.svc.cluster.local  service = 0 33 8080 pg-1.pg.net.svc.cluster.local.
_tcp._tcp.pg.net.svc.cluster.local  service = 0 33 8080 pg-2.pg.net.svc.cluster.local.

Expected result. The headless name lists every pod; the normal Service returns one virtual IP. SRV records even carry the port and the pod names.

Every command, step by step (kind lab with kubectl)

  1. Create the alias

    kubectl apply -f payments-db.yaml
    

    What it does: ExternalName.

    service/payments-db created
    
  2. Check no endpoints

    kubectl get endpointslices -n net -l kubernetes.io/service-name=payments-db
    

    What it does: None exist.

    No resources found in net namespace.
    
  3. Resolve the CNAME

    kubectl exec -n net curl -- nslookup -type=cname payments-db
    

    What it does: Alias visible.

    payments-db.net.svc.cluster.local   canonical name = payments.c9xk2abcd.ap-south-1.rds.amazonaws.com
    
  4. Point it elsewhere

    kubectl patch svc payments-db -n net -p '{"spec":{"externalName":"example.com"}}'
    

    What it does: Move without touching apps.

    service/payments-db patched
    
  5. Curl through the alias

    kubectl exec -n net curl -- curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: example.com' http://payments-db
    

    What it does: Host header must match the real site.

    200
    
  6. Create StatefulSet

    kubectl apply -f pg.yaml
    

    What it does: Headless plus sts.

    statefulset.apps/pg created
    
  7. Watch order

    kubectl get pods -n net -l app=pg -w
    

    What it does: pg-0 first.

    pg-0   1/1   Running
    pg-1   0/1   ContainerCreating
    
  8. Resolve all

    kubectl exec -n net curl -- nslookup pg
    

    What it does: Three IPs.

    Address: 10.244.40.70
    Address: 10.244.40.71
    Address: 10.244.21.12
    
  9. Call one member

    kubectl exec -n net curl -- curl -s pg-2.pg:8080
    

    What it does: Direct pod.

    member
    
  10. Delete and keep the name

    kubectl delete pod -n net pg-2
    kubectl exec -n net curl -- nslookup pg-2.pg
    

    What it does: New IP, same name.

    Name:   pg-2.pg.net.svc.cluster.local
    Address: 10.244.21.19
    
  11. Clean up

    kubectl delete -f pg.yaml
    kubectl delete svc -n net pg-vip payments-db
    

    What it does: Done.

    service "pg" deleted
    

When it is red 1

curl: (6) Could not resolve host: payments-db

Cause. The outside name does not exist or the node cannot reach external DNS.

Fix, step by step

  1. Check externalName for typos and test nslookup of the real host from a pod.

When it is red 2

SSL: no alternative certificate subject name matches target host name 'payments-db'

Cause. TLS cert is for the real host, not the alias.

Fix, step by step

  1. Connect with the real hostname for TLS, or use sslmode that checks the real name.

When it is red 3

nslookup pg-0.pg: NXDOMAIN

Cause. serviceName in the StatefulSet does not match the headless Service.

Fix, step by step

  1. Set serviceName to the headless Service name.

When it is red 4

ExternalName with an IP address does not work

Cause. externalName must be a DNS name.

Fix, step by step

  1. Use a Service without selector plus an EndpointSlice for raw IPs.

When it is red 5

Pod missing from headless DNS while starting

Cause. Not Ready pods are hidden.

Fix, step by step

  1. Set publishNotReadyAddresses: true for peer discovery.

Practice tasks

Easy Task 1: Alias

Why: Create.

What to do: Create payments-db.

How:
  1. ExternalName.
Solution
kubectl get svc -n net payments-db

Easy Task 2: CNAME

Why: Resolve.

What to do: See the CNAME.

How:
  1. nslookup -type=cname.
Solution
kubectl exec -n net curl -- nslookup -type=cname payments-db

Easy Task 3: Headless

Why: Create.

What to do: Create pg.

How:
  1. clusterIP: None.
Solution
kubectl get svc -n net pg

Medium Task 4: Members

Why: Resolve.

What to do: Resolve each pod.

How:
  1. pg-N.pg.
Solution
kubectl exec -n net curl -- nslookup pg-0.pg

Medium Task 5: Compare

Why: DNS.

What to do: Compare headless and ClusterIP.

How:
  1. compare.sh.
Solution
bash compare.sh

Medium Task 6: SRV

Why: Ports.

What to do: Read SRV records.

How:
  1. _tcp._tcp.
Solution
kubectl exec -n net curl -- nslookup -type=srv _tcp._tcp.pg

Medium Task 7: Stable

Why: Recreate.

What to do: Delete pg-2 and resolve again.

How:
  1. Same name.
Solution
kubectl delete pod -n net pg-2

Medium Task 8: NotReady

Why: Discover.

What to do: Use publishNotReadyAddresses.

How:
  1. Peer discovery.
Solution
kubectl explain service.spec.publishNotReadyAddresses

Hard Task 9: Ledger

Why: Main task.

What to do: Build ledger with peer names.

How:
  1. StatefulSet.
Solution
kubectl get pods -n net -l app=ledger

Hard Task 10: Explain

Why: Interview.

What to do: Explain headless for databases.

How:
  1. Stable identity.
Solution
kubectl explain statefulset.spec.serviceName

Homework: try at home

For Zomato, create payments-db as an ExternalName to the RDS host in each environment, and a 3-member headless StatefulSet 'ledger' where each member can call the others by pg-style DNS names. Show the SRV records.

Recap

  • ExternalName is a DNS CNAME alias to an outside host, with no proxy.
  • Headless Services (clusterIP: None) return pod IPs from DNS.
  • With a StatefulSet each pod gets a stable name like pg-0.pg.
  • SRV records carry named ports.
  • Next: CoreDNS, the server answering all these lookups.

FAQ

Q. Is ExternalName handled by kube-proxy?

No. It is purely DNS, answered by CoreDNS. kube-proxy writes no rules for it.

Q. Can a headless Service have no selector?

Yes. You then manage EndpointSlices yourself and DNS returns those addresses.

Q. Why use pod names instead of pod IPs in configs?

Pod IPs change on every restart. StatefulSet DNS names stay the same, so cluster member lists in config files stay valid.

Q. How long does DNS take to update after a pod restarts?

CoreDNS sees the EndpointSlice change within a second or two, but clients may cache the old answer for the record TTL, 30 seconds by default.

Q. Should I use ExternalName for every outside API?

Only where an alias really helps, such as databases or internal services that move between environments. For public HTTPS APIs, keep the real hostname so TLS checks and Host headers keep working.

Q. How does a Kafka client use the headless names?

Each broker advertises its own name, like kafka-1.kafka.data.svc.cluster.local, and clients connect straight to the broker that owns a partition. A single ClusterIP would send them to the wrong broker.

Q. What happens to DNS when a StatefulSet scales down?

The removed pod's record disappears, while the remaining names stay the same. Clients that cached the old IP may need to reconnect.

Q. Does ExternalName change the port?

No. Clients still connect to the real port.

Q. Does it work with HTTP Host headers?

The client sends Host: payments-db, which many sites reject. Fine for databases, tricky for HTTPS.

Q. Can I use headless with a Deployment?

Yes, for the list of IPs, but pods do not get stable names.

Q. Is a headless Service load balanced?

Only by the client, or by DNS answer order.

Q. Why do StatefulSets need it?

Members of clustered software find each other by stable names.

Q. Can I map an outside IP to a Service name?

Yes: a Service with no selector plus a manual EndpointSlice.

Interview questions

  1. What is an ExternalName Service?

    Model answer: A Service that returns a CNAME to an outside DNS name, without proxying.

  2. What is a headless Service?

    Model answer: A Service with clusterIP: None whose DNS returns pod IPs.

  3. Why do StatefulSets use headless Services?

    Model answer: To give each pod a stable DNS name for peer discovery.

  4. What is a limitation of ExternalName?

    Model answer: TLS and HTTP Host names do not match the alias; no IPs allowed.

  5. How do you expose an outside IP inside the cluster?

    Model answer: Service without selector plus an EndpointSlice with that IP.

  6. How would you migrate apps from one database host to another with minimal change?

    Model answer: Point an ExternalName Service at the old host, switch apps to use the Service name, then change externalName to the new host during the cutover window, keeping TLS hostname checks in mind.

Quick quiz

Q1. Does ExternalName have a ClusterIP?

No.

Q2. What does a headless Service return?

Pod IPs.

Q3. What links a StatefulSet to its Service?

serviceName.

Q4. Who balances with headless?

The client.

Q5. Can externalName be an IP?

No, it must be a hostname.

Samajla ka?