07 · Services & Networking Basics¶
Not run against a live cluster
Manifests and output below follow documented Service/DNS behavior; not executed against a live cluster in this session.
The problem Services solve¶
Pods are ephemeral — they get new IP addresses every time they're recreated (a crash, a rollout, a rescheduled node). If another part of your app hardcoded a Pod's IP, it would break the moment that Pod was replaced. A Service gives a stable network identity (a fixed virtual IP and DNS name) in front of a set of Pods, selected by label, and load-balances traffic across whichever Pods currently match.
A ClusterIP Service (the default type)¶
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80 # port the Service listens on
targetPort: 80 # port the container listens on
spec.selector works exactly like a Deployment's — it's a label match. Any
Pod (from any Deployment, or a bare Pod) carrying app: web becomes a
backend for this Service.
kubectl apply -f service.yaml
kubectl get svc web
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# web ClusterIP 10.96.123.45 <none> 80/TCP 5s
kubectl get endpoints web
# NAME ENDPOINTS AGE
# web 10.244.1.5:80,10.244.1.6:80,10.244.1.7:80 5s
The Endpoints object (or the newer EndpointSlice) is the live, automatically
maintained list of Pod IPs currently matching the selector — kube-proxy on
every node watches this and programs local routing rules so traffic to the
Service's ClusterIP gets distributed across these endpoints.
ClusterIP (the default) is only reachable from inside the cluster —
other Pods, not your laptop's browser directly (though kubectl
port-forward can tunnel to it for testing, as in Module 03/05).
Service types¶
| Type | Reachable from | Typical use |
|---|---|---|
ClusterIP (default) |
Inside the cluster only | Internal service-to-service traffic |
NodePort |
Any node's IP, on a fixed high port (30000-32767) | Simple external access, dev/test |
LoadBalancer |
The internet, via a cloud load balancer | Production external access (cloud only) |
ExternalName |
N/A — DNS alias to an external name | Pointing at a service outside the cluster |
NodePort example:
apiVersion: v1
kind: Service
metadata:
name: web-nodeport
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080 # optional -- auto-assigned if omitted
kubectl apply -f service-nodeport.yaml
minikube ip # e.g. 192.168.49.2
curl http://192.168.49.2:30080
LoadBalancer (only fully functional on a real cloud provider — on
minikube, minikube tunnel simulates it):
apiVersion: v1
kind: Service
metadata:
name: web-lb
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 80
On EKS/GKE/AKS, creating this triggers the cloud-controller-manager
(Module 02) to provision an actual cloud load balancer and populate
EXTERNAL-IP with its address.
DNS: how Pods find Services by name¶
Kubernetes runs a cluster DNS service (usually CoreDNS) that automatically creates a DNS record for every Service:
From any Pod in the same namespace, you can reach the web Service just by
its short name:
kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- sh
# inside the temporary pod:
wget -qO- http://web # short name, same namespace
wget -qO- http://web.default.svc.cluster.local # fully qualified
From a different namespace, you need at least web.<namespace> since the
short name alone resolves relative to your own namespace's search domain.
Worked example: Service survives Pod churn¶
kubectl apply -f deployment.yaml # 3-replica "web" Deployment from Module 06
kubectl apply -f service.yaml
kubectl get endpoints web
# 3 IPs listed
kubectl delete pod -l app=web --all # kill all 3 Pods at once
kubectl get pods -l app=web --watch # ReplicaSet recreates them with NEW IPs
kubectl get endpoints web
# a DIFFERENT set of 3 IPs -- but the Service's ClusterIP and DNS name never changed
This is the entire point of a Service: everything that talks to web
(other Pods, an Ingress in Level 2) keeps using the same stable name/IP,
completely unaffected by the fact that the underlying Pod IPs changed
underneath it.
How It Actually Works¶
A Service has no process of its own — "the Service" is really three separate mechanisms cooperating:
- The Endpoints/EndpointSlice controller does the label-matching.
It watches Pods and Services; whenever a Pod's labels or a Service's
selector changes, it recomputes the set of Pod IPs (only ones that are
Ready, per readiness probes) matching that selector and writes them into an EndpointSlice object. This is a plain reconciliation loop like any other — the Service object itself never lists Pod IPs; it only holds the selector. - kube-proxy turns EndpointSlices into kernel rules — no process
proxies the traffic. In iptables mode, kube-proxy watches Services
and EndpointSlices and programs
iptablesNAT chains such that a packet destined for the Service's ClusterIP hits a chain that does randomized destination-NAT to one of the backend Pod IPs, chosen via iptables' built-in probabilistic jump (--probability), directly in the kernel netfilter path. In IPVS mode it instead configures a real Linux virtual server with a chosen scheduling algorithm (round-robin by default) via the kernel's IPVS module — faster at scale because IPVS uses a hash table lookup instead of iptables' linear rule chain. Either way, the actual load balancing happens in kernel space on the node that originated the packet, before it ever leaves that node. - ClusterIP itself is not attached to any interface. It's a virtual
IP that only has meaning because every node's kube-proxy has installed
matching NAT/IPVS rules for it — this is why a ClusterIP is
unreachable from outside the cluster (no routing exists to it beyond
those per-node kernel rules) and why
kubectl get endpointsis the actual ground truth of "who will traffic go to," not the Service spec. - DNS resolution is a separate reconciling controller too. CoreDNS
(running as its own Deployment in
kube-system) also watches Services and, for each one, serves an A/AAAA record at<service>.<namespace>.svc.cluster.localresolving to the ClusterIP — this is populated by CoreDNS's Kubernetes plugin polling the API server's Service list, not by any special DNS-to-Service protocol.
Exercise¶
Apply the 3-replica web Deployment from Module 06 plus the ClusterIP
Service above. From a temporary debug Pod (kubectl run tmp --rm -it
--image=busybox:1.36 --restart=Never -- sh), curl/wget the Service by its
short DNS name several times and note that responses may come from
different backend Pods (if you add a per-Pod identifier to the response,
e.g. by mounting the Pod name as an env var via fieldRef, you'd see it
rotate — for now, just confirm the request succeeds repeatedly). Then
delete all the Pods at once and confirm the Service keeps working once the
ReplicaSet replaces them.