NodePort and LoadBalancer
Stage 2 · Guidance
A ClusterIP address is strictly private to the Kubernetes virtual network. When developers run curl http://localhost:30082 from their host laptop, two distinct forwarding mechanisms collaborate to bridge traffic into the cluster.
Two hops across two independent systems
Traffic entering a local kind cluster crosses two separate boundaries:
- Hop 1: Host-to-Node Port Mapping (Docker Engine):
- Configured via
kind-config.yamlwithextraPortMappings. - Forwards
localhost:30082on your laptop into the kind control-plane container on port30082.
- Configured via
- Hop 2: NodePort-to-Pod Routing (Kubernetes
kube-proxy):- The NodePort Service opens port
30082on all cluster nodes. - Node iptables rules pick a ready
bookingPod and translate the destination IP to the Pod IP (10.244.1.5:8082).
- The NodePort Service opens port
Diagram NW-05 — the first hop is Docker's port mapping; the second hop is the Kubernetes NodePort Service routing to a Pod.
Why NodePort is inappropriate for production
While practical for local labs, NodePort presents severe production drawbacks:
- Port range limitations: Restricted to high ports (
30000–32767). Passengers expect standard web ports (80and443). - Cluster-wide surface area: Allocates the chosen port on every single node in the cluster, creating security exposure and firewall complexity.
- No Layer 7 intelligence: Forwards raw TCP streams without host-based routing, TLS termination, or path rewrites.
The LoadBalancer Service type and MetalLB
In public clouds, setting type: LoadBalancer automatically triggers cloud provider controllers (AWS NLB, GCP Cloud Load Balancing) to provision an external IP.
In bare-metal or local kind environments:
- Without an external controller,
type: LoadBalancerremains stuck withEXTERNAL-IP: <pending>. - MetalLB provides the missing controller implementation locally:
- Controller: Watches for
LoadBalancerServices and assigns an IP from a preconfigured pool (172.18.0.50–172.18.0.100). - Speaker: Announces the IP to the local Docker network using ARP (Layer 2 mode).
- Controller: Watches for
Diagram NW-08 — the controller allocates the address, the speaker announces it, and Service routing sends client traffic to a ready Pod.
Service exposure comparison
| Type | Access Boundary | Ideal Usage |
|---|---|---|
ClusterIP | Internal cluster only | Backend microservices, databases |
NodePort | Node IP + High Port | Local dev, manual smoke testing |
LoadBalancer (MetalLB / Cloud) | Dedicated external IP | Public edge gateways, HTTP/HTTPS proxies |
Envoy Gateway / Ingress | L7 routing behind LoadBalancer | Modern multi-host, path-routed microservice architectures |
Evidence and limits
- 1. Docker host-port mapping: Confirm kind container has ports exposed:
docker ps --filter name=apollo11 --format "{{.Ports}}"
- 2. NodePort definition: Check assigned node ports:
kubectl get svc -n apollo-airlines-apps -o wide
- 3. MetalLB allocation: Verify external IP is assigned (not
<pending>):kubectl get svc -n envoy-gateway-system - 4. Endpoint health: Ensure the Service has backing Pods ready to receive traffic:
kubectl get endpoints -n apollo-airlines-apps