Networking I: Services, Service Discovery & DNS
Pod IPs are inherently unstable -- Pods are created and destroyed constantly. A Service is a stable virtual IP and DNS name that load-balances traffic across a dynamically-changing set of backing Pods
Pod IPs are inherently unstable -- Pods are created and destroyed constantly. A Service is a stable virtual IP and DNS name that load-balances traffic across a dynamically-changing set of backing Pods. Four types: ClusterIP (internal, default), NodePort, LoadBalancer, and ExternalName.
-
Every Service gets a stable ClusterIP (virtual IP) and a DNS name (..svc.cluster.local) from CoreDNS.
-
An EndpointSlice controller continuously watches matching Pods and updates EndpointSlice objects with their current IPs.
-
kube-proxy programs forwarding rules on every node so traffic to the ClusterIP is DNAT'd to a healthy backing Pod.
-
NodePort builds on ClusterIP -- additionally opens a port (30000-32767) on every node.
-
LoadBalancer builds on NodePort -- a cloud controller provisions a real external load balancer.
-
Label your application Pods consistently (e.g. app: payments) -- a Service's spec.selector matches on these labels, nothing else.
-
Create a Service (default type ClusterIP) with that selector and the containerPort your Pods actually listen on as targetPort.
-
CoreDNS immediately registers ..svc.cluster.local resolving to the Service's ClusterIP; other Pods can now reach it by name instead of IP.
-
The EndpointSlice controller populates the Service's backing Pod IPs automatically as matching Pods become Ready -- no manual wiring.
-
For access from outside the cluster, change type to NodePort (opens a port on every node) or LoadBalancer (provisions a cloud load balancer that forwards to the NodePort).
-
For referencing an external (non-Kubernetes) system by a stable in-cluster DNS name, use type: ExternalName instead of a selector.
-
A frontend Deployment calling a backend API purely by its Service DNS name (e.g. http://orders-api.default.svc.cluster.local) instead of hardcoded Pod IPs.
-
Exposing a public-facing web app via a LoadBalancer Service so cloud infrastructure provisions a real external IP/ALB.
-
Using NodePort for a quick debug/dev-only path to a service during troubleshooting, without waiting on a cloud load balancer.
-
Using ExternalName to give an in-cluster DNS name to a managed database or external SaaS API endpoint that lives outside the cluster.
-
Multiple replicas of a stateless API sitting behind a single ClusterIP Service so callers never need to track individual Pod churn.
-
Always name containerPort in the Pod spec and reference it by name in the Service's targetPort -- avoids silent breakage when the container's actual port changes.
-
Prefer ClusterIP + Ingress over NodePort for HTTP services that need external access -- NodePort's high port range and per-node exposure are awkward for real ingress traffic.
-
Use readiness probes on backing Pods -- the EndpointSlice controller only includes Ready Pods, so a bad readiness probe either sends traffic too early or needlessly excludes healthy Pods.
-
Keep Service selectors narrow and specific to avoid accidentally matching Pods from an unrelated Deployment sharing an overlapping label.
-
For headless use cases (direct Pod addressing, e.g. peer discovery), use clusterIP: None deliberately rather than a normal ClusterIP.
-
Assuming a Service load-balances at the connection level with even distribution -- kube-proxy's iptables mode is effectively random selection per connection, not round-robin per request.
-
Forgetting that a Service selector matching zero Pods creates a Service with no endpoints -- requests hang or fail with no obvious error unless you check
kubectl get endpointslices. -
Using a Pod's IP directly in application config instead of the Service DNS name -- Pod IPs change on every restart/reschedule.
-
Not aligning targetPort with the container's actual listening port after a code change -- the Service silently keeps forwarding to the old port.
-
Exposing internal-only services as LoadBalancer out of habit, unnecessarily creating public cloud load balancers and cost.
-
Confusing NodePort's port range (30000-32767) with the Service's own port, causing connection attempts on the wrong port from outside the cluster.
-
kube-proxy in iptables mode adds per-Service rule-matching overhead that grows with Service count; ipvs mode scales better on clusters with thousands of Services.
-
DNS lookups for Service names go through CoreDNS -- cache-miss latency on cold lookups can matter for latency-sensitive request paths; tune CoreDNS's cache TTL and replica count for high-QPS clusters.
-
LoadBalancer Services provision a real cloud load balancer per Service by default -- consolidating multiple HTTP backends behind a single Ingress Controller instead of one LoadBalancer Service per app reduces both cost and per-hop latency variance.
-
EndpointSlice (vs the older Endpoints object) caps entries per slice, which materially reduces control-plane churn/broadcast cost for Services with very large numbers of backing Pods.
-
Monitor kube-proxy and CoreDNS themselves as critical infrastructure -- a CoreDNS outage silently breaks in-cluster service discovery cluster-wide even though every individual Pod is healthy.
-
Set explicit readiness probes on every Service-backed Deployment; without one, a Pod can receive traffic before its app is actually ready to serve.
-
For public LoadBalancer Services, restrict source IP ranges (loadBalancerSourceRanges) where the traffic source is known, rather than leaving them open to the internet by default.
-
Audit Service types periodically -- LoadBalancer Services that should have been ClusterIP-behind-Ingress accumulate as real, ongoing cloud cost.
-
Deploy two replicas of a simple web app behind a ClusterIP Service, then curl the Service DNS name repeatedly from another Pod and observe requests landing on different backing Pods.
-
Change the backing Pods' labels temporarily so they no longer match the Service selector and run
kubectl get endpointslicesto see the Service drop to zero endpoints. -
Change a Service from ClusterIP to NodePort and connect to it from outside the cluster on the assigned node port.
-
Create a headless Service (clusterIP: None) and use
nslookupfrom inside a Pod to see individual Pod IPs returned instead of a single virtual IP.
-
Services provide stable virtual IP and DNS name across an inherently unstable set of backing Pods.
-
ClusterIP, NodePort, and LoadBalancer build in layers -- each a superset of network exposure.
-
EndpointSlices track current backing Pod IPs; kube-proxy uses them to program forwarding rules.
-
A Service with zero matching Ready Pods is a silent failure -- EndpointSlice shows the empty set.
Want a visual for this concept?
Generate a diagram tailored to “Networking I: Services, Service Discovery & DNS” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →