Cloud AI Campus
  • Career paths
  • Learning paths
  • Hands-on Labs
Log in Sign up

๐Ÿงช Hands-on lab · 45 min

Services โ€” ClusterIP, NodePort, LoadBalancer

  1. 1. ClusterIP โ€” the default
  2. 2. Reach pods via the Service DNS name
  3. 3. NodePort โ€” exposing to the node
  4. 4. LoadBalancer โ€” and why it stays Pending here
  5. 5. port-forward for local debugging

ClusterIP โ€” the default

A pod's IP is ephemeral. The moment Kubernetes recreates a pod, the address changes. A Service gives a logical group of pods a stable virtual IP and DNS name. Inside the cluster, callers reach the Service; kube-proxy load-balances onto the live pods behind it.

The starter Deployment from lab 102 has been preloaded:

kubectl get deployment nginx-deployment

Expose it as a ClusterIP Service (the default type):

kubectl expose deployment nginx-deployment --port 80

Look at what landed:

kubectl get svc nginx-deployment

TYPE is ClusterIP, CLUSTER-IP is a virtual address only reachable from inside the cluster, EXTERNAL-IP is <none>.

See which pods the Service routes to via its Endpoints object:

kubectl get endpoints nginx-deployment

You should see three pod IPs โ€” one per nginx replica.

Click Verify step.

Hint

`kubectl expose deployment nginx-deployment --port 80` creates a ClusterIP Service.

Reach pods via the Service DNS name

The cluster runs its own DNS. Every Service gets an A record at <service-name>.<namespace>.svc.cluster.local (and the short form <service-name> works inside the same namespace).

Run a one-shot client pod that curls the Service and exits:

kubectl run client --rm -it --restart=Never --image=busybox:1.36 -- \
    wget -qO- http://nginx-deployment

You should see the nginx welcome HTML.

Watch the Service round-robin across pods. Run the same command three times, asking nginx for the hostname:

for i in 1 2 3; do
    kubectl run client --rm --restart=Never --image=busybox:1.36 -q -- \
        wget -qO- http://nginx-deployment | grep -i nginx
done

The default nginx page doesn't surface the pod name, so let's stamp one in. Write a unique index.html into each pod:

for pod in $(kubectl get pods -l app=nginx -o name); do
    name=$(basename "$pod")
    kubectl exec "$pod" -- sh -c "echo $name > /usr/share/nginx/html/whoami.txt"
done

Now ask the Service who answered:

for i in 1 2 3 4 5 6; do
    kubectl run probe --rm --restart=Never --image=busybox:1.36 -q -- \
        wget -qO- http://nginx-deployment/whoami.txt
done

Different pod names appear โ€” the load-balancing is real.

Click Verify step.

Hint

Run a one-shot pod and `wget -qO- http://nginx-deployment` โ€” service DNS resolves cluster-wide.

NodePort โ€” exposing to the node

ClusterIP is reachable only from inside the cluster. NodePort opens a high-numbered port (default range 30000-32767) on every node, forwarding traffic to the Service.

Patch the Service in place:

kubectl patch svc nginx-deployment -p '{"spec":{"type":"NodePort"}}'

Check the assigned port:

kubectl get svc nginx-deployment

The PORT(S) column now shows something like 80:31234/TCP. The 31234 is the NodePort.

In this sandbox there's only one node (cac-lab). Find its IP:

kubectl get node cac-lab -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}{"\n"}'

Hit the NodePort from inside the cluster:

node_ip=$(kubectl get node cac-lab -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}')
node_port=$(kubectl get svc nginx-deployment -o jsonpath='{.spec.ports[0].nodePort}')
kubectl run probe --rm --restart=Never --image=busybox:1.36 -q -- \
    wget -qO- "http://${node_ip}:${node_port}/whoami.txt"

Same Service, different door. NodePort is what you'd use when you don't have a cloud load balancer but you do have a known node IP (typical for on-prem / bare-metal).

Click Verify step.

Hint

Patch the service to `type: NodePort`, then `kubectl get svc nginx-deployment` to find the assigned port.

LoadBalancer โ€” and why it stays Pending here

type: LoadBalancer asks the cloud provider for a real external load balancer (GCP TCP/HTTP LB, AWS NLB/ALB, etc.). On a managed cluster the provisioner sees the request, creates the LB, and writes its IP back onto the Service.

This sandbox has no cloud provider โ€” and that's a teachable moment. Try it:

kubectl expose deployment nginx-deployment --name nginx-lb \
    --port 80 --target-port 80 --type LoadBalancer
kubectl get svc nginx-lb

EXTERNAL-IP will show <pending> and stay there forever. Look at why:

kubectl describe svc nginx-lb | tail -10

In a real cluster you'd see events like EnsuringLoadBalancer and EnsuredLoadBalancer. Here, nobody answered the call.

Rule of thumb: use ClusterIP for in-cluster traffic, NodePort for on-prem with known node addresses, LoadBalancer for cloud external traffic, and Ingress when you need HTTP routing (host

  • path-based) on top of a single LB.

Clean up the unused service:

kubectl delete svc nginx-lb

Click Verify step.

Hint

Create a second service of `type: LoadBalancer` โ€” without a cloud LB it stays `<pending>`, which is the teachable moment.

port-forward for local debugging

kubectl port-forward opens a TCP tunnel from your local machine straight to a pod or Service, without touching any of the service types. It's the fastest way to reach an in-cluster Service from your workstation during development.

Start a background port-forward from the sandbox's localhost:8080 to the Service's port 80:

kubectl port-forward svc/nginx-deployment 8080:80 >/tmp/pf.log 2>&1 &

Give it a moment to come up:

sleep 2

Hit it from the sandbox's own loopback:

curl -s http://127.0.0.1:8080/whoami.txt

You should see one of the nginx pod names. Try several times โ€” every forward goes through the Service, so kube-proxy still load-balances:

for i in 1 2 3 4 5 6; do
    curl -s http://127.0.0.1:8080/whoami.txt
done

Real-world tip: kubectl port-forward is great for one-off debugging but it's a single TCP connection โ€” kill the session and you lose the tunnel. For shared dev access, set up a proper Ingress or a long-lived kubectl proxy.

When you've hit the endpoint at least once, click Verify step.

Hint

`kubectl port-forward svc/nginx-deployment 8080:80 &` then `curl http://127.0.0.1:8080`.

© 2026 Cloud AI Campus