Services: ClusterIP, NodePort, LoadBalancer and endpoints

How a Service turns a moving set of Pod IPs into one stable address, what each Service type adds, and how to read EndpointSlices when traffic goes nowhere.

Networking · 20%45 min3 tasks

After this lesson

  • 01You can expose a Deployment as a ClusterIP or NodePort Service with the right port, targetPort and nodePort in one command.
  • 02You can explain what LoadBalancer, ExternalName and headless Services do, and what each looks like on a cluster without a cloud provider.
  • 03You can diagnose a Service with no endpoints from its selector, the Pod labels and its EndpointSlices, and fix it without touching the workload.

This lesson covers Use ClusterIP, NodePort, LoadBalancer service types and endpoints. Expect at least one task that says "expose" and at least one troubleshooting task where a Service exists but nothing answers. Both are fast if you know three things: the selector decides which Pods are endpoints, targetPort must match what the container listens on, and EndpointSlices tell you which of those two is wrong.

What a Service is

A Service is a stable virtual IP (the ClusterIP, from the Service CIDR) plus a DNS name, in front of the set of Pods its selector matches. The EndpointSlice controller watches Pods and writes the IPs of the ready matching ones into EndpointSlices. kube-proxy on every node watches those slices and programs rules so that a connection to ClusterIP:port is rewritten to one PodIP:targetPort. No process listens on the ClusterIP, which is why you cannot ping it.

The fields that matteryaml
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"
  sessionAffinity: None      # ClientIP pins each client IP to one Pod (default timeout 10800s)

A named targetPort refers to ports[].name in the container spec. That lets different Pods behind one Service listen on different numbers, and lets you change the container port without editing the Service. If targetPort is omitted it defaults to port, which is a classic source of "connection refused".

The Service types

TypeWhat you getOn this kind lab
ClusterIPA virtual IP reachable only inside the clusterWorks
NodePortClusterIP plus the same port opened on every node, from 30000–32767 (spec.ports[].nodePort; random if you do not set it)Works; 30080 on the control plane is mapped to localhost:30080
LoadBalancerNodePort plus an external load balancer provisioned by the cloud controller manager, which writes its address to status.loadBalancer.ingressEXTERNAL-IP stays <pending>: nothing fulfills it
ExternalNameNo proxying and no ClusterIP; DNS returns a CNAME to spec.externalNameWorks (it is only DNS)
Headless (clusterIP: None)No virtual IP; DNS returns the ready Pod IPs directlyWorks; used by StatefulSets

LoadBalancer deserves a sentence of its own. Kubernetes only records the request. A cloud provider's controller (AWS, GCP, Azure, …) or an add-on such as MetalLB or cloud-provider-kind sees the Service, creates the load balancer, points it at the node ports, and fills in the status. Without one, the Service still works as a NodePort, which is exactly what you see for ingress-nginx-controller in this lab.

Imperative shortcuts

expose copies the selector; create service invents onebash
# 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

EndpointSlices (discovery.k8s.io/v1) are the source of truth. Each slice is labeled kubernetes.io/service-name=<service> and lists addresses with ready, serving and terminating conditions, the node and the Pod they belong to. The older v1 Endpoints object is still mirrored, but the API server now prints a deprecation warning when you read it. Use slices in scripts; either works for a quick look.

bash
kubectl -n nw-svc get endpointslices -l kubernetes.io/service-name=api-svc
# NAME            ADDRESSTYPE   PORTS   ENDPOINTS                       AGE
# api-svc-x7k2p   IPv4          8080    192.168.245.41,192.168.220.43   10s

kubectl -n nw-svc get endpointslices -l kubernetes.io/service-name=api-svc \
  -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{"  ready="}{.conditions.ready}{"  "}{.targetRef.name}{"\n"}{end}'

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 nothing looks perfectly healthy in kubectl get svc. The tells are elsewhere: describe shows an empty Endpoints: line, the EndpointSlice shows <unset> for ports and endpoints, and clients get connection refused immediately because kube-proxy rejects traffic to a Service with no backends. Pods that match but are not Ready show up in the slice with ready=false and receive no traffic.

The no-endpoints checklistbash
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 docs · the tab you get on exam day

Tasknw-svc-1
Networking4%~4 min

context · kubectl config use-context kind-cka

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.
  1. 01

    Set up the scenario

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

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-1/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-1/cleanup | bash
Tasknw-svc-2
Networking4%~5 min

context · kubectl config use-context kind-cka

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. A request to port 30080 of a node, for example curl -s localhost:30080/hostname from your machine on this lab, must return the name of a greeter Pod.
  1. 01

    Set up the scenario

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

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-2/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-2/cleanup | bash
Tasknw-svc-3
Networking5%~5 min

context · kubectl config use-context kind-cka

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.
  1. 01

    Set up the scenario

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

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-3/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

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

Finished reading?

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