Services
Pods come and go, and every one of them gets a new IP address. Nothing can safely be configured to talk to a Pod IP. A Service is the stable thing in front of them: one virtual address and one DNS name, pointing at whatever Pods currently match its selector.
Reference
- https://kubernetes.io/docs/concepts/services-networking/service/
- https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
Terminology
- ClusterIP — The default. A virtual IP reachable only from inside the cluster. Every node programs forwarding rules for it; no single Pod owns it.
- NodePort — A port opened on every node that forwards to the Service. Useful on a cluster you own, and a useful debugging fallback.
- LoadBalancer — Asks a cloud provider (or a tool like MetalLB) for an external address and forwards to the Service. This is where “Pending” happens when there is nothing to satisfy the request.
- ExternalName — No proxying at all: a DNS alias. The Service name resolves to a CNAME you specify, which is handy for pointing at a managed database without hard-coding its hostname in every app.
- Headless Service —
clusterIP: None. No virtual IP; DNS returns the Pod IPs directly. This is what a StatefulSet needs so thatdb-0.dbis addressable. - Endpoints / EndpointSlices — The list of Pod IPs a Service currently points at. Kubernetes maintains it from the selector. If you ever want to know what a Service is actually doing, read this.
- port / targetPort / nodePort —
portis what clients connect to on the Service;targetPortis the port on the Pod;nodePortis the port on the node (30000–32767 by default).
The Shape of It
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web # matches Pod labels — not Pod names
ports:
- port: 80 # what clients use
targetPort: 8080 # what the container listens onThree things flow from that selector:
Service(selector: app=web) ──▶ Endpoints ──▶ Pod IPs ──▶ containers
▲
maintained continuously
by the endpoints controllerExercises
Create the pair and watch the endpoints appear.
kubectl create deployment web --image=docker.io/nginxinc/nginx-unprivileged:1.27-alpine --replicas=3 kubectl expose deployment web --port=80 --target-port=8080 kubectl get svc,endpoints web kubectl get endpointslices -l kubernetes.io/service-name=webNote that
--target-port=8080matters: the unprivileged nginx image listens on 8080, not 80.Reach it from inside the cluster. The most reliable test is another Pod, because it exercises DNS, kube-proxy and the network path together:
kubectl run client --image=docker.io/library/busybox --restart=Never -it --rm -- sh # inside: nslookup web wget -qO- web:80 | head -3 wget -qO- web.default.svc.cluster.local:80 | head -3Break the selector on purpose. This is the single most valuable habit in this module:
kubectl label pod -l app=web app=renamed --overwrite kubectl get endpoints web # empty — the Service now points at nothingThe Service is healthy, the Pods are healthy, and traffic goes nowhere. Every object reports what you asked for. When something “cannot connect”, this is the first thing to check.
Try a manual endpoint. A Service with no selector gets no automatic endpoints, so you can point it at something outside the cluster:
apiVersion: v1 kind: Service metadata: name: external-db spec: ports: - port: 5432 --- apiVersion: v1 kind: Endpoints metadata: name: external-db # must match the Service name subsets: - addresses: - ip: 10.0.0.10 ports: - port: 5432This is how you give an external database a normal in-cluster DNS name, and it is worth knowing that the Endpoints object’s name has to match the Service exactly.
See the difference between a timeout and a refusal. Delete the endpoints and connect to the ClusterIP: it hangs and times out. Scale to zero and it also hangs. Point
targetPortat a closed port and you getconnection refusedinstead — because something answered and said no. Those two symptoms send you to different places, and knowing which is which saves a lot of guessing.
Access From Outside
| Type | In your cluster | On the shared cluster |
|---|---|---|
| ClusterIP | kubectl port-forward | kubectl port-forward |
| NodePort | works, on the node’s IP | works |
| LoadBalancer | stays Pending unless a load-balancer controller is installed | gets a real Azure load balancer |
port-forward is the tool you will use most while learning:
kubectl port-forward svc/web 8080:80It opens a tunnel through the API server, so it needs no cluster networking to work at all — which also makes it a poor test of anything.
What to Take Away
- A Service gives a stable name and address to an unstable set of Pods, and finds them by label selector.
kubectl get endpoints <service>tells you whether the selector is doing what you think.targetPortmust match what the container actually listens on — the commonest cause of a Service that exists and refuses connections.- A
LoadBalancerstayingPendingis normal where no controller can satisfy it, and says nothing about your manifest.
Where This Is Practised
Lab 03 in the workbook is built around this. Run labctl list to see whether it is ready yet.