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.
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.
targetPortmust 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/16in 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.The EndpointSlice controller watches the Pods. It writes the IPs of the matching Pods, and whether each one is ready, into an EndpointSlice.
- 2. on every reads those EndpointSlices. It turns them into network rules on the node (iptables rules in this lab).
- 3.A client connects to
ClusterIP:port. The rules on the client's node change the destination to one readyPodIP: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:
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:
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.
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.
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 exposecopies the selector from the , so the Service finds the right Pods.kubectl create servicealways sets the selector toapp=<service-name>. If your Pods have other labels, fix the selector afterwards.
# 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=webEndpoints 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
Endpointsobject still exists, but reading it prints a deprecation warning. Use slices in scripts; either is fine for a quick look.
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
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.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" warningA Service whose selector matches no Pod still looks fine in kubectl get svc. The signs are elsewhere:
kubectl describe svcshows an emptyEndpoints: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.
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
- Servicesearch: service nodeport targetport
- EndpointSlicessearch: endpointslice
- Connecting Applications with Servicessearch: connecting applications with services
- kubectl exposesearch: kubectl expose
Expose a Deployment inside the cluster
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
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-1/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-1/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-1/cleanup | bash
Publish a Deployment on a fixed NodePort
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
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-2/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-2/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-2/cleanup | bash
A Service that sends traffic nowhere
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
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-3/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-svc-3/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 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.