NodePort vs LoadBalancer

Key Differences in Kubernetes

ClusterIP is the default Service type that exposes Pods internally within the cluster. NodePort extends ClusterIP by opening a static port on every node for external access. LoadBalancer extends NodePort by provisioning an external cloud load balancer that routes traffic to the NodePorts. Choose based on whether traffic is internal-only, needs basic external access, or requires production-grade external load balancing.

Side-by-Side Comparison

DimensionNodePortLoadBalancer
AccessibilityExternal — accessible via any node's IP on a high port (30000-32767)External — accessible via a dedicated cloud load balancer IP
ClusterIP BaselineIncludes a ClusterIP automaticallyIncludes both ClusterIP and NodePort automatically
Port RangeRestricted to 30000-32767 by defaultUses standard ports (80, 443) on the load balancer
Load BalancingClient must choose which node to hit; no external LBCloud LB distributes traffic across nodes automatically
CostFree — no additional cloud resourcesCosts money — provisions a cloud load balancer per Service
SSL TerminationNot supported nativelySupported via cloud LB annotations
Health ChecksNo external health checking of nodesCloud LB health-checks nodes and routes around failures
Production ReadinessSuitable for development and testingSuitable for production external traffic

Detailed Breakdown

The Three Service Types

Each Service type builds on the previous one:

ClusterIP (internal only)
  └── NodePort (adds external port on every node)
       └── LoadBalancer (adds cloud load balancer)

ClusterIP — Internal Communication

apiVersion: v1
kind: Service
metadata:
  name: backend-api
spec:
  type: ClusterIP  # default, can be omitted
  selector:
    app: backend-api
  ports:
    - port: 80
      targetPort: 8080

ClusterIP assigns a virtual IP (e.g., 10.96.45.12) that is only routable within the cluster. Pods can reach this Service at backend-api.default.svc.cluster.local:80. External clients cannot reach it.

This is the right choice for any internal service — databases, caches, internal APIs that only other Pods need to access.

NodePort — Basic External Access

apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  type: NodePort
  selector:
    app: web-app
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080  # optional, auto-assigned if omitted

NodePort opens port 30080 on every node in the cluster. External clients can reach the Service at <any-node-ip>:30080. Internally, the Service still has a ClusterIP.

The drawbacks are clear:

  • Ports are limited to 30000-32767 — no standard 80/443
  • Clients need to know a node IP (and handle node failures)
  • No built-in external load balancing or health checking
  • One port per Service across the entire cluster

NodePort works well for development, testing, or bare-metal clusters where you provide your own load balancer (like MetalLB or an F5).

LoadBalancer — Production External Access

apiVersion: v1
kind: Service
metadata:
  name: web-app
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
spec:
  type: LoadBalancer
  selector:
    app: web-app
  ports:
    - port: 443
      targetPort: 8080

When you create a LoadBalancer Service in a cloud environment, the cloud controller manager provisions an external load balancer (AWS NLB/ALB, GCP LB, Azure LB). The load balancer gets a public IP or DNS name and distributes traffic across the NodePorts on healthy nodes.

kubectl get svc web-app
# NAME      TYPE           CLUSTER-IP     EXTERNAL-IP        PORT(S)
# web-app   LoadBalancer   10.96.45.12    a1b2c3.elb.aws.com 443:31234/TCP

The EXTERNAL-IP field shows the cloud load balancer endpoint. Clients connect to this address on port 443, and traffic flows through the load balancer to NodePorts to Pods.

Traffic Flow Comparison

# ClusterIP
Pod → ClusterIP (kube-proxy rules) → Backend Pod

# NodePort
External Client → Node:30080 → ClusterIP → Backend Pod

# LoadBalancer
External Client → Cloud LB:443 → Node:30080 → ClusterIP → Backend Pod

externalTrafficPolicy

By default, NodePort and LoadBalancer Services can route traffic to Pods on any node, even if the traffic arrives at a node that does not have a matching Pod. This causes an extra network hop.

spec:
  externalTrafficPolicy: Local

Setting externalTrafficPolicy: Local ensures traffic is only routed to Pods on the node that received it. This preserves the client's source IP and avoids the extra hop, but it means nodes without a Pod will fail health checks and receive no traffic from the load balancer.

Cost Considerations

Each LoadBalancer Service provisions a separate cloud load balancer. At ~$15-25/month per LB, exposing many Services this way gets expensive. The common pattern is:

  1. Deploy one LoadBalancer Service for an Ingress controller
  2. Use ClusterIP for all backend Services
  3. Route traffic through Ingress rules
# One LoadBalancer for the Ingress controller
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  type: LoadBalancer
  selector:
    app: ingress-nginx
  ports:
    - name: http
      port: 80
    - name: https
      port: 443

This gives you one external entry point, and Ingress rules fan out traffic to many ClusterIP Services internally.

Bare-Metal Considerations

LoadBalancer type requires a cloud provider integration. On bare-metal clusters, it will stay in Pending state indefinitely unless you install a bare-metal load balancer solution:

  • MetalLB — assigns real IPs from a configured pool using ARP or BGP
  • kube-vip — provides a virtual IP using leader election

Without these tools, bare-metal clusters typically use NodePort combined with an external load balancer or DNS round-robin.

Choosing the Right Type

The decision tree is straightforward:

  • Internal-only traffic → ClusterIP
  • External access in development or bare-metal → NodePort
  • External access in cloud production → LoadBalancer (usually just for the Ingress controller)
  • HTTP external access with routing → ClusterIP Services behind an Ingress

Use NodePort when...

  • You need quick external access during development or testing
  • You're running on bare-metal without a cloud load balancer
  • You have your own external load balancer or DNS-based routing
  • Cost is a concern and you don't need cloud LB features

Use LoadBalancer when...

  • You need production-grade external access with automatic failover
  • You're running in a cloud environment (AWS, GCP, Azure)
  • You need standard ports (80/443) without high-port workarounds
  • You want cloud-native health checking and SSL termination
  • You're exposing non-HTTP services that can't use Ingress

Model Interview Answer

Kubernetes has three main Service types that build on each other. ClusterIP is the default — it gives the Service an internal IP reachable only within the cluster, used for Pod-to-Pod communication. NodePort extends ClusterIP by opening a static port (30000-32767) on every node, allowing external clients to reach the Service via any node's IP. LoadBalancer extends NodePort by asking the cloud provider to provision an external load balancer that distributes traffic across the NodePorts. In production, you typically use LoadBalancer for direct external exposure or, more commonly, a single LoadBalancer fronting an Ingress controller that routes to ClusterIP Services internally.

Related Comparisons