Ingress and Cluster DNS: One Entrance In, One Name to Recognise Each Other
· tech
📑 Contents
- Ingress: one entrance, routed by URL
- A pit: an Ingress is only “rules”; a Controller has to execute them
- Cluster DNS: how services recognise each other by “name”
- Reflections
- Separate L4 from L7, and “which one” stops being a dilemma
- ”The rules” and “the thing that executes the rules” are two different things — this idea saves a lot of debugging
- Service discovery is K8s’s most underrated free gift
The fourth post gave short-lived Pods a fixed address, the Service, but left two loose ends: first, “how does the outside come in through one entrance and get routed by URL to different services?” — the Ingress at the top of that exposure-types diagram; second, when services inside the cluster call each other, who actually resolves the name http://web? This post closes both: outward via Ingress, inward via cluster DNS. They’re two sides of one coin — both solve “how to find a service by name rather than by IP”.
The outward half first; the key is that Service and Ingress divide the work across different network layers:
Ingress: one entrance, routed by URL
Recall the LoadBalancer type of Service: every external service you open gets its own public IP from the cloud. Ten services means ten IPs and ten bills, and it’s L4 — it looks only at destination IP and port, can’t see the HTTP URL, so there’s no “same domain, /api goes here, /img goes there”.
Ingress fills exactly that layer. It’s the L7 (HTTP layer) entrance, and an Ingress rule looks like this:
apiVersion: networking.k8s.io/v1
kind: Ingress
spec:
rules:
- host: shop.com
http:
paths:
- path: /
backend: { service: { name: web-svc, port: { number: 80 } } }
- path: /api
backend: { service: { name: api-svc, port: { number: 80 } } }
tls: # https is terminated at the Ingress layer; backends receive plain http
- hosts: [shop.com]
secretName: shop-tls
One public IP, splitting traffic by Host + path across a crowd of ClusterIP Services behind it, and doing TLS termination at this layer as a bonus (decrypt https to http before forwarding, so backend Pods don’t each handle certificates). This is exactly the most common combination the fourth post described: “an Ingress in front + a bunch of internal ClusterIP Services”.
A pit: an Ingress is only “rules”; a Controller has to execute them
This is where most people get stuck: you kubectl apply an Ingress and nothing happens. Because the Ingress object is only a rule table — it needs an actually running Ingress Controller (commonly ingress-nginx or Traefik) to read those rules and configure its internal reverse proxy before any traffic flows.
A cluster can run several kinds of Controller at once, with IngressClass deciding which one a given Ingress belongs to. It’s the familiar pattern again: you declare a desired state (routing rules), and a controller keeps converging reality (proxy config) onto it — exactly the same loop as Deployment and Endpoints, only this time what converges is nginx’s config file.
Cluster DNS: how services recognise each other by “name”
Now the inward half. The fourth post said services call each other by names like http://web, but who translates the name into a ClusterIP? The answer is the cluster’s built-in DNS — CoreDNS (running as a Deployment in kube-system, exposed through a Service). When each Pod is created, its /etc/resolv.conf is pointed at CoreDNS, so every DNS query from inside a Pod goes there.
Every Service automatically gets a fixed DNS name, following <service>.<namespace>.svc.cluster.local:
web — the search domain in resolv.conf completes it to the full name and CoreDNS turns it into the ClusterIP; only across namespaces do you need to write web.other-nsIn practice you’ll almost only use short names: web within the namespace, web.payments across namespaces — that trailing .svc.cluster.local is filled in automatically by the search domain. One exception worth remembering: a headless Service (clusterIP: None) doesn’t return a single virtual IP; it returns the IP of every Pod, and gives each Pod its own DNS name (pod-0.web.default.svc...) — needed only by stateful services like a StatefulSet that have to “address one specific member”; ordinary stateless services use a normal Service.
Reflections
Separate L4 from L7, and “which one” stops being a dilemma
When I started, LoadBalancer and Ingress were easy to confuse — both look like “external”. The watershed is actually crisp: does it understand HTTP. A Service stops at L4, knows only IP:port, and suits “I just need this port opened up” (databases, gRPC, non-HTTP things); Ingress stands at L7, reads Host and path, and suits “a pile of HTTP services under one domain, TLS collected at one entrance, routed by URL”. Once that line was clear, my default became: external HTTP always goes through Ingress, one IP for everything; only non-HTTP, or things needing their own IP, get a separate LoadBalancer. Saves IPs, saves bills, and certificates are managed in one place.
”The rules” and “the thing that executes the rules” are two different things — this idea saves a lot of debugging
An Ingress applied with no effect is the most common newbie ghost story, and behind it is a more general truth: many objects in K8s are only “desired state”; something has to happen only if a controller is running. Ingress needs an Ingress Controller, NetworkPolicy needs CNI support, a CRD needs its operator. I’ve built a habit: when a resource “applied but nothing moved”, my first reaction isn’t to suspect a typo but to ask — “is the controller that executes this rule actually running?” That question often hits the mark directly. Rules never move on their own; what moves is always the loop watching them.
Service discovery is K8s’s most underrated free gift
Coming from the era of self-built services, I have scars from “service discovery” — Consul, Eureka, hand-assembled etcd; just keeping “who is where” correct was exhausting. K8s made it built in, zero config: create a Service and it automatically has a DNS name, CoreDNS resolves it automatically, and the name stays the same when Pods change IP. It’s the underlying support for the fourth post‘s “depend on names, not locations” — precisely because this DNS exists, “recognising each other by name” went from a wish to the default behaviour. I now deliberately remind teams not to build their own service-discovery wheel: K8s has handed you the hardest piece for free; treasure it.