# Services and Label Selectors — Kubernetes

Source: https://www.skillbyai.com/en/kubernetes/n-service

> A stable address in front of changing pods.

## Selector -> endpoints

A **Service** gives a set of pods a stable virtual IP and DNS name, and load-balances across them. It finds its pods with a **label selector**; Kubernetes keeps the list of matching, **ready** pods (endpoints) up to date. Types: **ClusterIP** (internal only, the default), **NodePort** (a port on every node), **LoadBalancer** (a cloud load balancer). `port` is what clients use; `targetPort` is the container port. A selector that is too broad routes traffic to pods you did not intend.

## Stable names for moving pods

Services select pods by label; Ingress and Gateway route outside traffic; DNS ties it together.

![Three ideas: Services and selectors, ingress, DNS.](assets/figures/kubernetes/section-3-map.svg) — Figure 3.1 — Services, ingress and DNS.

## Generating a ClusterIP Service, run

I ran this with kubectl 1.37.0 using --dry-run=client (or kubectl kustomize), which generates manifests locally without a cluster; nothing was applied to a live cluster. Clients connect to port 80; traffic goes to port 8080 on pods labelled app=web.

```bash
kubectl create service clusterip web --tcp=80:8080 --dry-run=client -o yaml
```

Output:

```
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web
  name: web
spec:
  ports:
  - name: 80-8080
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: web
  type: ClusterIP
status:
  loadBalancer: {}
```

## Which pods does a selector match? run

I ran this with Python 3. It is a simplified model of Kubernetes behaviour for learning, not the real controller code. Selector app=web also matches a stray debug pod; adding tier=frontend excludes it; adding version=1.5 selects only the new version, which is how some canary setups route traffic.

```python
pods = [
    {"name": "web-7c9-a", "labels": {"app": "web", "tier": "frontend", "version": "1.4"}},
    {"name": "web-7c9-b", "labels": {"app": "web", "tier": "frontend", "version": "1.5"}},
    {"name": "api-55f-a", "labels": {"app": "api", "tier": "backend"}},
    {"name": "debug-pod", "labels": {"app": "web"}},
]
def matches(selector, labels):          # equality-based selector: every key must match
    return all(labels.get(k) == v for k, v in selector.items())
for sel in [{"app": "web"}, {"app": "web", "tier": "frontend"}, {"app": "web", "version": "1.5"}]:
    print(sel, "->", [p["name"] for p in pods if matches(sel, p["labels"])])
```

Output:

```
{'app': 'web'} -> ['web-7c9-a', 'web-7c9-b', 'debug-pod']
{'app': 'web', 'tier': 'frontend'} -> ['web-7c9-a', 'web-7c9-b']
{'app': 'web', 'version': '1.5'} -> ['web-7c9-b']
```

**Quiz:** How does a Service know which pods to send traffic to?

- [ ] By pod name prefix
- [ ] By pod IP addresses written in the Service
- [x] Its label selector matches pod labels, and only ready pods receive traffic
- [ ] Randomly across the cluster

*Answer:* Its label selector matches pod labels, and only ready pods receive traffic. Labels connect Services to pods.
