CKA — Ingress & DNS Lab

6 minute read

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

  1. Create a Deployment named web with image nginx:1.25 and 2 replicas, labeled app: web
  2. Expose it as a ClusterIP Service named web-svc on port 80
  3. Create a Deployment named api with image nginx:1.25 and 2 replicas, labeled app: api
  4. Expose it as a ClusterIP Service named api-svc on 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

  1. Create an Ingress named app-ingress with IngressClass nginx
  2. Route path /web (Prefix) to web-svc on port 80
  3. Route path /api (Prefix) to api-svc on port 80
  4. 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

  1. Generate a self-signed certificate for app.example.com
  2. Create a TLS Secret named app-tls
  3. Update the Ingress to use host app.example.com with 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

  1. View the CoreDNS Corefile
  2. Check CoreDNS Pods are running
  3. Launch a debug Pod and test DNS resolution for web-svc and api-svc
  4. 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

  1. Before creating the policy, test that web Pods can reach api-svc
  2. Create a default deny all ingress NetworkPolicy in the default namespace
  3. Verify that traffic to the api Pods 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

  1. Create a NetworkPolicy named allow-web-to-api that:
    • Applies to Pods labeled app: api
    • Allows ingress from Pods labeled app: web on port 80
  2. Verify web Pods can now reach api Pods
  3. Verify other Pods still cannot reach api Pods
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:

  • web Pod: 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.

Back to Chapter Overview →

Updated: