ExternalName and Services without Selectors

Most Services front Pods picked by a selector, so how do the two kinds without one reach outside? An ExternalName Service has no ClusterIP: CoreDNS answers with a CNAME and the client connects by itself, while TLS SNI and HTTP Host still carry the in-cluster name the server may not recognize. A selectorless Service keeps its virtual IP, and a hand-written EndpointSlice labelled kubernetes.io/service-name lists the address kube-proxy writes rules for.

Play the animation 5 steps, animated, best on a wide screen

ExternalName and Services without Selectors, Kubernetes diagram

ExternalName and Services without Selectors, step by step

  1. Service api is type ExternalName: no selector, no ClusterIP, only the name api.partner.example. The client looks up api.default.svc. Under ndots:5 the resolver walks the search list, and after two misses api.default.svc.cluster.local matches: CoreDNS reads that Service and answers with a CNAME to api.partner.example plus the address of that name. No endpoints and no Service rules exist for Service api.

    Play from step 1
  2. With that address the client opens HTTPS to api.partner.example by itself. The connection is ordinary egress, and no Service rule matches it. The TLS hello names the server the client asked for, so SNI still says api.default.svc.

    Play from step 2
  3. The partner server answers with its certificate, issued for api.partner.example. That is not api.default.svc, so the client rejects the handshake. Plain HTTP has the same problem: its Host header also says api.default.svc, a name that server does not recognize.

    Play from step 3
  4. Service pg has no selector, and the EndpointSlice controller writes no slice for it. So EndpointSlice pg-ext is written by hand: the label kubernetes.io/service-name: pg ties it to pg, and it lists 203.0.113.5 on port 5432. kube-proxy watches it and writes Service rules for 10.96.0.40.

    Play from step 4
  5. The client dials 10.96.0.40:5432. The Service rules in the Node dataplane DNAT it to 203.0.113.5:5432, and the connection leaves the cluster for the database, exactly as a Pod-backed Service would send it to a Pod. The client only ever sees the Service address.

    Play from step 5

Sources