What is the difference between ClusterIP, NodePort, and LoadBalancer services?
Quick Answer
ClusterIP exposes the Service on an internal-only virtual IP reachable only within the cluster. NodePort opens a static port (30000-32767) on every node's IP, allowing external access. LoadBalancer provisions a cloud provider's load balancer that routes external traffic through NodePorts to Pods.
Detailed Answer
Think of these three Service types like three levels of restaurant accessibility. ClusterIP is like an employee cafeteria inside a corporate office — only people who already work in the building (other Pods in the cluster) can eat there. NodePort is like putting a food window on the side of the building — anyone on the street who knows the building address and the window number can order food, but they have to know the exact window. LoadBalancer is like having a dedicated restaurant entrance with a host stand, a sign on the street, and an automated system that seats you at the next available table — the full public-facing experience with professional traffic management.
ClusterIP is the default Service type. When you create a Service without specifying a type, you get a ClusterIP. Kubernetes assigns it a virtual IP from the Service CIDR range (typically 10.96.0.0/12). This IP exists only as iptables/IPVS rules — no network interface has this IP. Other Pods reach the Service by its DNS name (payments-api.production.svc.cluster.local) which resolves to the ClusterIP. kube-proxy ensures traffic to this IP gets distributed across healthy backend Pods. Use ClusterIP for all internal communication: microservice-to-microservice, app-to-database, worker-to-queue.
NodePort builds on ClusterIP. When you create a NodePort Service, Kubernetes does everything ClusterIP does AND additionally opens a specific port (from range 30000-32767, unless you pick one explicitly) on every node in the cluster. Traffic hitting any node's IP on that port gets forwarded to the Service, which then routes it to a backend Pod. This means you can access the Service from outside the cluster by hitting http://<any-node-ip>:<node-port>. The downside: you're exposing a high-numbered port that clients need to know about, you have no TLS termination or path-based routing, and you're bypassing any external load balancing — if a node goes down, clients hitting that specific node get errors.
LoadBalancer builds on NodePort. When you create a LoadBalancer Service, Kubernetes does everything NodePort does AND additionally requests a load balancer from your cloud provider (AWS NLB/ALB, GCP Network LB, Azure Load Balancer). The cloud provider provisions an external IP or DNS name, configures health checks against the NodePorts, and distributes traffic across healthy nodes. From there, NodePort routing takes over and delivers traffic to Pods. This is the simplest way to expose a Service to the internet, but it has costs: each LoadBalancer Service gets its own cloud load balancer (at $15-20/month on AWS), which adds up fast if you have dozens of services. This cost explosion is why most production teams use an Ingress controller (a single LoadBalancer Service in front of an nginx or Envoy reverse proxy that handles routing for all services based on hostnames and paths).
There's also a fourth type that's rarely discussed: ExternalName. It creates a CNAME DNS record pointing to an external DNS name — no proxy, no ClusterIP, just DNS aliasing. Use it to give an in-cluster DNS name to an external service (like an RDS database endpoint) so your application code uses consistent service discovery regardless of whether the dependency is internal or external.
The production gotcha: when using NodePort or LoadBalancer, be aware of externalTrafficPolicy. The default (Cluster) means any node can handle the traffic, even if it doesn't run the target Pod — it'll forward to another node, adding a hop and losing the client's source IP. Setting externalTrafficPolicy: Local means the node only handles traffic if it has a local Pod, preserving source IP and reducing latency, but creating uneven load distribution if Pods aren't evenly spread across nodes.