Who this is for
You can deploy a pod and you cannot reliably get traffic to it. You do not need to understand CNI plugins or kube-proxy internals to fix the common failures.
The model
Kubernetes requires that every pod can reach every other pod by IP, across
nodes, without NAT. That is why there is no -p 8080:80 here: the pod already
has an address, and the only question is how anyone finds it.
Pod IPs are not stable — a replacement pod gets a new one (a container restarting inside the same pod keeps it). A Service is the stable thing in front of them.
Service types
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web # <- matches pod LABELS, not pod names
ports:
- port: 80 # the port the Service listens on
targetPort: 8080 # the port the container listens on
| Type | Reachable from | Use |
|---|---|---|
ClusterIP | Inside the cluster only | The default, and what most things want |
NodePort | Any node IP on a high port | Development, or behind your own load balancer |
LoadBalancer | The internet, via a cloud LB | One cloud load balancer per Service — this gets expensive |
ExternalName | — | A CNAME to something outside the cluster |
port and targetPort are different things and mixing them up produces a
Service that resolves and then refuses the connection, because traffic arrives at a port nothing in the pod is listening on.
For StatefulSets you often want clusterIP: None — a headless Service, which
returns the pod IPs directly instead of load-balancing, so each replica is
individually addressable.
How a Service finds its pods
The selector matches labels, every matching pod is listed as an endpoint, and only the ones marked Ready receive traffic:
kubectl get endpointslices -l kubernetes.io/service-name=web
kubectl get endpoints web # deprecated since v1.33, but lists only Ready pods
If the EndpointSlice lists no addresses, the selector matches nothing. If it lists addresses but kubectl describe endpointslice shows Ready: false for every one of them, the pods are not Ready — the plain listing shows not-Ready pods too. Those are the only two causes:
The labels do not match. A Service with app: web and pods labelled
app: web-server select nothing. There is no error — Kubernetes has no opinion
about labels you have not created yet.
The pods are not Ready. Readiness gates endpoint membership deliberately: a pod that is starting, or whose readiness probe is failing, is removed from the Service so traffic is not sent to it. A failing readiness probe therefore looks exactly like a networking problem from the outside. This site covers that one directly: kubectl says my service has no endpoints.
Ingress
An Ingress is a request for HTTP routing. (The Ingress API is frozen — still supported, no longer developed — and the Kubernetes project now recommends Gateway API for new work. The community ingress-nginx controller stopped receiving releases and security fixes after March 2026, so if that is what you run, plan a move to a maintained controller.) On its own it does nothing — a controller has to be running to act on it. Creating an Ingress in a cluster with no controller produces a resource that stays there, with no address, and no error explaining why.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx # which controller should pick this up
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
tls:
- hosts: ["app.example.com"]
secretName: app-tls
ingressClassName matters as soon as there is more than one controller, and
omitting it in a cluster with no default class is a quiet way to have nothing
happen at all.
When the Ingress is working and the backend is not, you get a 502 from the controller rather than a Kubernetes error — see 502 Bad Gateway.
DNS
CoreDNS gives every Service a name:
web # same namespace
web.production # across namespaces
web.production.svc.cluster.local # fully qualified
A detail worth knowing: pods get ndots:5 in /etc/resolv.conf, which means
any name with fewer than five dots is first tried against each cluster search
domain. Looking up api.github.com can therefore cost several failed queries
before the real one. It is usually invisible, and it shows up as latency under
load. A trailing dot (api.github.com.) skips the search list.
When DNS itself stops working the symptoms are broad and confusing — DNS stops resolving walks through what actually happens in the cluster.
Deprecations to plan for
Added 27 October 2026. Three parts of this picture now carry end dates, checked against the Kubernetes release notes and source up to 1.37:
- kube-dns was deprecated in 1.37, and no new packages are expected after
v1.40. CoreDNS has been the default since 1.13. To see which you run:
kubectl -n kube-system get deploy kube-dns coredns. A Service namedkube-dnsproves nothing, because kubeadm keeps that name for CoreDNS. - kube-proxy's ipvs mode was deprecated in 1.35. kube-proxy 1.37 says it
will be disabled by default in 1.40 and removed in 1.43, in favour of
nftables. Check with
kubectl -n kube-system get configmap kube-proxy -o yaml | grep -n 'mode:'. - Service
spec.externalIPswas deprecated in 1.36. Every apply of such a Service now prints a warning, and support goes completely no earlier than 1.43. Move those Services totype: LoadBalanceror a Gateway.
Each item, with its source and the check to run, is on the Kubernetes upgrade checklist.
Network policies
Default behaviour is allow-all. A pod becomes restricted only once some NetworkPolicy selects it — and then, for the directions that policy mentions, everything not explicitly allowed is denied.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-allow-from-ingress
spec:
podSelector:
matchLabels:
app: web
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: 8080
Two traps. Writing an Ingress-only policy does not restrict egress at all,
which people assume it does. And policies need a CNI that implements them — on
a cluster whose CNI does not, the resource is accepted and silently ignored,
which is the worst of both outcomes.
Debugging, in order
kubectl describe endpointslices -l kubernetes.io/service-name=web # 1. any endpoints, and Ready: true?
kubectl get pods -l app=web -o wide # 2. are they Ready?
kubectl describe svc web # 3. selector and ports
kubectl run tmp --rm -it --image=nicolaka/netshoot -- bash # 4. test from inside
# then: curl -v web.production.svc.cluster.local
# nslookup web.production
Do step 1 first every time. It takes five seconds and it separates "nothing is behind this Service" from every other possible cause, which is most of the diagnosis.
Common mistakes
- Selector and labels not matching. Silent, and the most common cause of an empty endpoint list.
portwheretargetPortwas meant. Resolves, then the connection is refused.- A
LoadBalancerService per application. That is a cloud load balancer each, billed hourly. An Ingress controller gives you one for all of them. - Expecting an Ingress to work with no controller installed. Nothing errors; nothing happens.
- Assuming a readiness failure is a network failure. It removes the pod from the Service, which looks identical from outside.
If this did not work
- Endpoints exist, connection still refused. The container is probably
listening on
127.0.0.1rather than0.0.0.0, so nothing outside the pod can reach it. - It works by pod IP but not by Service name. DNS. Check CoreDNS pods are running, then resolve from inside a pod rather than from a node.
- Works in one namespace, not another. The short name only resolves within
the namespace; use
service.namespaceacross them. - Ingress has no address. No controller, or no
ingressClassNamein a cluster with no default. - Planning ranges. The CIDR calculator is quicker than doing the arithmetic twice.
Next steps
Deploy one application with a Deployment, a ClusterIP Service and an Ingress, then deliberately break the Service selector by one character and run the debugging sequence above. Doing it once when nothing is on fire is worth a lot when something is.
Related on this site
- DNS stops resolving — What actually happens in the cluster when it does.
- 502 Bad Gateway — What an Ingress reports when nothing healthy is behind it.
- CIDR calculator — Work out the ranges before you allocate them.
Tagged with
Enjoyed this article?
Get more DevOps insights delivered to your inbox.
Get new posts by email
Subscribe to get an email when a new blog post is published. Skip anytime.
No spam, unsubscribe anytime.
Terms used here (1)plain definitions
Short definitions for the vocabulary on this page, in case any of it is new. Each links to the full entry.
- Service Mesh
- A dedicated infrastructure layer for managing service-to-service communication within a microservices architecture. Service meshes like Istio and Linkerd provide traffic management, mutual TLS, observability, and resilience features without changing application code.
Related Posts
Discussion
0 comments
Sign in to join the conversation.
Be the first to comment
Start a conversation about this post



