Lab 13 - Services and DNS

Lab 13 - Services and DNS

Goal: reach Pods by a stable name, from inside and from outside. Chapter: Networking, CNI, Services and DNS

# 1 - something to expose
cd ~
kubectl create deploy web --image nginx:1.27 --replicas 3
kubectl get pods -l app=web -o wide      # note the Pod IPs
# 2 - ClusterIP, the default
kubectl expose deploy web --port 80 --name web-svc
kubectl get svc web-svc
kubectl get endpointslices -l kubernetes.io/service-name=web-svc

The EndpointSlice is the interesting object: it holds the three Pod IPs. The Service is just a stable front; the EndpointSlice is the live membership list, maintained by a controller as Pods become ready.

# 3 - reach it by name from inside the cluster
kubectl run tmp --image busybox:1.36 --restart=Never -it --rm -- \
  wget -qO- --timeout=3 web-svc | head -3

# 4 - what DNS actually returns
kubectl run tmp --image busybox:1.36 --restart=Never -it --rm -- \
  nslookup web-svc.default.svc.cluster.local

The name resolves to the ClusterIP, not to a Pod. Traffic to that virtual IP is rewritten by kube-proxy on the node to one of the real Pod IPs.

# 5 - stability: destroy every Pod, keep the address
kubectl get svc web-svc -o jsonpath='{.spec.clusterIP}{"\n"}'
kubectl delete pod -l app=web
kubectl get pods -l app=web -o wide      # all new IPs
kubectl get svc web-svc -o jsonpath='{.spec.clusterIP}{"\n"}'   # unchanged
kubectl get endpointslices -l kubernetes.io/service-name=web-svc  # new IPs inside

That is the whole value proposition: the Pods are cattle, the Service name is not.

From outside the cluster

# 6 - NodePort opens the same port on EVERY node
kubectl delete svc web-svc
kubectl expose deploy web --port 80 --type NodePort --name web-svc
PORT=$(kubectl get svc web-svc -o jsonpath='{.spec.ports[0].nodePort}')
echo $PORT
for n in control01 worker01 worker02 worker03; do
  echo -n "$n: "; curl -s --max-time 3 http://$n:$PORT | grep -o '<title>.*</title>'
done

Every node answers, including nodes running none of the Pods - kube-proxy forwards for the whole cluster. Note how quickly this gets unmanageable with more than a couple of applications, which is the argument for the Gateway API in Lab 16.

# 7 - port-forward, for debugging only
kubectl port-forward svc/web-svc 8080:80 &
curl -s localhost:8080 | head -3
kill %1

port-forward runs on your workstation and dies with the terminal. Useful for poking at something that has no external access; never part of a solution.

Discovery

Infodiscover

Task A. Create namespace backend with a Deployment api (nginx:1.27) and a ClusterIP Service api. From a Pod in default, reach it by name. Expected: the nginx welcome page.

kubectl create ns backend
kubectl create deploy api --image nginx:1.27 -n backend
kubectl expose deploy api --port 80 -n backend
kubectl run tmp --image busybox:1.36 --restart=Never -it --rm -- \
  wget -qO- --timeout=3 api.backend | head -3

api alone would fail - it resolves in the caller’s namespace.

Task B. Break the Service: change its selector to app=nothing. What do the EndpointSlice and a request show? Fix it again.

kubectl patch svc api -n backend -p '{"spec":{"selector":{"app":"nothing"}}}'
kubectl get endpointslices -n backend -l kubernetes.io/service-name=api   # no endpoints
kubectl run tmp --image busybox:1.36 --restart=Never -it --rm -- \
  wget -qO- --timeout=3 api.backend        # times out
kubectl patch svc api -n backend -p '{"spec":{"selector":{"app":"api"}}}'

Empty endpoints is the number-one Service problem - check the selector first.

Task C. Which mode does kube-proxy run in on this cluster?

kubectl get cm -n kube-system kube-proxy -o yaml | grep -E '^\s+mode:'

Empty means the default, iptables.

Clean up

# 8 -
kubectl delete svc web-svc
kubectl delete deploy web
kubectl delete ns backend --ignore-not-found