SkillByAIOpen interactive version →

Lesson 8 / 25

Services and Label Selectors

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.

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.

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.

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']

Quick check: How does a Service know which pods to send traffic to?

  • By pod name prefix
  • By pod IP addresses written in the Service
  • 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.