Kubernetes Networking in Depth: Pods, ReplicaSets, Deployments, Services, Endpoints, and Ingress
Traffic in Kubernetes is not one magic pipe. Pods get real IPs on the cluster network. ReplicaSets and Deployments keep the right number of those Pods alive. A Service gives clients a stable name and virtual IP in front of moving Pod IPs. Endpoints (or EndpointSlices) are the live list of backends. Ingress adds HTTP routing from outside the cluster. This guide walks the full chain—what each object does, how packets move, and how to debug when something is reachable from kubectl exec but not from your browser.
In short
You declare a Deployment → it owns a ReplicaSet → which creates Pods with labels. A Service selects those labels and gets a cluster IP. The Endpoints controller writes Pod IPs into Endpoints/EndpointSlice. kube-proxy (or a CNI replacement) programs rules so traffic to the Service IP reaches healthy Pods. Ingress plus an ingress controller routes external HTTP(S) to Services. If DNS resolves but connections fail, walk Pod → Endpoints → Service → proxy/CNI → Ingress—in that order.
Why Kubernetes splits networking across objects
Pods are ephemeral: they restart, reschedule, and change IP. Clients cannot hard-code Pod IPs. Kubernetes separates concerns:
- Workload objects (Deployment, ReplicaSet, Pod) answer “what runs and how many copies?”
- Service answers “what stable address should callers use for this set of Pods?”
- Endpoints / EndpointSlice answer “which Pod IPs are healthy backends right now?”
- Ingress answers “how should HTTP from the internet map to Services?”
That split lets you roll out new Pods without changing Service YAML, and drain bad replicas without touching Ingress host rules—provided labels, selectors, and readiness probes are correct.
For control-plane anatomy see Kubernetes architecture in simple terms. For hands-on apply commands see your first workloads (Part 3). For health gates on endpoints see Kubernetes probes in depth.
Ownership chain: who creates whom
Deployment (you apply)
└── ReplicaSet (created by Deployment controller)
└── Pod (created by ReplicaSet controller)
Service (you apply)
└── Endpoints / EndpointSlice (populated by Endpoints controller from Ready Pods)
└── Pod IPs + ports (dynamic)
In production you almost never create Pods or ReplicaSets directly. You create a Deployment; controllers create the rest. You create a Service; the Endpoints controller mirrors matching, Ready Pods into backend lists.
Pod: the unit of network identity
A Pod is the smallest object Kubernetes schedules onto a node. For networking, the important facts are:
- One IP per Pod on the cluster network (assigned by the CNI plugin—Calico, Cilium, Flannel, etc.). Containers in the same Pod share that network namespace: they see
localhostfor each other’s ports. - Pod IP is routable from other Pods in the cluster (unless NetworkPolicy blocks it). It is not stable across Pod restarts.
- Ports are declared on containers (
containerPortis documentation for humans and some systems; the process must actually listen). - DNS: Pods get resolvable names like
10-244-1-5.namespace.pod.cluster.localin many clusters; apps usually talk to Services instead.
apiVersion: v1
kind: Pod
metadata:
name: demo
labels:
app: demo
spec:
containers:
- name: app
image: nginx:1.25-alpine
ports:
- containerPort: 80
Teaching clusters use bare Pods; production uses Deployments so replacements are automatic.
ReplicaSet: keeping replica count matched
A ReplicaSet ensures a specified number of Pod replicas with matching labels exist. Its spec.selector must match template.metadata.labels. The controller loop:
- Count Pods with matching labels.
- If too few → create Pods from the template.
- If too many → delete Pods (respecting budgets when owned by Deployment).
You rarely apply ReplicaSets by hand. When you scale a Deployment with kubectl scale deployment/web --replicas=5, the Deployment updates its ReplicaSet’s replicas field; the ReplicaSet creates or removes Pods.
During a rolling update, a Deployment may temporarily own two ReplicaSets—old and new—with different pod-template hashes in labels like pod-template-hash=abc123.
Deployment: desired state and rollouts
A Deployment declares desired state for stateless apps: image, env, resources, replica count, rollout strategy. It owns ReplicaSets and drives rolling updates (maxSurge, maxUnavailable, minReadySeconds).
Networking relevance:
- New Pods must pass readiness before they appear in Service endpoints—so traffic shifts gradually.
- Old Pods are terminated after new ones are Ready (subject to strategy), reducing black holes during deploys.
- Label changes on the Pod template create a new ReplicaSet; ensure your Service selector still matches (usually a stable label like
app: web, not the hash).
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
periodSeconds: 5
Service: stable front door inside the cluster
A Service is a long-lived API object with a cluster IP (for most types), DNS name <service>.<namespace>.svc.cluster.local, and ports that map to Pod targetPorts. It selects backends by labels (spec.selector), not by Pod name.
Service types
| Type | Cluster IP | Typical use | How external clients reach it |
|---|---|---|---|
| ClusterIP (default) | Yes, virtual IP only inside cluster | Microservice-to-microservice | Ingress, LoadBalancer, or port-forward |
| NodePort | Yes + high port on every node | Dev, bare-metal, simple exposure | <node-ip>:<nodePort> |
| LoadBalancer | Yes + cloud LB provisioned | Public TCP/UDP services on AWS/GCP/Azure | Cloud provider external IP/DNS |
| ExternalName | No (CNAME only) | Alias to external DNS | Redirects DNS to outside hostname |
Headless (clusterIP: None) |
No virtual IP | StatefulSet peer discovery, direct Pod DNS | DNS returns Pod A records |
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: ClusterIP
selector:
app: web
ports:
- name: http
port: 80 # Service port (what clients connect to)
targetPort: 80 # Pod port (where container listens)
port and targetPort can differ—e.g. Service port: 80 → Pod targetPort: 8080. Named ports in Pod specs can be referenced by name in targetPort.
What kube-proxy does
On each node, kube-proxy watches Services and Endpoints/EndpointSlices and programs the data plane so traffic to Service.spec.clusterIP:port is load-balanced to backend Pod IPs. Implementations include:
- iptables — DNAT rules per Service (classic default on many distros).
- IPVS — better scalability for large numbers of Services.
- userspace — legacy, rare today.
Modern clusters may use CNI/kube-proxy replacements (e.g. Cilium kube-proxy-free) but the model is the same: Service IP → one of the ready backends.
Session affinity: sessionAffinity: ClientIP sticks a client to one Pod (use carefully with rolling deploys).
Endpoints and EndpointSlice: the live backend list
When you create a Service with a selector, Kubernetes automatically maintains Endpoints (older) or EndpointSlice (scalable, default in modern clusters) objects with the same name as the Service.
Each backend entry is roughly: Pod IP + port. Only Pods that are:
- Matching the Service
selector, and - Ready (readiness probe success; startup probe finished if configured)
…appear in the list. Not Ready Pods are omitted—this is how Kubernetes sheds traffic without killing containers.
kubectl get endpoints web -o wide
kubectl get endpointslice -l kubernetes.io/service-name=web
Empty endpoints is the #1 Service debugging signal: wrong labels, no matching Pods, all Pods Not Ready, or selector accidentally removed (selector: {} manual Endpoints pattern).
Services without selectors (external DB, legacy IP) can use manually created Endpoints objects pointing at IPs outside the cluster.
Ingress: HTTP routing from outside
Ingress is an API object describing HTTP/HTTPS rules: host, path, TLS, backend Service. It does not route packets by itself—you need an Ingress controller (nginx, Traefik, AWS Load Balancer Controller, GCE, etc.) that watches Ingress resources and configures a reverse proxy or cloud load balancer.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
rules:
- host: demo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
Typical north-south path:
Internet → cloud LB / NodePort → Ingress controller Pod
→ Service web:80 (cluster IP)
→ kube-proxy/CNI → one of web Pods :80
Ingress vs Service type LoadBalancer: Ingress is L7 (HTTP host/path, TLS termination at controller). LoadBalancer Service is L4 (TCP/UDP) per Service—fine for gRPC, databases, or when you want one LB per Service.
Gateway API (successor in many greenfield designs) expresses similar ideas with more expressive route types; Ingress remains ubiquitous in existing clusters.
DNS: how names resolve
CoreDNS (or kube-dns) runs as a cluster add-on. Pods use /etc/resolv.conf with search paths like default.svc.cluster.local.
webin same namespace → Service cluster IP.web.otherns→web.otherns.svc.cluster.local.- Headless Service → DNS returns A records for each Pod IP (StatefulSet pattern).
If nslookup web works inside a debug Pod but HTTP fails, the problem is downstream (endpoints, port, NetworkPolicy, app)—not DNS.
CNI and NetworkPolicy (the underlay)
The Container Network Interface (CNI) plugin attaches each Pod to the cluster network, allocates IPs, and may enforce NetworkPolicy (iptables/eBPF rules). Kubernetes Service routing sits on top of Pod connectivity.
Symptom: Pod A cannot reach Pod B IP directly → fix CNI/routing/firewall first. Symptom: Pod A reaches Pod B IP but not Service/web → fix Service, endpoints, or kube-proxy.
For storage and node topics see PVs, PVCs, and StorageClasses and node groups and node pools.
End-to-end flows (east-west and north-south)
Flow A — Pod to Pod via Service (east-west)
- App in Pod
apiresolvesweb.default.svc.cluster.local→ Service cluster IP10.96.x.x. - Packet destined to
10.96.x.x:80hits local node’s kube-proxy/CNI rules. - DNAT/load-balance to one Ready Pod IP, e.g.
10.244.2.15:80. - CNI delivers to the node hosting that Pod; container receives on port 80.
Flow B — Internet via Ingress (north-south)
- Client hits
https://demo.example.com→ DNS to cloud load balancer fronting ingress controller. - Controller terminates TLS (if configured), matches Ingress rule, forwards to
Service/web:80. - Same Service → Endpoints → Pod path as Flow A.
Flow C — LoadBalancer Service (north-south L4)
- Cloud controller sees
type: LoadBalancer, provisions ELB/NLB/etc. - External traffic to LB → nodePort/target group → Service cluster IP → Pods.
Complete example: Deployment + Service + Ingress
# web-all.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
rules:
- host: demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
kubectl apply -f web-all.yaml
kubectl get deploy,rs,pod,svc,endpoints,ingress
kubectl describe ingress web
Debugging checklist (ordered)
| Step | Command / check | Healthy signal |
|---|---|---|
| 1. Pods exist | kubectl get pods -l app=web |
Desired replicas Running |
| 2. Ready for traffic | kubectl get pods READY column |
1/1 or n/n Ready |
| 3. Service selector | kubectl describe svc web |
Selector matches Pod labels |
| 4. Backends listed | kubectl get endpoints web |
Non-empty IP:port list |
| 5. In-cluster reachability | Debug Pod: curl http://web |
HTTP 200 (or expected code) |
| 6. Port mapping | Compare port vs targetPort |
Container actually listens on targetPort |
| 7. NetworkPolicy | kubectl get networkpolicy |
Allows ingress from client namespace/controller |
| 8. Ingress | kubectl describe ingress web |
Address assigned, backend points to correct Service port |
# Quick in-cluster probe
kubectl run curl --rm -it --image=curlimages/curl --restart=Never -- \
curl -s -o /dev/null -w "%{http_code}\n" http://web.default.svc/
# Port-forward bypasses Ingress/LB (isolates Service → Pod)
kubectl port-forward svc/web 8080:80
Deeper incident patterns: Kubernetes troubleshooting playbook.
Common mistakes
- Service selector typo —
app:webvsapp: web; endpoints stay empty. - Targeting wrong port — Service
port: 80but app listens on 8080 without fixingtargetPort. - Readiness never passes — Pods Running but Not Ready; Ingress/LB see zero backends.
- Using Pod IP in config — breaks on every restart; use Service DNS.
- Ingress without controller — Ingress object exists, no implementation, no ADDRESS.
- mixing pod-template-hash into Service selector — breaks during rollouts when hash changes.
- assuming ClusterIP is reachable from laptop — use port-forward, Ingress, or LB.
Production checklist
- Stable labels on Pod template; Service selector uses those labels only.
- Readiness probes gate Endpoints; liveness separate (probes guide).
- Named ports where multiple ports exist; document
portvstargetPort. - Ingress TLS via cert-manager or cloud-managed certs; redirect HTTP → HTTPS.
- NetworkPolicies: default-deny with explicit allow for ingress controller and monitoring.
- Headless Services only when you need per-Pod DNS (StatefulSets).
- Monitor endpoint count; alert when endpoints = 0 for critical Services.
Object reference (one glance)
| Object | Primary job | Networking role |
|---|---|---|
| Pod | Run containers | Real IP; processes listen on ports |
| ReplicaSet | Hold replica count | Indirect—creates Pods |
| Deployment | Rollouts + desired state | Indirect—controls which Pods exist |
| Service | Stable cluster IP + DNS | Virtual front door; kube-proxy programs paths |
| Endpoints / EndpointSlice | Backend list | Pod IP:port for Ready replicas |
| Ingress | HTTP routing rules | North-south L7 to Services (with controller) |
Further reading
- Kubernetes documentation — Services, Ingress, EndpointSlices, DNS for Services and Pods
- Hands-on Part 3 — apply Deployment and Service on a local cluster
- Probes in depth — how readiness controls endpoints
- HPA in depth — scaling replicas behind the same Service
- Cluster RBAC — who can expose Services and Ingress
Blog index · Architecture · Hands-on Part 3 · Probes · Troubleshooting