Skip to content

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.

Client --> Service (stable IP/DNS) --> one of the matching Pods (changes over time)

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:

<service-name>.<namespace>.svc.cluster.local

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 iptables NAT 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 endpoints is 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.local resolving 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.