Articles

Kubernetes Service Types: ClusterIP vs NodePort vs LoadBalancer

Kubernetes offers four Service types for exposing pods on the network. How ClusterIP, NodePort, LoadBalancer, and ExternalName each route traffic.

Chisato Chisato · · 4 min read
Kubernetes logo on a technical background

A Kubernetes Service gives a stable network identity to a set of pods whose individual IP addresses change constantly as they’re rescheduled, scaled, or replaced. The type field on a Service determines where that stable identity is reachable from — inside the cluster only, on every node’s IP, or from the public internet — and picking the wrong one is one of the more common sources of “I can’t reach my app” confusion in a new cluster.

ClusterIP: the default, internal-only

apiVersion: v1
kind: Service
metadata:
  name: backend
spec:
  type: ClusterIP
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 8080

ClusterIP is the default type if you don’t specify one. It allocates a stable virtual IP address that’s only routable within the cluster, and registers a DNS name (backend.namespace.svc.cluster.local, or just backend from within the same namespace) that resolves to it. Any pod in the cluster can reach this service by name; nothing outside the cluster can reach it at all.

This is the right default for internal service-to-service traffic — a frontend calling a backend API, a backend calling a database proxy — which is the majority of traffic inside a typical microservices deployment. It pairs naturally with Kubernetes network policies, which can further restrict which pods are allowed to reach a given ClusterIP service, even though every pod can technically resolve its DNS name.

NodePort: exposed on every node’s IP

spec:
  type: NodePort
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080

NodePort builds on ClusterIP — it still gets the internal virtual IP — but additionally opens a specific port (from a default range, typically 30000–32767) on every node in the cluster, regardless of whether that node is actually running a pod for the service. Traffic hitting <any-node-IP>:30080 gets routed to the service, which then load-balances it to a healthy pod, possibly on a different node.

NodePort is mostly a building block rather than a production-facing choice on its own: it’s how LoadBalancer type services are implemented under the hood in most cluster setups, and it’s occasionally used directly for quick testing or in bare-metal clusters without a cloud load balancer integration available. Exposing a NodePort directly to the internet means every node’s firewall has to allow that port, and clients need to know node IPs directly — brittle compared to the alternatives below.

LoadBalancer: the cloud-provisioned front door

spec:
  type: LoadBalancer
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 8080

LoadBalancer is what most production internet-facing services actually use. It builds on NodePort, but additionally asks the cloud provider the cluster is running on to provision an actual external load balancer — an AWS Network Load Balancer, a GCP Load Balancer, an Azure Load Balancer — with a real public IP address that routes to the NodePort on the cluster’s nodes.

This is the type that requires the most external infrastructure to actually work: it depends on the cluster having a cloud-controller-manager integration that knows how to talk to the underlying cloud provider’s API. A LoadBalancer service created in a bare-metal or local cluster without that integration will typically sit indefinitely with its external IP showing <pending>, because nothing exists to fulfill the request — a common point of confusion for anyone testing manifests locally before deploying to a managed cluster.

Because provisioning a cloud load balancer per service gets expensive at scale, most production clusters exposing many services externally use a single LoadBalancer service in front of an Ingress or Gateway API controller, which then does host- and path-based routing to many internal ClusterIP services behind it — one external IP and one load balancer bill, instead of one per service.

ExternalName: a DNS alias, no proxying at all

A fourth, less commonly used type, ExternalName, doesn’t route traffic through the cluster’s networking at all. It maps a Service name to an external DNS name via a CNAME record:

spec:
  type: ExternalName
  externalName: legacy-db.example.com

This is useful for giving an external dependency — a managed database, a third-party API — a name that fits the cluster’s internal DNS conventions, so application code can reference it the same way it references an in-cluster service, without hardcoding an external hostname. No proxying, load balancing, or health checking happens; it’s purely a DNS-level redirect.

Choosing the right type

TypeReachable fromTypical use
ClusterIPInside the cluster onlyInternal service-to-service traffic (the default, and most common)
NodePortAny node’s IP, on a specific portA building block for LoadBalancer; rare as a direct choice
LoadBalancerThe public internet, via a cloud LBA small number of true entry points, often one in front of an Ingress
ExternalNameN/A — DNS alias onlyReferencing an external dependency by an internal-style name

The general pattern in a well-structured cluster: most services stay ClusterIP, a single LoadBalancer service (or a couple, for distinct concerns like public API traffic vs internal admin tooling) sits at the actual edge, and an Ingress or Gateway API resource handles the routing fan-out behind it. This mirrors the layering you’d expect from any reverse proxy sitting in front of internal services — one well-known entry point, internal addresses that don’t need to be publicly routable.

The takeaway

Kubernetes Service types control where a set of pods becomes reachable: ClusterIP for internal-only traffic, NodePort as the building block that exposes a port on every node, LoadBalancer for a cloud-provisioned public entry point, and ExternalName for aliasing an external dependency into the cluster’s DNS. Most services in a real deployment should be ClusterIP, with a small number of LoadBalancer services — often just one, in front of an Ingress — handling the actual traffic from outside the cluster.

Chisato Chisato · · 4 min read

Kubernetes Init Containers Explained

Init containers run to completion before a pod's main containers start, making them the standard way to handle setup steps and startup ordering.

#Kubernetes #DevOps #Cloud
Chisato Chisato · · 4 min read

Helm vs Kustomize for Kubernetes Config

Helm templates and packages Kubernetes manifests with a templating language; Kustomize patches plain YAML declaratively, with no templates at all.

#Kubernetes #DevOps #Cloud