Services: ClusterIP, NodePort, LoadBalancer and endpoints

How a Service gives changing Pods one stable address, what each Service type adds, and how to read EndpointSlices.

45 min 3 hands-on tasksNetworking · 20% of the exam

Key ideas in this lesson · tap one for a simple explanation

What you will be able to do

  • Expose a Deployment as a ClusterIP or NodePort Service, with the right port, targetPort and nodePort.
  • Explain what LoadBalancer, ExternalName and headless Services do, and how each looks on a cluster without a cloud provider.
  • Fix a Service with no endpoints by comparing its selector with the Pod labels, without touching the workload.

come and go, and each new Pod gets a new IP. A gives a group of Pods one address that does not change. Your frontend calls http://api, and the Service passes each request to one of the api Pods. The exam topic is Use , , service types and endpoints.

Expect one task that says "expose", and one where a Service exists but nothing answers. Both are quick if you know three things:

  • The decides which Pods get the traffic.
  • targetPort must be the port the listens on.
  • The shows which of those two is wrong.

What a Service is

A Service is a stable address in front of a group of Pods. It has three parts:

  • A ClusterIP: a virtual IP address from the Service range (10.96.0.0/16 in this lab).
  • A DNS name, such as api, so clients never need to know the IP.
  • A selector, such as app=api. Every Pod in the same with those gets the traffic.

Three things happen behind the scenes to make that work:

  1. 1.The EndpointSlice controller watches the Pods. It writes the IPs of the matching Pods, and whether each one is ready, into an EndpointSlice.
  2. 2. on every reads those EndpointSlices. It turns them into network rules on the node (iptables rules in this lab).
  3. 3.A client connects to ClusterIP:port. The rules on the client's node change the destination to one ready PodIP:targetPort.

No program listens on the ClusterIP itself, which is why you cannot ping a Service. kube-proxy runs as a in kube-system, one Pod per node. Its mode is in the kube-proxy : iptables here, with nftables as the newer choice.

Here is a Service in , with the fields you set most often:

The fields that matter
apiVersion: v1
kind: Service
metadata:
  name: api
  namespace: nw-svc
spec:
  type: ClusterIP            # default; NodePort and LoadBalancer build on it
  selector:
    app: api                 # must match the Pod template labels, not the Deployment's own labels
  ports:
    - name: http             # required once a Service has more than one port
      protocol: TCP
      port: 80               # what clients connect to on the ClusterIP
      targetPort: 8080       # container port; a number or a named containerPort such as "http"

port, targetPort and nodePort: three ports, three jobs

These names look alike, but each one is a different port:

FieldWhat it isExample
portThe Service's own port. Clients call it.http://api:80
targetPortThe port on the Pod that gets the traffic. It must match what the container listens on.8080
nodePortA port opened on every node, for traffic from outside. Only NodePort and LoadBalancer Services have one.30080
containerPortSet in the Pod spec, not in the Service. It documents the container's port, and can give it a name.8080, named http
One request, three ports

Client outside

curl <node-ip>:30080

Any node

nodePort

30080

Service

port

80

A Pod

targetPort

8080

A Pod inside the cluster skips the first step: it calls the Service on port 80 directly.

targetPort can also be a name, such as http. It then means the container port with that name. That lets you change the container's port number without editing the Service. If you leave targetPort out, it is the same as port. That is a common cause of "connection refused".

The Service types

Each type adds one more way in, on top of the type before it.

Each type builds on the one before

ClusterIP

a virtual IP, reachable only inside the cluster

NodePort

adds the same port on every node, from 30000 to 32767

LoadBalancer

adds an external load balancer from the cloud

A NodePort Service still has a ClusterIP, and a LoadBalancer Service still has node ports.

TypeWhat you getOn this kind lab
ClusterIPA virtual IP that works only inside the cluster. This is the default type.Works
NodePortA ClusterIP, plus the same port opened on every node (spec.ports[].nodePort, from 30000–32767). Kubernetes picks one if you do not.Works. kind maps node port 30080 to localhost:30080 on your laptop.
LoadBalancerA NodePort, plus an external load balancer. A cloud controller creates it and writes its address into status.loadBalancer.ingress.EXTERNAL-IP stays <pending>, because nothing creates the load balancer
ExternalNameNo ClusterIP and no proxying. DNS answers with a CNAME (an alias) to spec.externalName.Works, because it is only DNS
Headless (clusterIP: None)No virtual IP. DNS answers with the ready Pods' own IPs.Works. use it.

A LoadBalancer Service needs a helper outside Kubernetes, because Kubernetes only records the request. A cloud provider's controller (AWS, GCP, Azure) or an add-on such as MetalLB creates the load balancer and fills in its address. Without one, the Service still works as a NodePort. You can see this on ingress-nginx-controller in this lab.

Quick commands: expose and create service

Two commands create a Service in one line. The difference is the selector:

  • kubectl expose copies the selector from the , so the Service finds the right Pods.
  • kubectl create service always sets the selector to app=<service-name>. If your Pods have other labels, fix the selector afterwards.
expose copies the selector; create service invents one
# expose reads the Deployment's selector, so the endpoints are right by construction
kubectl -n nw-svc expose deploy api --name=api-svc --port=80 --target-port=8080
kubectl -n nw-svc expose deploy api --name=api-np --type=NodePort --port=80 --target-port=8080
#   there is no --node-port flag on expose; set it afterwards:
kubectl -n nw-svc patch svc api-np --type=json -p='[{"op":"replace","path":"/spec/ports/0/nodePort","value":30080}]'

# create service sets the selector to app=<service-name>; correct it if your Pods differ
kubectl -n nw-svc create service clusterip api --tcp=80:8080
kubectl -n nw-svc create service nodeport api --tcp=80:8080 --node-port=30080
kubectl -n nw-svc create service externalname docs --external-name=kubernetes.io
kubectl -n nw-svc create service clusterip db --clusterip=None --tcp=5432:5432   # headless

# A Pod can be exposed too (it uses the Pod's labels as the selector)
kubectl -n nw-svc expose pod web --port=80 --target-port=8080 --name=web

Endpoints and EndpointSlices

An EndpointSlice is the list of Pod IPs behind a Service. When a Service does not answer, look here first.

  • Each slice has the label kubernetes.io/service-name=<service>, so you find it by label, not by name.
  • Each address has conditions, such as ready. Only ready Pods get traffic.
  • The older Endpoints object still exists, but reading it prints a deprecation warning. Use slices in scripts; either is fine for a quick look.
How a Service picks its PodsTap a Pod to fail its readiness probe.

Client Pod

curl web:80

Service web

10.96.0.20:80 → 8080

selector: app=web

EndpointSlice

  • 10.244.1.11:8080 ready
  • 10.244.2.12:8080 ready
  • 10.244.1.13:8080 ready
The Service selects Pods labeled app=web. The api Pod does not match, so it never gets traffic. Tap a web Pod to fail its readiness probe, or break the selector.
Terminal
kubectl -n nw-svc get endpointslices -l kubernetes.io/service-name=api-svc
# NAME            ADDRESSTYPE   PORTS   ENDPOINTS                       AGE
# api-svc-x7k2p   IPv4          8080    10.244.157.130,10.244.89.67   10s

kubectl -n nw-svc describe svc api-svc | grep -E 'Selector|TargetPort|Endpoints'
kubectl -n nw-svc get endpoints api-svc       # works, with "v1 Endpoints is deprecated" warning

A Service whose selector matches no Pod still looks fine in kubectl get svc. The signs are elsewhere:

  • kubectl describe svc shows an empty Endpoints: line.
  • The EndpointSlice shows <unset> for its ports and endpoints.
  • Clients get connection refused at once, because kube-proxy rejects traffic to a Service with no backends.
  • Pods that match but are not Ready appear with ready=false, and get no traffic.
The no-endpoints checklist
kubectl -n nw-svc get svc orders -o jsonpath='{.spec.selector}{"\n"}'
kubectl -n nw-svc get pods --show-labels
kubectl -n nw-svc get pods -l app=orders,tier=backend      # paste the selector here; empty means mismatch
kubectl -n nw-svc get endpointslices -l kubernetes.io/service-name=orders

# Fix the Service, not the Pods, unless the question says otherwise
kubectl -n nw-svc set selector svc orders app=orders,tier=backend
# or: kubectl -n nw-svc patch svc orders -p '{"spec":{"selector":{"app":"orders"}}}'

In the official docs· open on exam day too

Hands-on task 1 of 3nw-svc-1
~4 min4% weightNetworking

Expose a Deployment inside the cluster

In namespace nw-svc, the Deployment api serves HTTP on container port 8080. Create a Service named api-svc of type ClusterIP that exposes it on port 80 and forwards to port 8080 of the Pods. A request from Pod client to http://api-svc/hostname must return the name of an api Pod.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-1/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-1/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-1/cleanup | bash
Hands-on task 2 of 3nw-svc-2
~5 min4% weightNetworking

Publish a Deployment on a fixed NodePort

Make the Deployment greeter in namespace nw-nodeport reachable from outside the cluster. Create a Service greeter-np of type NodePort with Service port 80, target port 8080 and node port 30080. From your laptop, curl -s localhost:30080/hostname must then return the name of a greeter Pod.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-2/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-2/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-2/cleanup | bash
Hands-on task 3 of 3nw-svc-3
~5 min5% weightNetworking

A Service that sends traffic nowhere

In namespace nw-svcfix, requests from Pod client to http://orders/hostname fail with connection refused, even though both orders Pods are Running and Ready. Find the cause and fix it so the Service orders delivers requests to the orders Pods. Do not modify the Deployment.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-3/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-3/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-3/cleanup | bash

Finished this lesson?

0 of 3 tasks passed. Your readiness counts tasks, so run them before you move on.