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`.