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.yaml with 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

  1. You create a Service with a selector (app: web).
  2. The control plane finds matching Ready Pods and writes their IPs into EndpointSlices.
  3. 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.
  4. CoreDNS gives it a name: web.<namespace>.svc.cluster.local.

Changed since the book:

Endpoints are now tracked by EndpointSlices (kubectl get endpointslices). The old Endpoints API 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=nginx
apiVersion: 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 deployment labels Pods app=nginx. The book’s kubectl run labelled them run=nginx, so old Service YAML with run: 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, nginx resolves. From another namespace use nginx.<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/agnhost or nicolaka/netshoot are 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 tunnel or 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>/web to the nginx Service.
  • 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 ingressClassName means 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/servicePort became service.name/service.port.number. pathType is required. The kubernetes.io/ingress.class annotation was replaced by ingressClassName.

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 the Gateway, app teams own their HTTPRoutes. 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 exec into. 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/v1 with ingressClassName + pathType. Gateway API is the future.
  • port-forward for quick local access. Relabel a Pod to pull it out of rotation.