skip to content

A Kubernetes Service manifest can set spec.type to ClusterIP, NodePort, LoadBalancer or ExternalName. What does each type give you, and how do they relate to one another?

level: juniorimportance: must knowfreq 78%

answer

  1. ClusterIP = default, internal VIP only
  2. NodePort = ClusterIP + same port on every node, 30000-32767
  3. LoadBalancer = NodePort + cloud L4 LB, EXTERNAL-IP in status
  4. ExternalName = CNAME only, no VIP, no proxy
  5. no-selector Service = manual endpoints, still proxies

basics

~20 s

ClusterIP gives a stable cluster-internal virtual IP (the default). NodePort adds a fixed port on every node. LoadBalancer adds an external cloud load balancer. ExternalName is only a DNS CNAME to an external host: no virtual IP, no proxying.

solid answer

~50 s

A Service is a stable name and address in front of a changing set of pods. - **ClusterIP** (default): allocates a virtual IP from the service CIDR, reachable only from inside the cluster. Node dataplane rules rewrite traffic for that IP to a ready backing pod. - **NodePort**: everything ClusterIP does, *plus* it reserves a port (default range 30000-32767) on **every** node, so `nodeIP:nodePort` reaches the Service even from a node running none of its pods. - **LoadBalancer**: everything NodePort does, *plus* the cloud controller provisions an external L4 load balancer targeting those node ports and publishes its address in `status.loadBalancer.ingress`. On bare metal with no such controller the external IP stays `<pending>` unless something like MetalLB is installed. - **ExternalName**: no selector, no virtual IP, no proxying at all. Cluster DNS returns a CNAME to `spec.externalName`, aliasing an out-of-cluster host such as a managed database behind an in-cluster name. The first three stack; ExternalName is a DNS-only mechanism.

code

yaml · 43 lines
yaml
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: api-nodeport
spec:
  type: NodePort
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080
---
apiVersion: v1
kind: Service
metadata:
  name: api-lb
spec:
  type: LoadBalancer
  selector:
    app: api
  ports:
    - port: 443
      targetPort: 8443
---
apiVersion: v1
kind: Service
metadata:
  name: legacy-db
spec:
  type: ExternalName
  externalName: db-prod-1.rds.example.com

go deeper

for a junior

Be able to name the four types, state which are reachable from outside, and say that NodePort and LoadBalancer build on ClusterIP.

for a middle

Add the mechanics: the node-port range, that the port is open on every node, that the cloud controller provisions the LB, and that ExternalName is DNS-only.

for a senior

Bring judgment: cost and blast radius of one LB per service, why in-cluster callers should use the ClusterIP name, provider annotations, and the no-selector Service as an alternative to ExternalName.

for a principal

Frame it as an edge strategy: how many external entry points the platform should have, who owns the annotation surface, portability across providers, and how the choice interacts with the L7 layer sitting above.

## Why Services exist Every pod has its own IP, but pods are disposable: a rollout, crash or reschedule replaces them and the IP changes. A **Service** is a stable identity in front of a set of pods chosen by a label selector. Every Service except ExternalName gets a DNS name of the form `<service>.<namespace>.svc.cluster.local`, and unless it is headless it also gets a **virtual IP (VIP)** that no network interface actually owns: the per-node dataplane (iptables, IPVS or an eBPF equivalent, depending on the proxy implementation) rewrites packets destined for that VIP to one of the ready backing pod IPs. ## ClusterIP The default. The API server allocates a VIP out of the cluster's service CIDR and it stays fixed for the Service's whole lifetime, even as pods come and go. It is routable only from inside the cluster (from pods and from nodes), because the VIP exists purely as forwarding rules on cluster nodes, not as a real address on the network. This is the right type for internal service-to-service traffic, which is most Services in a typical cluster. ## NodePort A superset of ClusterIP: the Service still has its ClusterIP and internal DNS name, and *in addition* every node in the cluster listens on the same allocated port, by default from the 30000-32767 range (`--service-node-port-range` on the API server changes it). You can pin the value with `spec.ports[].nodePort`, but pinning risks collisions with other Services. The important property is that the port is open on **every** node, not only nodes hosting a matching pod: a request arriving at a node with no local backend is forwarded to a node that has one. That is what makes it usable as a target for an external load balancer or an on-prem TCP proxy. Exposing node ports straight to users is usually discouraged: the port numbers are unfriendly, nodes come and go, and you get no TLS termination. ## LoadBalancer A superset of NodePort. The Service still gets a ClusterIP and node ports, and additionally the cloud controller manager asks the cloud provider for an external L4 load balancer whose backend pool is the cluster's nodes on that node port. The provisioned address appears in `status.loadBalancer.ingress` and is what `kubectl get svc` shows under EXTERNAL-IP. Provider-specific behaviour (internal vs internet-facing, health-check settings, idle timeout) is configured through annotations, which is why LoadBalancer manifests are rarely portable across clouds. Two details worth knowing. First, on a cluster with no cloud controller (bare metal, kubeadm, plain kind) the EXTERNAL-IP stays `<pending>` forever unless you install an implementation such as MetalLB. Second, since Kubernetes 1.20 you can set `spec.allocateLoadBalancerNodePorts: false` for implementations that program pod IPs directly and do not need the node-port hop. Because each LoadBalancer Service typically means one cloud load balancer with its own bill, teams usually front many HTTP services with a single ingress or gateway implementation rather than giving every service its own LoadBalancer. ## ExternalName The odd one out. It has no selector, no endpoints, no VIP, and no traffic ever passes through the Kubernetes dataplane. Cluster DNS simply answers queries for the Service name with a CNAME to `spec.externalName`. Its use case is aliasing: applications talk to `payments-db.prod.svc.cluster.local`, and you can later repoint that name to an in-cluster Service without touching application config. The gotchas are all DNS-and-TLS gotchas. Because it is a CNAME, the client ultimately connects to the external host, so TLS certificates and SNI/Host headers must match the *external* name. Port remapping is impossible: no proxy exists to remap anything, so the client must use the target's real port. And the target must be resolvable by the cluster's upstream DNS. ## The related fifth case: no selector You can also create a normal ClusterIP Service with **no selector** and hand-write the backing endpoints (an EndpointSlice) yourself. Unlike ExternalName, this one does proxy: you get a real VIP, in-cluster load balancing and port remapping toward arbitrary IPs, which is the usual way to front an external database that you want to reach by IP rather than by name. ## Choosing Internal traffic: ClusterIP. Something outside must reach it and you control an external proxy: NodePort. Cloud, and this service genuinely deserves its own L4 entry point: LoadBalancer. Pure naming indirection to an external DNS host: ExternalName.

  • If a Service is type LoadBalancer, can pods inside the cluster still reach it by its ClusterIP and internal DNS name?
    Yes. LoadBalancer is a superset of NodePort, which is a superset of ClusterIP, so the Service keeps its virtual IP and its `<name>.<namespace>.svc.cluster.local` record. In-cluster clients should use that name, not the external address, so traffic stays inside the cluster and avoids a hairpin through the cloud load balancer (which also costs money and can lose the client identity).
  • You create a Service of type LoadBalancer on a bare-metal kubeadm cluster and EXTERNAL-IP stays <pending>. Why?
    Nothing is implementing the type. The cloud controller manager is what calls a provider API to create the load balancer and write its address into `status.loadBalancer.ingress`; on bare metal there is no such provider. You either install an implementation such as MetalLB, or expose the Service through its node ports behind your own proxy. The node ports are already allocated and working in the meantime.

ClusterIP is an internal extension number; NodePort is a public direct-dial line answered at every office door; LoadBalancer is the switchboard the phone company puts in front of those doors; ExternalName is just an entry in the phone book pointing at someone else's number.

saying these in an interview costs you the question

  • Saying a ClusterIP is reachable from outside the cluster.
  • Thinking a NodePort only opens on the nodes that happen to run a matching pod.
  • Believing ExternalName sets up proxying or lets you remap ports.
  • Thinking LoadBalancer creates one load balancer per pod, or that it replaces the ClusterIP.
  • Assuming EXTERNAL-IP will eventually populate on any cluster, with no cloud controller installed.

context