# Services and Service Discovery

## 1. Why Services

Pods are ephemeral: they get new IPs every time they're recreated. A **Service** gives a stable virtual IP and DNS name in front of a changing set of Pods selected by labels, and load-balances connections across the ready ones.

```yaml
apiVersion: v1
kind: Service
metadata: { name: api, namespace: petpal }
spec:
  selector: { app: api }        # matches Pod labels
  ports:
    - name: http
      port: 80                  # Service port
      targetPort: http          # container port (name or number)
  type: ClusterIP
```

Other Pods reach it as `http://api` (same namespace), `http://api.petpal` or `api.petpal.svc.cluster.local` thanks to **CoreDNS**.

## 2. Service Types

| Type | Reachable from | Use |
|---|---|---|
| **ClusterIP** (default) | inside the cluster | internal service-to-service traffic |
| **NodePort** | every node's IP on a port (30000–32767) | rarely directly; building block for LBs |
| **LoadBalancer** | external cloud load balancer (NLB/ALB, etc.) | expose a TCP/UDP service publicly or internally |
| **ExternalName** | DNS CNAME to an external name | alias for an external database host |
| **Headless** (`clusterIP: None`) | DNS returns Pod IPs directly | StatefulSets, client-side load balancing (`05-storage_and_statefulsets/`) |

HTTP routing by host/path for many services through one load balancer is done with **Ingress** or the **Gateway API** (`04-ingress_and_networking/`).

## 3. Endpoints and Readiness

The **EndpointSlice** controller keeps a list of IPs of Pods that match the selector **and are Ready**. A Pod failing its readiness probe is removed from endpoints (no traffic) without being restarted. During termination, a Pod is removed from endpoints at the same time SIGTERM is sent — and because removal propagates asynchronously to every node, apps should keep serving for a few seconds after SIGTERM (a `preStop` sleep is the common trick):

```yaml
lifecycle:
  preStop:
    exec: { command: ["sh", "-c", "sleep 5"] }
```

## 4. Session Affinity, Traffic Policies, Topology

- `sessionAffinity: ClientIP` pins clients to a Pod (prefer stateless apps instead).
- `externalTrafficPolicy: Local` preserves client source IP and avoids extra hops (only nodes with Pods receive traffic).
- `internalTrafficPolicy: Local` and **topology-aware routing** keep traffic within a node/zone to cut latency and cross-AZ costs.
- Long-lived connections (gRPC/HTTP2) are balanced per **connection**, not per request — use client-side balancing over a headless Service or a service mesh.

## 5. Debugging Services

```bash
kubectl get svc,endpointslices -n petpal -l app=api      # are there endpoints?
kubectl get pods -n petpal -l app=api --show-labels       # do labels match the selector?
kubectl run tmp --rm -it --image=busybox:1.36 -- wget -qO- http://api.petpal/health
kubectl port-forward svc/api 8080:80 -n petpal            # reach it from your laptop
```

90% of "Service doesn't work": selector doesn't match Pod labels, `targetPort` wrong, or Pods not Ready.

## Under the Hood: From Service IP to Pod

A ClusterIP is **virtual** — no interface owns it. kube-proxy on every node watches Services and EndpointSlices and programs the kernel: in **iptables mode**, a `KUBE-SERVICES` chain matches the Service IP:port and jumps to a per-service chain that picks one endpoint chain at random (probabilistic rules), which **DNATs** the packet to a Pod IP:port. **IPVS mode** uses kernel load-balancing tables (better at thousands of Services), and eBPF data planes (Cilium) do the lookup in BPF maps attached to sockets/interfaces, skipping iptables entirely. The CNI then routes the packet to the Pod's IP, possibly on another node (via VPC routing, overlay encapsulation like VXLAN, or BGP). Because the decision happens per connection at the client's node, there's no central load balancer hop for in-cluster traffic. CoreDNS answers `api.petpal.svc.cluster.local` with the ClusterIP (or Pod IPs for headless Services).

![A client Pod resolves api.petpal via CoreDNS to the Service ClusterIP; kube-proxy rules on its node DNAT the connection to one of the ready Pod IPs from the EndpointSlice, while a not-ready Pod is excluded](diagrams/01-service-routing.svg)

## Common Pitfalls

1. **Selector/label mismatch** — Service with no endpoints.
2. **`LoadBalancer` per service** — dozens of cloud LBs and bills; use Ingress/Gateway.
3. **No preStop delay** — 502s during rollouts.
4. **gRPC behind a ClusterIP** — all requests stick to one Pod.
5. **Hard-coding Pod IPs**.

## Quick Reference

```
Service = stable VIP + DNS (svc.ns.svc.cluster.local) → ready Pods by selector
types: ClusterIP · NodePort · LoadBalancer · ExternalName · headless (clusterIP: None)
EndpointSlices = ready Pod IPs · kube-proxy iptables/IPVS/eBPF DNAT · preStop sleep for graceful drain
debug: endpoints? labels? targetPort? readiness? · port-forward · busybox wget
