EthicsComputerCourses
Join training +91 90532 10052

Services: ClusterIP, NodePort & LoadBalancer

Kubernetes · Lesson 12 of 30 Intermediate 8 min read Updated

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:

  • menu from Pods in the same namespace,
  • menu.default from 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:

YAML
apiVersion: v1
kind: Service
metadata:
  name: menu
spec:
  selector:
    app: menu
  ports:
    - name: http
      port: 80
      targetPort: 80
  • selector must match the Pods' labels.
  • port is the port the Service listens on.
  • targetPort is 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.
Terminal
kubectl apply -f menu-service.yaml
kubectl get service menu
kubectl get endpointslices -l kubernetes.io/service-name=menu
Output
service/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:

Terminal
kubectl run tmp --rm -i --restart=Never --image=busybox:1.37 -- sh -c 'wget -qO- http://menu | grep "<title>"'
Output
<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:

YAML
apiVersion: v1
kind: Service
metadata:
  name: menu-public
spec:
  type: NodePort
  selector:
    app: menu
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

Because 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:

Terminal
kubectl expose deployment menu --name=menu-lb --type=LoadBalancer --port=80
kubectl get services
Output
service/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.

TypeReachable fromTypical use
ClusterIPInside the cluster onlyAPIs, databases, anything internal
NodePortAny node's IP and the node portLocal testing, your own external load balancer
LoadBalancerA cloud load balancer's public addressExposing one service directly on a cloud
ExternalNameInside the cluster, as a DNS aliasGiving 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:

YAML
apiVersion: v1
kind: Service
metadata:
  name: results-db
spec:
  clusterIP: None
  selector:
    app: results-db
  ports:
    - port: 3306

Common mistakes

  • Selector does not match Pod labels. The Service exists but has no endpoints. Compare kubectl get endpointslices with kubectl get pods --show-labels.
  • Confusing port and targetPort. Clients use port; the app inside the Pod listens on targetPort.
  • Using Pod IPs in configuration. They change on every restart. Use the Service's DNS name.
  • A LoadBalancer for 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
YAML
apiVersion: v1
kind: Service
metadata:
  name: booking-api
spec:
  selector:
    app: booking-api
  ports:
    - name: http
      port: 80
      targetPort: http
Terminal
kubectl run tmp --rm -i --restart=Never --image=busybox:1.37 -- wget -qO- http://booking-api/health

Using 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?

2. A Service's ENDPOINTS list is empty. What is the most likely cause?

3. What is the full DNS name of the Service menu in the namespace shop?

4. Why does a LoadBalancer Service show <pending> on a kind cluster?

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