CKA — Services & Networking Lab
Lab Overview
This hands-on lab covers the core Services & Networking concepts you need for the CKA exam. You’ll create different Service types, test DNS resolution, inspect Endpoints, and debug Pod-to-Pod connectivity.
What you’ll practice:
- Creating ClusterIP, NodePort, and Headless Services
- Testing DNS resolution from inside Pods
- Inspecting Endpoints and watching them change
- Verifying Pod-to-Pod communication
Prerequisites:
- A running Kubernetes cluster (minikube, kind, or kubeadm)
kubectlconfigured and connected to the cluster
Task 1: Create a ClusterIP Service
Goal: Deploy an nginx application and expose it as a ClusterIP Service.
Step 1: Create the Deployment:
kubectl create deployment web --image=nginx --replicas=3
Step 2: Verify the Pods are running:
kubectl get pods -l app=web -o wide
Expected Output
NAME READY STATUS RESTARTS AGE IP NODE
web-96d5df5c8-abc12 1/1 Running 0 30s 10.244.1.5 node1
web-96d5df5c8-def34 1/1 Running 0 30s 10.244.2.8 node2
web-96d5df5c8-ghi56 1/1 Running 0 30s 10.244.1.9 node1
Step 3: Expose the Deployment as a ClusterIP Service:
kubectl expose deployment web --port=80 --target-port=80 --name=web-clusterip
Step 4: Verify the Service:
kubectl get svc web-clusterip
Expected Output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web-clusterip ClusterIP 10.96.45.123 <none> 80/TCP 10s
Step 5: Test the Service from a temporary Pod:
kubectl run test-pod --image=busybox:1.28 --rm -it --restart=Never -- wget -qO- http://web-clusterip
Expected Output
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
You should see the default nginx welcome page HTML.
Task 2: Create a NodePort Service
Goal: Expose the same Deployment as a NodePort Service and access it from outside the cluster.
Step 1: Create a NodePort Service:
kubectl expose deployment web --port=80 --target-port=80 --type=NodePort --name=web-nodeport
Step 2: Check the assigned NodePort:
kubectl get svc web-nodeport
Expected Output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web-nodeport NodePort 10.96.78.210 <none> 80:31245/TCP 5s
Note the NodePort (31245 in this example — yours will differ).
Step 3: Get a node IP:
kubectl get nodes -o wide
Step 4: Access the Service via NodePort:
# Replace <node-ip> and <node-port> with actual values
curl http://<node-ip>:<node-port>
Expected Output
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
You should see the nginx welcome page, confirming external access works.
Task 3: Test DNS Resolution
Goal: Deploy a DNS utility Pod and test Service DNS resolution within and across namespaces.
Step 1: Create a namespace for cross-namespace testing:
kubectl create namespace other-ns
Step 2: Create a Deployment and Service in the other-ns namespace:
kubectl create deployment web-other --image=nginx --replicas=2 -n other-ns
kubectl expose deployment web-other --port=80 --target-port=80 --name=web-other-svc -n other-ns
Step 3: Deploy a DNS utility Pod in the default namespace:
kubectl run dnsutils --image=gcr.io/kubernetes-e2e-test-images/dnsutils:1.3 --command -- sleep 3600
Wait for it to be ready:
kubectl wait --for=condition=Ready pod/dnsutils --timeout=60s
Step 4: Test same-namespace DNS resolution:
kubectl exec -it dnsutils -- nslookup web-clusterip
Expected Output
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-clusterip.default.svc.cluster.local
Address: 10.96.45.123
The short name resolves because both the Pod and Service are in the default namespace.
Step 5: Test cross-namespace DNS resolution:
kubectl exec -it dnsutils -- nslookup web-other-svc.other-ns
Expected Output
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-other-svc.other-ns.svc.cluster.local
Address: 10.96.88.45
Step 6: Test full FQDN resolution:
kubectl exec -it dnsutils -- nslookup web-other-svc.other-ns.svc.cluster.local
Expected Output
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-other-svc.other-ns.svc.cluster.local
Address: 10.96.88.45
Task 4: Headless Service
Goal: Create a Headless Service and verify that DNS returns individual Pod IPs.
Step 1: Create a Headless Service for the web Deployment:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: web-headless
spec:
clusterIP: None
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 80
EOF
Step 2: Verify the Service has no ClusterIP:
kubectl get svc web-headless
Expected Output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web-headless ClusterIP None <none> 80/TCP 5s
Note that CLUSTER-IP is None.
Step 3: Test DNS resolution — it should return Pod IPs:
kubectl exec -it dnsutils -- nslookup web-headless
Expected Output
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-headless.default.svc.cluster.local
Address: 10.244.1.5
Name: web-headless.default.svc.cluster.local
Address: 10.244.2.8
Name: web-headless.default.svc.cluster.local
Address: 10.244.1.9
Notice that DNS returns multiple A records — one for each Pod’s IP. Compare this to the ClusterIP Service which returns a single virtual IP.
Task 5: Endpoints Investigation
Goal: View Endpoints for a Service, scale the Deployment, and watch Endpoints change.
Step 1: View current Endpoints for the web-clusterip Service:
kubectl get endpoints web-clusterip
Expected Output
NAME ENDPOINTS AGE
web-clusterip 10.244.1.5:80,10.244.1.9:80,10.244.2.8:80 5m
Three endpoints — one for each replica.
Step 2: Scale the Deployment to 5 replicas:
kubectl scale deployment web --replicas=5
Step 3: Watch Endpoints update:
kubectl get endpoints web-clusterip
Expected Output
NAME ENDPOINTS AGE
web-clusterip 10.244.1.5:80,10.244.1.9:80,10.244.1.12:80,10.244.2.8:80,... 6m
You should now see 5 endpoints — Kubernetes automatically updated the Endpoints object.
Step 4: Scale down to 1 replica and check again:
kubectl scale deployment web --replicas=1
kubectl get endpoints web-clusterip
Expected Output
NAME ENDPOINTS AGE
web-clusterip 10.244.1.5:80 7m
Only one endpoint remains.
Step 5: View EndpointSlices:
kubectl get endpointslices -l kubernetes.io/service-name=web-clusterip
Expected Output
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
web-clusterip-abc12 IPv4 80 10.244.1.5 7m
Step 6: Scale back up for the next task:
kubectl scale deployment web --replicas=3
Task 6: Pod-to-Pod Communication
Goal: Create two Deployments and test Pod-to-Pod connectivity by IP and Service name.
Step 1: Create a second Deployment:
kubectl create deployment backend --image=nginx --replicas=2
kubectl expose deployment backend --port=80 --target-port=80 --name=backend-svc
Step 2: Get Pod IPs for both Deployments:
kubectl get pods -o wide -l app=web
kubectl get pods -o wide -l app=backend
Expected Output
# web Pods
NAME READY STATUS IP NODE
web-96d5df5c8-abc12 1/1 Running 10.244.1.5 node1
web-96d5df5c8-def34 1/1 Running 10.244.2.8 node2
web-96d5df5c8-ghi56 1/1 Running 10.244.1.9 node1
# backend Pods
NAME READY STATUS IP NODE
backend-7f8b9c6d4-jkl78 1/1 Running 10.244.2.11 node2
backend-7f8b9c6d4-mno90 1/1 Running 10.244.1.14 node1
Step 3: Test Pod-to-Pod communication by IP (from a web Pod to a backend Pod):
# Get a web Pod name
WEB_POD=$(kubectl get pods -l app=web -o jsonpath='{.items[0].metadata.name}')
# Get a backend Pod IP
BACKEND_IP=$(kubectl get pods -l app=backend -o jsonpath='{.items[0].status.podIP}')
# Test connectivity by IP
kubectl exec -it $WEB_POD -- wget -qO- http://$BACKEND_IP
Expected Output
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
Direct Pod-to-Pod communication by IP works.
Step 4: Test Pod-to-Pod communication via Service name:
kubectl exec -it $WEB_POD -- wget -qO- http://backend-svc
Expected Output
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
Communication via Service DNS name works — this is the recommended approach.
Step 5: Test cross-namespace connectivity:
kubectl exec -it $WEB_POD -- wget -qO- http://web-other-svc.other-ns
Expected Output
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
Cross-namespace communication works using <service>.<namespace> format.
Cleanup
Remove all resources created during this lab:
# Delete Services
kubectl delete svc web-clusterip web-nodeport web-headless backend-svc
# Delete Deployments
kubectl delete deployment web backend
# Delete the dnsutils Pod
kubectl delete pod dnsutils
# Delete resources in other-ns
kubectl delete deployment web-other -n other-ns
kubectl delete svc web-other-svc -n other-ns
kubectl delete namespace other-ns
Verify everything is cleaned up:
kubectl get all
Expected Output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 1d
Only the default kubernetes Service should remain.
Congratulations! You’ve completed the Services & Networking hands-on lab. You’ve practiced creating different Service types, testing DNS resolution, inspecting Endpoints, and verifying Pod-to-Pod communication — all essential skills for the CKA exam.