On a real cloud, Service type: LoadBalancer provisions an external address. On a local cluster it often does nothing useful unless you add a path: MetalLB (VMs), kind’s extra port mappings, minikube tunnel, or an ingress controller bound to localhost. Know which fake you are using.
Practical local options
- kubectl port-forward: always works; good enough for one API. Not how you test “the Ingress path.”
- kind extraPortMappings: map 80/443 on the host to the ingress node. Then
/etc/hostsfor a fake name. - minikube tunnel / k3d port mapping: similar idea. Read the tool’s current docs; they change.
- Ingress (nginx, Traefik) + localhost: the usual local “load balancer” for HTTP. Gateway API if that is what you run in prod — match the family, not the brand.
What you are not testing
Cloud LB idle timeouts, PROXY protocol, security groups, cross-zone behavior. If the bug lives there, you need a cloud environment, not a more clever kind config.
Example
This is a kind cluster config, not a manifest you kubectl apply. Host port 8080 reaches container port 80 on the node, where the ingress controller is listening.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 8080
What breaks it
A cloud LoadBalancer Service copied onto the laptop stays <pending>. Use the port mapping above, or kubectl port-forward, for local HTTP.
apiVersion: v1
kind: Service
metadata:
name: api
spec:
type: LoadBalancer
selector:
app: api
ports:
- port: 80
targetPort: 8080
Pitfalls
- Copy-pasting a cloud LoadBalancer YAML locally and wondering why EXTERNAL-IP is pending forever.
- Binding 80 as root on the laptop when 8080 would do.
- TLS with a public CA on localhost — use mkcert or skip TLS locally and test TLS in staging.
Cluster objects: Kubernetes Deployment. Local cluster: Local Kubernetes.