Skip to content

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

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 ServiceclusterIP: None. No virtual IP; DNS returns the Pod IPs directly. This is what a StatefulSet needs so that db-0.db is 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 / nodePortport is what clients connect to on the Service; targetPort is the port on the Pod; nodePort is 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 on

Three things flow from that selector:

Service(selector: app=web) ──▶ Endpoints ──▶ Pod IPs ──▶ containers
                                   ▲
                            maintained continuously
                            by the endpoints controller

Exercises

  1. 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=web

    Note that --target-port=8080 matters: the unprivileged nginx image listens on 8080, not 80.

  2. 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 -3
  3. Break 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 nothing

    The 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.

  4. 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: 5432

    This 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.

  5. 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 targetPort at a closed port and you get connection refused instead — 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

TypeIn your clusterOn the shared cluster
ClusterIPkubectl port-forwardkubectl port-forward
NodePortworks, on the node’s IPworks
LoadBalancerstays Pending unless a load-balancer controller is installedgets a real Azure load balancer

port-forward is the tool you will use most while learning:

kubectl port-forward svc/web 8080:80

It 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.
  • targetPort must match what the container actually listens on — the commonest cause of a Service that exists and refuses connections.
  • A LoadBalancer staying Pending is 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.