Expose a Deployment with a NodePort Service
In namespace default, Deployment api-app exists with Pods labeled app=api and container port 9090.
Live lab access
This task description is public. Live practice requires full access unless this is the selected free weekly task. AI Lab Coach is available in single-task labs, including the free weekly task. See this week's free task.
Create an account to continue →
On a phone? Read the task and create your account here, then sign in on a desktop or laptop for the terminal lab. Nothing starts until you choose Start.
The task
Task
In namespace default, Deployment api-app exists with Pods labeled app=api and container port 9090.
Requirements
Create a Service named api-nodeport that:
- Type:
NodePort - Selects Pods with label
app=api - Exposes Service port
80mapping to target port9090
Original Prepium guide · Written for Kubernetes v1.35 and checked against the official documentation on 6 October 2026. Use a disposable practice cluster. The tests run from a Pod inside the cluster, so they do not depend on your laptop being able to reach the nodes.
Expose Pods on every node with a NodePort Service, quickly and correctly
A NodePort Service is a normal ClusterIP Service plus one extra port opened on every node. The control plane allocates that node port from a fixed range (30000-32767 by default), and each node forwards traffic on it to the Service's ready endpoints. Getting it right means keeping three numbers straight and making sure the selector really matches your Pods.
outside ──→ any-node-IP:nodePort (e.g. 30042)
inside ──→ quotes-np:port (7000)
└──→ Pod:targetPort (80, where httpd listens)
1. Start from a workload whose labels you know
kubectl create namespace prepium-tutorial
kubectl -n prepium-tutorial create deployment quotes --image=httpd:2.4-alpine --replicas=2
kubectl -n prepium-tutorial get pods --show-labels
create deployment labels the Pods app=quotes. Any Service you build must select exactly that.
2. The fast path: expose
kubectl -n prepium-tutorial expose deployment quotes --name=quotes-np \
--type=NodePort --port=7000 --target-port=80
kubectl -n prepium-tutorial get svc quotes-np
kubectl -n prepium-tutorial get endpointslices -l kubernetes.io/service-name=quotes-np
expose copies the Deployment's selector, so the label match is done for you. The PORT(S) column shows the Service port followed by the node port the control plane picked. The EndpointSlice should list both Pod IPs with port 80. If you left out --target-port, it would default to the Service port, 7000, where nothing is listening.
3. Pinning a specific node port
expose has no dedicated flag for the node port, but kubectl create service nodeport does. It only knows the name and ports you type, so it cannot read your Pods' labels: always inspect the selector it generates before applying.
kubectl -n prepium-tutorial create service nodeport quotes-fixed \
--tcp=7000:80 --node-port=30042 --dry-run=client -o yaml > quotes-fixed.yaml
grep -A2 selector quotes-fixed.yaml # change it to app: quotes, then:
kubectl apply -f quotes-fixed.yaml
A requested nodePort must lie inside the configured range and must be free; otherwise the API server rejects the Service. With the default range, ports 30000-30085 form a "static" band that automatic allocation avoids until the rest is used up, so pinned ports there are less likely to collide.
4. Test the way a client would
kubectl -n prepium-tutorial run client --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl -n prepium-tutorial wait --for=condition=Ready pod/client
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
kubectl -n prepium-tutorial exec client -- wget -T 3 -qO- "http://$NODE_IP:30042"
kubectl -n prepium-tutorial exec client -- wget -T 3 -qO- http://quotes-fixed:7000
Both should print Apache's default page. The first proves the node port path; the second proves the in-cluster Service port, which a NodePort Service always has as well. The same node port answers on every node, not only on nodes running a quotes Pod.
Common mistakes
- Swapping
portandtargetPort. The Service is created, endpoints look fine, and every request fails because the Pods listen on the other number. - A selector that matches nothing, often from
create servicewithout editing the generated selector. The EndpointSlice has no addresses. - Asking for a node port such as 8080 or 32800 that is outside the range: the request is rejected outright.
- Testing
localhost:30042from your workstation. Use a node's address; external firewalls may also block the node port range.
| Example check | Evidence |
|---|---|
| Type and ports | spec.type is NodePort; port, targetPort and nodePort are the intended numbers |
| Selector matches Pods | EndpointSlice lists the Pod IPs with the target port |
| Node path works | Request to a node IP and the node port returns the app |
| Cluster path works | Request to the Service name and port returns the app |
Cleanup: kubectl delete namespace prepium-tutorial and rm quotes-fixed.yaml. References: Service type NodePort, kubectl expose, kubectl create service nodeport. Next, try Service selector troubleshooting or named ports with NodePort.
What this tests
Expose workloads with services and ingress, and restrict traffic with network policies. On the CKAD exam, Services & Networking tasks are graded purely on what you build in the cluster - not multiple choice - so the only way to get faster is to do them on a real cluster against a clock.
Practice it for real
The public description lets you study the task before signing up. Full access includes this live lab; the selected Task of the Week is free with an account. You solve the task in a real terminal, hit validate, and a programmatic checker scores exactly what you got right and wrong (with partial credit). You can open the solution while practising, then retry and use the failed checks to improve your score.