Services: ClusterIP, NodePort & LoadBalancer
Pods come and go. A rolling update replaces all of them, a failed node takes some away, and every new Pod gets a new IP address. So how does the shop's frontend find the API, or a customer's browser find the frontend? Through a Service: one stable name and virtual IP address in front of a changing group of Pods.
In this lesson you will create each type of Service, reach Pods by DNS name, see how a Service tracks its Pods, and learn which type to use when.
How a Service works
A Service has a label selector. Kubernetes keeps a list of the IP addresses of all ready Pods that match it, stored in objects called EndpointSlices. On every node, kube-proxy (or the network plug-in) programs rules so that traffic sent to the Service's address goes to one of those Pods. When Pods are added, removed or become unready, the list updates within seconds. Your clients never need to know.
Cluster DNS (CoreDNS) gives every Service a name. From any Pod you can reach a Service called menu in the namespace default as:
menufrom Pods in the same namespace,menu.defaultfrom any namespace,menu.default.svc.cluster.local, the fully qualified name.
ClusterIP: inside the cluster
ClusterIP is the default type. It gives a virtual IP that is reachable only from inside the cluster, which is exactly what internal parts of an app, such as APIs and databases, should have. This Service fronts the menu Deployment from the earlier lessons:
apiVersion: v1
kind: Service
metadata:
name: menu
spec:
selector:
app: menu
ports:
- name: http
port: 80
targetPort: 80selectormust match the Pods' labels.portis the port the Service listens on.targetPortis the port on the Pods. It can also be the name of a container port, so the Pods can change their port number without breaking the Service.
kubectl apply -f menu-service.yaml
kubectl get service menu
kubectl get endpointslices -l kubernetes.io/service-name=menuservice/menu created NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE menu ClusterIP 10.96.48.211 <none> 80/TCP 12s NAME ADDRESSTYPE PORTS ENDPOINTS AGE menu-5rkxz IPv4 80 10.244.1.5,10.244.2.4,10.244.2.5 12s
The EndpointSlice lists the three Pod IPs. Now call the Service by name from a throwaway Pod. --rm deletes the Pod when the command finishes, and -i shows its output:
kubectl run tmp --rm -i --restart=Never --image=busybox:1.37 -- sh -c 'wget -qO- http://menu | grep "<title>"'<title>Welcome to nginx!</title> pod "tmp" deleted from default namespace
Each new connection may land on a different Pod. This simple temporary Pod is the standard way to test Services from inside the cluster.
NodePort: open a port on every node
A NodePort Service is a ClusterIP Service that also opens the same port on every node. Traffic to any node's IP on that port reaches the Pods. Node ports come from the range 30000 to 32767; Kubernetes picks a free one unless you choose it:
apiVersion: v1
kind: Service
metadata:
name: menu-public
spec:
type: NodePort
selector:
app: menu
ports:
- port: 80
targetPort: 80
nodePort: 30080Because your kind cluster maps port 30080 to your laptop (from the extraPortMappings in Install kubectl & a Local Cluster), you can now open http://localhost:30080 in your browser and see the Nginx page. NodePort is handy for local testing and for putting your own load balancer in front of the nodes, but exposing high port numbers on every node is rarely what you want in production.
LoadBalancer: a public address from the cloud
A LoadBalancer Service is a NodePort Service that also asks the cloud provider for an external load balancer with a public IP address or host name. On EKS, AKS or GKE the address appears in EXTERNAL-IP after a minute or two. On a local cluster there is no cloud, so it waits forever:
kubectl expose deployment menu --name=menu-lb --type=LoadBalancer --port=80
kubectl get servicesservice/menu-lb exposed NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 52m menu ClusterIP 10.96.48.211 <none> 80/TCP 6m40s menu-lb LoadBalancer 10.96.133.92 <pending> 80:31547/TCP 15s menu-public NodePort 10.96.201.17 <none> 80:30080/TCP 3m2s
kubectl expose creates a Service from a Deployment's selector, a quick alternative to writing YAML. 80:31547/TCP shows the Service port and its automatically chosen node port. The kubernetes Service is created by the cluster itself and points at the API server.
To get real external addresses locally: kind offers a separate tool, cloud-provider-kind, that you run on your computer; minikube has minikube tunnel; and k3s (used by k3d) ships its own simple load balancer.
| Type | Reachable from | Typical use |
|---|---|---|
ClusterIP | Inside the cluster only | APIs, databases, anything internal |
NodePort | Any node's IP and the node port | Local testing, your own external load balancer |
LoadBalancer | A cloud load balancer's public address | Exposing one service directly on a cloud |
ExternalName | Inside the cluster, as a DNS alias | Giving an outside host (such as a managed database) a cluster name |
One LoadBalancer per app gets expensive and gives you no HTTP routing by host name or path. For websites and APIs, you normally keep Services as ClusterIP and put one shared entry point in front of them: an Ingress or, the modern choice, a Gateway.
Headless Services
Setting clusterIP: None makes a headless Service. It gets no virtual IP and no load balancing; instead, its DNS name returns the IP addresses of the individual ready Pods. That is useful when clients must talk to specific Pods, such as the members of a database cluster, and it is a key part of StatefulSets:
apiVersion: v1
kind: Service
metadata:
name: results-db
spec:
clusterIP: None
selector:
app: results-db
ports:
- port: 3306Common mistakes
- Selector does not match Pod labels. The Service exists but has no endpoints. Compare
kubectl get endpointsliceswithkubectl get pods --show-labels. - Confusing
portandtargetPort. Clients useport; the app inside the Pod listens ontargetPort. - Using Pod IPs in configuration. They change on every restart. Use the Service's DNS name.
- A
LoadBalancerfor every microservice. Use internal ClusterIP Services and one Gateway or Ingress.
Key points
- A Service gives a changing set of ready Pods one stable virtual IP and DNS name, using a label selector.
- EndpointSlices hold the current Pod addresses; an empty list means no ready Pod matches.
- ClusterIP is internal; NodePort adds a port (30000-32767) on every node; LoadBalancer adds a cloud load balancer.
- DNS names follow
<service>.<namespace>.svc.cluster.local, and the short name works within a namespace. - Headless Services (
clusterIP: None) return individual Pod IPs and are used with StatefulSets.
Exercise
Meera's bus-booking API runs as a Deployment with label app: booking-api, listening on container port 8080 named http. Write a ClusterIP Service called booking-api that clients reach on port 80, using the named target port. Then write the one-line command that tests it from a temporary busybox Pod by fetching http://booking-api/health.
Show solution
apiVersion: v1
kind: Service
metadata:
name: booking-api
spec:
selector:
app: booking-api
ports:
- name: http
port: 80
targetPort: httpkubectl run tmp --rm -i --restart=Never --image=busybox:1.37 -- wget -qO- http://booking-api/healthUsing the port name http as targetPort means the Service keeps working even if the container later listens on a different number, as long as the port keeps its name. No type is given, so the Service is ClusterIP.
Quick Quiz
1. Which Service type is reachable only from inside the cluster?
ClusterIP is the default and suits internal components.
2. A Service's ENDPOINTS list is empty. What is the most likely cause?
Check the labels, the namespace and the Pods' readiness.
3. What is the full DNS name of the Service menu in the namespace shop?
The pattern is service, namespace, svc, then the cluster domain.
4. Why does a LoadBalancer Service show <pending> on a kind cluster?
Tools like cloud-provider-kind or minikube tunnel fill this role locally.
Learn Kubernetes with a mentor
Free lessons get you started. Our Cloud & DevOps training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
