CKA — Ingress & DNS Lab
Lab Overview
This hands-on lab brings together everything from Chapter 10. You’ll deploy applications, configure Ingress with path-based routing and TLS, investigate CoreDNS, and lock down traffic with NetworkPolicies.
What you’ll practice:
- Deploying Services and Deployments
- Creating path-based Ingress resources
- Configuring TLS termination
- Inspecting CoreDNS configuration and testing DNS
- Writing default-deny and allow-specific NetworkPolicies
Prerequisites:
- A running Kubernetes cluster (minikube, kind, or kubeadm)
- An Ingress Controller installed (NGINX recommended)
- A CNI plugin that supports NetworkPolicies (Calico or Cilium)
Task 1: Deploy Applications
Deploy two applications — web and api — each with a Service.
Instructions
- Create a Deployment named
webwith imagenginx:1.25and 2 replicas, labeledapp: web - Expose it as a ClusterIP Service named
web-svcon port 80 - Create a Deployment named
apiwith imagenginx:1.25and 2 replicas, labeledapp: api - Expose it as a ClusterIP Service named
api-svcon port 80
Try it yourself first, then check the solution.
Show Solution
# Create web Deployment and Service
kubectl create deployment web --image=nginx:1.25 --replicas=2
kubectl expose deployment web --name=web-svc --port=80 --target-port=80
# Create api Deployment and Service
kubectl create deployment api --image=nginx:1.25 --replicas=2
kubectl expose deployment api --name=api-svc --port=80 --target-port=80
Verify:
kubectl get deployments
kubectl get svc
kubectl get pods -l app=web
kubectl get pods -l app=api
Expected output:
NAME READY UP-TO-DATE AVAILABLE AGE
web 2/2 2 2 30s
api 2/2 2 2 25s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web-svc ClusterIP 10.96.100.10 <none> 80/TCP 20s
api-svc ClusterIP 10.96.100.20 <none> 80/TCP 15s
Task 2: Create Path-Based Ingress
Create an Ingress that routes /web to web-svc and /api to api-svc.
Instructions
- Create an Ingress named
app-ingresswith IngressClassnginx - Route path
/web(Prefix) toweb-svcon port 80 - Route path
/api(Prefix) toapi-svcon port 80 - Add the rewrite-target annotation to strip the path prefix
Show Solution
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /web
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
Save as app-ingress.yaml and apply:
kubectl apply -f app-ingress.yaml
Verify:
kubectl get ingress
kubectl describe ingress app-ingress
Expected output (describe):
Rules:
Host Path Backends
---- ---- --------
*
/web web-svc:80 (10.244.1.5:80,10.244.2.3:80)
/api api-svc:80 (10.244.1.6:80,10.244.2.4:80)
Test (if Ingress Controller has an external IP or NodePort):
# Get the Ingress address
INGRESS_IP=$(kubectl get ingress app-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://$INGRESS_IP/web
curl http://$INGRESS_IP/api
Task 3: TLS Configuration
Add TLS termination to the Ingress for host app.example.com.
Instructions
- Generate a self-signed certificate for
app.example.com - Create a TLS Secret named
app-tls - Update the Ingress to use host
app.example.comwith TLS
Show Solution
# Step 1: Generate self-signed certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt -subj "/CN=app.example.com"
# Step 2: Create TLS Secret
kubectl create secret tls app-tls --cert=tls.crt --key=tls.key
Step 3: Update the Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /web
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
Apply:
kubectl apply -f app-ingress.yaml
Verify:
kubectl describe ingress app-ingress | grep -A 2 TLS
Expected output:
TLS:
app-tls terminates app.example.com
Test HTTPS:
curl -vk --resolve app.example.com:443:$INGRESS_IP https://app.example.com/web
Task 4: CoreDNS Investigation
Explore the cluster’s DNS configuration and test resolution.
Instructions
- View the CoreDNS Corefile
- Check CoreDNS Pods are running
- Launch a debug Pod and test DNS resolution for
web-svcandapi-svc - Check the Pod’s
/etc/resolv.conf
Show Solution
# Step 1: View the Corefile
kubectl get configmap coredns -n kube-system -o yaml
# Step 2: Check CoreDNS Pods
kubectl get pods -n kube-system -l k8s-app=kube-dns
# Expected:
# NAME READY STATUS RESTARTS AGE
# coredns-5d78c9869d-abc12 1/1 Running 0 1d
# coredns-5d78c9869d-def34 1/1 Running 0 1d
# Step 3: Launch debug Pod
kubectl run dnsutils --image=registry.k8s.io/e2e-test-images/agnhost:2.39 \
--command -- sleep infinity
# Wait for it to be ready
kubectl wait --for=condition=ready pod/dnsutils --timeout=60s
# Test DNS resolution
kubectl exec dnsutils -- nslookup web-svc
kubectl exec dnsutils -- nslookup web-svc.default.svc.cluster.local
kubectl exec dnsutils -- nslookup api-svc
kubectl exec dnsutils -- nslookup kubernetes.default
# Step 4: Check resolv.conf
kubectl exec dnsutils -- cat /etc/resolv.conf
Expected resolv.conf output:
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
Expected nslookup output:
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-svc.default.svc.cluster.local
Address: 10.96.100.10
Task 5: Default Deny NetworkPolicy
Create a default-deny NetworkPolicy and verify it blocks traffic.
Instructions
- Before creating the policy, test that
webPods can reachapi-svc - Create a default deny all ingress NetworkPolicy in the
defaultnamespace - Verify that traffic to the
apiPods is now blocked
Show Solution
# Step 1: Test connectivity before policy (should work)
kubectl exec -it $(kubectl get pod -l app=web -o name | head -1) -- \
curl -s --max-time 3 http://api-svc
Expected: NGINX welcome page HTML output.
# Step 2: Create default deny policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
Save as deny-all.yaml and apply:
kubectl apply -f deny-all.yaml
# Step 3: Test connectivity after policy (should be blocked)
kubectl exec -it $(kubectl get pod -l app=web -o name | head -1) -- \
curl -s --max-time 3 http://api-svc
Expected: The request should time out after 3 seconds with no response, confirming the policy is blocking traffic.
Verify the policy:
kubectl get networkpolicies
kubectl describe networkpolicy default-deny-ingress
Expected output:
Name: default-deny-ingress
Namespace: default
Spec:
PodSelector: <none> (Coverage: all pods in the namespace)
Allowing ingress traffic:
<none> (Selected pods are isolated for ingress connectivity)
Task 6: Allow Specific Traffic
Create a NetworkPolicy that allows only web Pods to access api Pods on port 80.
Instructions
- Create a NetworkPolicy named
allow-web-to-apithat:- Applies to Pods labeled
app: api - Allows ingress from Pods labeled
app: webon port 80
- Applies to Pods labeled
- Verify
webPods can now reachapiPods - Verify other Pods still cannot reach
apiPods
Show Solution
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-to-api
namespace: default
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 80
Save as allow-web-to-api.yaml and apply:
kubectl apply -f allow-web-to-api.yaml
Test:
# From web Pod — should WORK now
kubectl exec -it $(kubectl get pod -l app=web -o name | head -1) -- \
curl -s --max-time 3 http://api-svc
# From a random Pod — should still be BLOCKED
kubectl run test-pod --image=curlimages/curl --rm -it -- \
curl -s --max-time 3 http://api-svc
Expected:
webPod: Returns NGINX welcome page (traffic allowed)test-pod: Times out after 3 seconds (traffic blocked by default-deny + no matching allow policy)
Verify policies:
kubectl get networkpolicies
Expected output:
NAME POD-SELECTOR AGE
default-deny-ingress <none> 5m
allow-web-to-api app=api 1m
Cleanup
Remove all resources created in this lab:
# Delete Ingress
kubectl delete ingress app-ingress
# Delete NetworkPolicies
kubectl delete networkpolicy default-deny-ingress allow-web-to-api
# Delete TLS Secret
kubectl delete secret app-tls
# Delete Deployments and Services
kubectl delete deployment web api
kubectl delete svc web-svc api-svc
# Delete debug Pod
kubectl delete pod dnsutils
# Clean up generated certificate files
rm -f tls.crt tls.key
Verify everything is cleaned up:
kubectl get ingress,networkpolicy,deployment,svc,pod
You should see only the default kubernetes Service remaining.
Congratulations! You’ve completed the Ingress & DNS lab. You’ve practiced the core skills tested in the CKA exam’s Services & Networking domain — creating Ingress resources with TLS, investigating CoreDNS, and writing NetworkPolicies.