Overview
- Chapter 5 of the Kubernetes Cookbook (plus Recipes 12.2 and 12.3, which are service tricks): giving Pods a stable address, DNS names, service types, and getting HTTP traffic in from outside.
- My basics: Services (the four service types, L4 vs L7) and Ingress (my
app-ingress.yamlwith host-based rules). This note adds the recipes, debugging and the modern pieces (IngressClass, Gateway API). - Back to the hub: Kubernetes Cookbook
How a Service Actually Works
- You create a Service with a selector (
app: web). - The control plane finds matching Ready Pods and writes their IPs into EndpointSlices.
- kube-proxy on every node programs iptables/IPVS/nftables, so the Service’s virtual ClusterIP forwards to one of those Pod IPs. Some CNIs (Cilium) replace kube-proxy with eBPF.
- CoreDNS gives it a name:
web.<namespace>.svc.cluster.local.
Changed since the book:
Endpoints are now tracked by EndpointSlices (
kubectl get endpointslices). The oldEndpointsAPI is deprecated (since 1.33) but still readable. kube-dns was replaced by CoreDNS. kube-proxy gained an nftables mode.
Recipes
Recipe 5.1 — Create a Service to Expose Your App
- Problem: Pods come and go (see Pods), but clients need one stable address.
- Solution:
kubectl create deployment nginx --image=nginx:1.27
kubectl expose deployment nginx --port=80 --target-port=80
kubectl get svc nginx
kubectl get endpointslices -l kubernetes.io/service-name=nginxapiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: ClusterIP # default
selector:
app: nginx # must match the Pod labels
ports:
- name: http
port: 80 # the Service's port
targetPort: 80 # the container's port (number or named port)- Discussion: Deployments and Services both use labels but do different jobs. The Deployment keeps Pods alive, the Service routes to them.
- Gotchas: Service “doesn’t work”? Check the endpoints first. Empty endpoints mean the selector doesn’t match, or no Pod is Ready.
Changed since the book:
kubectl create deploymentlabels Podsapp=nginx. The book’skubectl runlabelled themrun=nginx, so old Service YAML withrun:selectors won’t match.
Source: K8s Cookbook, Recipe 5.1
Recipe 5.2 — Verify a Service’s DNS Entry
- Problem: Is cluster DNS resolving your Service?
- Solution:
kubectl run dnstest --image=busybox:1.36 -it --rm --restart=Never -- nslookup nginx
# full name: nginx.default.svc.cluster.local- Discussion: From the same namespace,
nginxresolves. From another namespace usenginx.<ns>. The answer should be the Service’s ClusterIP (headless Services return Pod IPs instead). - Gotchas: Pin busybox to a known tag, since some busybox builds have a flaky
nslookup.registry.k8s.io/e2e-test-images/agnhostornicolaka/netshootare nicer debugging images.
Source: K8s Cookbook, Recipe 5.2
Recipe 5.3 — Change a Service’s Type
- Problem: You started with ClusterIP but now need NodePort or a cloud LoadBalancer.
- Solution:
kubectl patch svc webserver -p '{"spec":{"type":"NodePort"}}'
kubectl get svc webserver # PORT(S) shows 80:3xxxx/TCP
# or edit the YAML (type: LoadBalancer) and kubectl apply- Discussion: NodePort opens a port (30000–32767) on every node. LoadBalancer asks the cloud for an external LB, which on-prem or locally needs MetalLB,
minikube tunnelor cloud-provider-kind. - Gotchas: Each LoadBalancer Service = one paid cloud LB. For many HTTP apps, put one Ingress/Gateway in front instead.
Source: K8s Cookbook, Recipe 5.3
Recipe 5.4 — Deploy an Ingress Controller
- Problem: Ingress objects do nothing until a controller watches them.
- Solution:
minikube addons enable ingress # minikube
# anywhere else: install a controller with Helm, e.g. Traefik, HAProxy,
# Contour, Kong, Envoy Gateway, or your cloud's controller (GKE, AWS LB Controller, AGIC)
kubectl get ingressclass- Discussion: Each controller registers an IngressClass. Your Ingress points at one with
ingressClassName. - Gotchas: Controller-specific annotations (rewrites, auth, rate limits) aren’t portable, so standardise on one controller (Best Practices).
Changed since the book:
The community ingress-nginx project was retired in 2026 (no more fixes). New setups should pick another controller or move to Gateway API (below). My existing Ingress note’s
nginx.ingress.kubernetes.io/*annotations are specific to ingress-nginx.
Source: K8s Cookbook, Recipe 5.4
Recipe 5.5 — Make Services Reachable from Outside (Ingress)
- Problem: Route
http://<host>/webto thenginxService. - Solution:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-public
spec:
ingressClassName: nginx # whatever `kubectl get ingressclass` shows
tls:
- hosts: ["demo.example.com"]
secretName: demo-tls # kubectl create secret tls demo-tls --cert=… --key=…
rules:
- host: demo.example.com
http:
paths:
- path: /web
pathType: Prefix # Prefix | Exact | ImplementationSpecific
backend:
service:
name: nginx
port:
number: 80- Discussion: The controller watches Ingress objects and reconfigures its proxy. Use cert-manager to get and renew TLS certificates (Let’s Encrypt) automatically.
- Gotchas: Forgetting
ingressClassNamemeans no controller may pick the Ingress up (unless one is marked as the default class).
Changed since the book:
extensions/v1beta1→networking.k8s.io/v1.serviceName/servicePortbecameservice.name/service.port.number.pathTypeis required. Thekubernetes.io/ingress.classannotation was replaced byingressClassName.
Source: K8s Cookbook, Recipe 5.5
Modern Extra — Gateway API (Ingress’s successor)
- Why: Ingress only covers basic HTTP routing, and everything else lives in vendor annotations. Gateway API (
gateway.networking.k8s.io/v1, GA) splits responsibilities: the platform team owns theGateway, app teams own theirHTTPRoutes. It has first-class header matching, traffic splitting and TCP/gRPC routes.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
spec:
parentRefs:
- name: public-gateway # a Gateway managed by the platform team
hostnames: ["demo.example.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /web
backendRefs:
- name: nginx
port: 80
weight: 90
- name: nginx-canary # built-in canary split
port: 80
weight: 10- Gotchas: The Gateway API CRDs plus a controller (Envoy Gateway, Istio, Cilium, Traefik, NGINX Gateway Fabric, cloud implementations) must be installed first.
Recipe 12.2 — Take One Pod Out of a Service (for Inspection)
- Problem: One replica is misbehaving. You want it out of rotation but still alive to debug.
- Solution: Change its label so the Service selector (and the ReplicaSet) no longer match:
kubectl label pod nginx-7c5d-abcde app=quarantine --overwrite- Discussion: The Service drops it from its endpoints, and the ReplicaSet notices it’s “missing” and starts a replacement. The quarantined Pod keeps running for you to
execinto. Delete it when done.
Source: K8s Cookbook, Recipe 12.2
Recipe 12.3 — Reach a ClusterIP Service from Your Laptop
- Problem: Test an internal Service without exposing it.
- Solution:
kubectl port-forward svc/nginx 8080:80 # simplest
curl localhost:8080
kubectl proxy & # via the API server
curl localhost:8001/api/v1/namespaces/default/services/nginx:80/proxy/Changed since the book:
The old proxy path
/api/v1/proxy/namespaces/...was removed. It’s now/api/v1/namespaces/<ns>/services/<name>[:port]/proxy/.
Source: K8s Cookbook, Recipe 12.3
Summary
- A Service = selector → EndpointSlices of Ready Pods → a virtual IP plus a DNS name (
svc.ns.svc.cluster.local). - Debug Services by checking endpoints and labels first.
- ClusterIP (internal) → NodePort (node port) → LoadBalancer (cloud LB). For HTTP, use one Ingress or Gateway.
- Ingress needs a controller and
networking.k8s.io/v1withingressClassName+pathType. Gateway API is the future. port-forwardfor quick local access. Relabel a Pod to pull it out of rotation.