Services
Topic 08

Services

NetworkingDiscovery

Pods are ephemeral and their IPs change every time they are recreated. A Service solves this: it is a stable virtual IP and DNS name in front of a changing set of Pods, selected by label. Clients talk to the Service; Kubernetes load-balances to whichever Pods currently match.

Service is the object that makes microservices on Kubernetes practical. Without it, every client would need to track Pod IPs as they come and go. With it, a name like orders just works, no matter how the Pods behind it churn.

Why Pod IPs Are Not Enough

How a Service routes traffic
Client
Servicestable ClusterIP
kube-proxyiptables / IPVS
EndpointSliceready Pod IPs
Podsthe current backends

Each Pod has an IP, but it is transient — reschedule the Pod and the IP changes; scale out and there are several. A Service gives the group one stable virtual IP (the ClusterIP) and a DNS name that persists for the life of the Service. Behind it, an EndpointSlice tracks the current set of ready Pod IPs, updated continuously as Pods come and go.

The selector does the matching. A Service with selector app=orders routes to every ready Pod labeled app=orders. Add a Pod with that label and it joins; remove the label and it leaves. The client never knows or cares.

Service Types

Four types cover the range from internal to external. ClusterIP (the default) exposes the Service on an internal IP reachable only inside the cluster. NodePort opens a fixed port on every node. LoadBalancer provisions an external cloud load balancer pointing at the Service. ExternalName is a DNS alias to an external host, with no proxying.

TypeReachable fromTypical use
ClusterIPInside the cluster onlyService-to-service (the default)
NodePortAny node's IP at a fixed portDev, or behind an external LB
LoadBalancerThe internet, via a cloud LBExposing a service externally
ExternalNameDNS alias, no proxyPointing at an external dependency
A ClusterIP Service
apiVersion: v1
kind: Service
metadata:
  name: orders
spec:
  selector:
    app: orders           # routes to ready Pods with this label
  ports:
    - port: 80            # the Service port
      targetPort: 8080     # the container port

How kube-proxy Routes Traffic

A Service IP is virtual — no process listens on it. On each node, kube-proxy programs the kernel (via iptables or IPVS) so that packets to the ClusterIP are rewritten to one of the backing Pod IPs. Load balancing is per-connection and effectively random; it is not application-aware. For richer routing you reach up to Ingress or a service mesh.

A headless Service (clusterIP: None) skips the virtual IP entirely and instead returns the individual Pod IPs via DNS. This is what stateful systems use when a client needs to address specific Pods rather than a load-balanced pool — the companion to StatefulSets.

Service vs Ingress

A Service is a layer-4 construct: it balances TCP/UDP connections to Pods. It does not understand HTTP, hostnames, paths, or TLS. When you need host- and path-based routing, a single external entry point for many services, or HTTP features, that is the job of Ingress or the Gateway API (Topic 09), which sit in front of Services.

ClusterIP vs NodePort vs LoadBalancer vs Ingress

ClusterIP — internal-only stable IP; the building block the others extend.

NodePort — exposes a port on every node; rarely the right external answer on its own.

LoadBalancer — one cloud load balancer per Service; simple but multiplies cost across many services.

Ingress — one HTTP entry point routing to many Services by host/path — usually what you actually want externally.

Common Mistakes
  • Exposing production traffic via NodePort directly instead of a LoadBalancer or Ingress.
  • Expecting a LoadBalancer Service to work without a cloud controller to provision the external LB.
  • A selector that matches no Pods (a typo), leaving the Service with empty endpoints and silent failures.
  • Provisioning one LoadBalancer per service and paying for dozens of cloud LBs instead of fronting them with one Ingress.
  • Assuming kube-proxy gives HTTP-aware or session-sticky routing — it balances connections, not requests.
Best Practices
  • Default to ClusterIP for internal services; expose externally through Ingress, not a LoadBalancer per service.
  • Verify a Service's EndpointSlice is populated when debugging — empty endpoints almost always mean a selector mismatch.
  • Use a headless Service for stateful workloads that need to address individual Pods.
  • Keep port and targetPort straight: the Service port is what clients use, targetPort is the container's.
  • Reach for Ingress or a mesh when you need HTTP routing, TLS, or host/path rules — not more Services.
RelatedIngress / Gateway API — L7 routing in front of Services (Topic 09)EndpointSlice — the live list of ready Pod IPs behind a ServiceCloud load balancers — what a LoadBalancer Service provisions (ELB, Cloud LB, Azure LB)

Knowledge Check

What problem does a Service primarily solve?

  • It gives a stable IP and DNS name in front of Pods whose own IPs change as they are recreated
  • It builds the container images and pushes them to the registry for its Pods
  • It automatically encrypts all traffic between the backing Pods with mutual TLS
  • It schedules each backing Pod onto whichever cluster node currently has the most free capacity

A Service has empty endpoints and clients get connection failures. What is the most likely cause?

  • The Service selector does not match any ready Pod labels
  • kube-proxy has crashed on every node
  • The Service type is left at its ClusterIP default instead of LoadBalancer
  • The Pods have far too much memory allocated in their resource requests

You need host- and path-based HTTP routing into the cluster. Which object fits?

  • Ingress or the Gateway API, which sit in front of Services
  • A separate NodePort Service defined for each host and path route
  • A headless Service returning Pod IPs directly to clients
  • An ExternalName Service aliased to an external DNS name

You got correct