Module 5.2: Ingress
Complexity:
[MEDIUM]- Important for external access, multiple conceptsTime to Complete: 45-55 minutes
Prerequisites: Module 5.1 (Services), understanding of HTTP and DNS
Learning Outcomes
Section titled “Learning Outcomes”After completing this module, you will be able to:
- Create Ingress resources with host-based and path-based routing rules
- Configure TLS termination and multiple backend Services in a single Ingress
- Debug Ingress routing failures by checking controller logs, annotations, and backend Service health
- Explain the relationship between Ingress resources, IngressClasses, and Ingress controllers
Why This Module Matters
Section titled “Why This Module Matters”Ingress provides HTTP/HTTPS routing from outside the cluster to Services inside. Instead of exposing multiple LoadBalancer Services (expensive) or using NodePorts (ugly URLs), Ingress gives you host/path-based routing with a single entry point.
The CKAD exam tests:
- Creating Ingress resources
- Host-based and path-based routing
- TLS termination
- Understanding Ingress controllers
The Hotel Reception Analogy
Ingress is like a hotel reception desk. Guests (requests) arrive at one entrance (Ingress) and ask for different services: “restaurant” goes to the dining room Service, “spa” goes to the wellness Service, “room 203” goes to a specific guest Service. The receptionist (Ingress controller) routes everyone to the right place based on what they ask for.
Ingress Components
Section titled “Ingress Components”Ingress Controller
Section titled “Ingress Controller”The Ingress Controller is a pod that watches Ingress resources and configures routing. Common controllers:
- Envoy Gateway (reference Gateway API implementation)
- Traefik (supports both Ingress and Gateway API)
- Kong (supports both Ingress and Gateway API)
- Cilium (CNI with built-in Ingress and Gateway API support)
- NGINX Gateway Fabric (successor to ingress-nginx)
Note: The popular ingress-nginx controller was retired on March 31, 2026 and no longer receives updates. For new deployments, use Gateway API (see CKA Module 3.5) with one of the controllers above.
Important: Ingress resources do nothing without a controller!
# Check if you have an Ingress controllerk get pods -n ingress-nginx# ork get pods -A | grep -i ingressIngress Resource
Section titled “Ingress Resource”The Ingress is a Kubernetes resource defining routing rules:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: my-ingressspec: rules: - host: myapp.example.com http: paths: - path: / pathType: Prefix backend: service: name: my-service port: number: 80Path Types
Section titled “Path Types”Prefix (Most Common)
Section titled “Prefix (Most Common)”Matches URL path prefix:
pathType: Prefixpath: /api# Matches: /api, /api/, /api/users, /api/users/123Pause and predict: You create an Ingress resource with routing rules, but visiting the URL returns nothing.
kubectl get ingressshows no ADDRESS. What is most likely missing from the cluster?
Matches exact path only:
pathType: Exactpath: /api# Matches: /api only# Does NOT match: /api/, /api/usersImplementationSpecific
Section titled “ImplementationSpecific”Depends on IngressClass (controller-specific).
Routing Patterns
Section titled “Routing Patterns”Host-Based Routing
Section titled “Host-Based Routing”Different hosts to different services:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: host-routingspec: rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 80 - host: web.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-service port: number: 80Path-Based Routing
Section titled “Path-Based Routing”Different paths to different services:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: path-routingspec: rules: - host: example.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 80 - path: /web pathType: Prefix backend: service: name: web-service port: number: 80Stop and think: An Ingress has two path rules:
/apiwithpathType: Prefixand/api/v2withpathType: Prefix. A request comes in for/api/v2/users. Which backend receives the traffic? Why?
Default Backend
Section titled “Default Backend”Catch-all for unmatched requests:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: with-defaultspec: defaultBackend: service: name: default-service port: number: 80 rules: - host: example.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 80TLS/HTTPS
Section titled “TLS/HTTPS”Create TLS Secret
Section titled “Create TLS Secret”# Create TLS secret from cert and keyk create secret tls my-tls-secret \ --cert=path/to/tls.crt \ --key=path/to/tls.keyIngress with TLS
Section titled “Ingress with TLS”apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: tls-ingressspec: tls: - hosts: - secure.example.com secretName: my-tls-secret rules: - host: secure.example.com http: paths: - path: / pathType: Prefix backend: service: name: secure-service port: number: 80Visualization
Section titled “Visualization”┌─────────────────────────────────────────────────────────────┐│ Ingress Flow │├─────────────────────────────────────────────────────────────┤│ ││ Internet ││ │ ││ ▼ ││ ┌─────────────────────────────────────┐ ││ │ Ingress Controller │ ││ │ (nginx, traefik, etc.) │ ││ │ │ ││ │ Reads Ingress rules │ ││ │ Routes based on host/path │ ││ └─────────────────────────────────────┘ ││ │ ││ │ api.example.com/users ││ │ ││ ▼ ││ ┌─────────────────────────────────────┐ ││ │ Ingress Resource │ ││ │ │ ││ │ rules: │ ││ │ - host: api.example.com │ ││ │ paths: │ ││ │ - /users → user-svc:80 │ ││ │ - /orders → order-svc:80 │ ││ │ - host: web.example.com │ ││ │ paths: │ ││ │ - / → frontend-svc:80 │ ││ └─────────────────────────────────────┘ ││ │ ││ ▼ ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││ │ user-svc │ │order-svc │ │frontend │ ││ │ :80 │ │ :80 │ │svc :80 │ ││ └──────────┘ └──────────┘ └──────────┘ ││ │└─────────────────────────────────────────────────────────────┘IngressClass
Section titled “IngressClass”Specifies which controller handles the Ingress:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: my-ingressspec: ingressClassName: nginx # Which controller rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: my-service port: number: 80# List available IngressClassesk get ingressclassAnnotations
Section titled “Annotations”Controller-specific behavior via annotations:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: annotated-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/ssl-redirect: "true"spec: rules: - host: example.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 80Common NGINX annotations:
nginx.ingress.kubernetes.io/rewrite-target: URL rewritingnginx.ingress.kubernetes.io/ssl-redirect: Force HTTPSnginx.ingress.kubernetes.io/proxy-body-size: Max request body
Quick Reference
Section titled “Quick Reference”# Create Ingress imperatively (limited)k create ingress my-ingress \ --rule="host.example.com/path=service:port"
# View Ingressk get ingressk describe ingress NAME
# Get Ingress addressk get ingress NAME -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
# Check IngressClassk get ingressclass
# View controller logsk logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginxDid You Know?
Section titled “Did You Know?”-
Ingress is just a configuration, not a service. The actual routing is done by the Ingress controller pods.
-
Multiple Ingress resources can exist for the same host. Controllers typically merge them.
-
The
kubernetes.io/ingress.classannotation is deprecated. Usespec.ingressClassNameinstead (Kubernetes 1.18+). -
Ingress can’t route non-HTTP traffic. For TCP/UDP, use LoadBalancer Services or the newer Gateway API.
Common Mistakes
Section titled “Common Mistakes”| Mistake | Why It Hurts | Solution |
|---|---|---|
| No Ingress controller installed | Ingress does nothing | Install nginx-ingress or similar |
| Wrong pathType | Routes don’t match | Use Prefix for most cases |
| Service name/port mismatch | 503 errors | Verify service exists and port matches |
| Missing host in rules | Matches all hosts | Add explicit host or use carefully |
| TLS secret in wrong namespace | TLS fails | Secret must be in same namespace as Ingress |
-
A developer creates an Ingress resource with correct rules and applies it successfully. But when they visit the URL, nothing happens — no response at all.
kubectl get ingressshows the resource exists but the ADDRESS column is empty. Other teams’ Ingress resources work fine. What should the developer check?Answer
The Ingress resource exists as data in the API server, but an Ingress controller pod must be running to read those rules and configure actual routing. Since other Ingresses work, the controller is installed — the issue is likely that the developer's Ingress doesn't specify the correct `ingressClassName` (or uses a different class than the installed controller). Check `kubectl get ingressclass` to see available classes, then add `spec.ingressClassName:` to the Ingress. Also verify the backend Service exists and has endpoints with `kubectl get endpoints `. -
Users report 503 errors when accessing
app.example.com/api. The Ingress exists and the controller is working.kubectl describe ingressshows the backend isapi-service:80. What are the most likely causes and how do you debug them?Answer
A 503 from the Ingress controller typically means the backend Service has no healthy endpoints. Debug systematically: (1) Check `kubectl get endpoints api-service` — if empty, the Service selector doesn't match any pods or no pods are Ready. (2) Check `kubectl get pods -l` to see if pods exist and are Running/Ready. (3) Verify the Service port (80) matches what the Ingress specifies. (4) Verify the Service's `targetPort` matches what the application actually listens on. (5) Check the Ingress controller logs: `kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx` for specific error messages. The chain is: Ingress -> Service -> Endpoints -> Pods, and any break in that chain produces 503s. -
A team needs HTTPS for their application. They create a TLS Secret and reference it in their Ingress, but browsers show a certificate warning. The Ingress spec has
tls: - hosts: [app.example.com]andsecretName: app-tls. What are two common reasons for the certificate warning?Answer
Two common causes: (1) The TLS Secret is in a different namespace than the Ingress resource. Kubernetes TLS Secrets must be in the same namespace as the Ingress that references them — cross-namespace Secret references are not allowed. Check with `kubectl get secret app-tls` in the Ingress's namespace. (2) The certificate's Common Name (CN) or Subject Alternative Names (SANs) don't match the hostname in the Ingress rule. If the cert was issued for `www.example.com` but the Ingress uses `app.example.com`, browsers will warn about the mismatch. Verify with `openssl x509 -in cert.pem -text | grep -A1 "Subject Alternative Name"`. -
You need to route
shop.example.comto one Service andblog.example.comto another, both through a single Ingress. The blog should also be accessible atshop.example.com/blog. How would you structure the Ingress rules?Answer
Use a combination of host-based and path-based routing. Define two host rules: one for `blog.example.com` routing `/` to the blog-service, and one for `shop.example.com` with two path entries — `/blog` routing to blog-service and `/` (catch-all) routing to shop-service. The more specific path `/blog` takes priority over the prefix `/`. Order the paths from most specific to least specific. Both host rules can be in a single Ingress resource. The key detail is `pathType: Prefix` — the `/blog` prefix rule will match `/blog`, `/blog/`, and `/blog/posts/123`, forwarding all to the blog service.
Hands-On Exercise
Section titled “Hands-On Exercise”Task: Create Ingress with path-based routing.
Setup:
# Create two deploymentsk create deployment web --image=nginxk create deployment api --image=nginx
# Create servicesk expose deployment web --port=80k expose deployment api --port=80
# Wait for podsk wait --for=condition=Ready pod -l app=web --timeout=60sk wait --for=condition=Ready pod -l app=api --timeout=60sPart 1: Simple Ingress
cat << 'EOF' | k apply -f -apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: simple-ingressspec: rules: - http: paths: - path: / pathType: Prefix backend: service: name: web port: number: 80EOF
k get ingress simple-ingressk describe ingress simple-ingressPart 2: Path-Based Routing
cat << 'EOF' | k apply -f -apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: path-ingressspec: rules: - http: paths: - path: /web pathType: Prefix backend: service: name: web port: number: 80 - path: /api pathType: Prefix backend: service: name: api port: number: 80EOF
k describe ingress path-ingressPart 3: Host-Based Routing
cat << 'EOF' | k apply -f -apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: host-ingressspec: rules: - host: web.local http: paths: - path: / pathType: Prefix backend: service: name: web port: number: 80 - host: api.local http: paths: - path: / pathType: Prefix backend: service: name: api port: number: 80EOF
k describe ingress host-ingressCleanup:
k delete ingress simple-ingress path-ingress host-ingressk delete deployment web apik delete svc web apiPractice Drills
Section titled “Practice Drills”Drill 1: Simple Ingress (Target: 2 minutes)
Section titled “Drill 1: Simple Ingress (Target: 2 minutes)”k create deployment drill1 --image=nginxk expose deployment drill1 --port=80
cat << 'EOF' | k apply -f -apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: drill1spec: rules: - http: paths: - path: / pathType: Prefix backend: service: name: drill1 port: number: 80EOF
k get ingress drill1k delete ingress drill1 deploy drill1 svc drill1Drill 2: Host-Based Routing (Target: 3 minutes)
Section titled “Drill 2: Host-Based Routing (Target: 3 minutes)”k create deployment app1 --image=nginxk create deployment app2 --image=nginxk expose deployment app1 --port=80k expose deployment app2 --port=80
cat << 'EOF' | k apply -f -apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: drill2spec: rules: - host: app1.local http: paths: - path: / pathType: Prefix backend: service: name: app1 port: number: 80 - host: app2.local http: paths: - path: / pathType: Prefix backend: service: name: app2 port: number: 80EOF
k describe ingress drill2k delete ingress drill2 deploy app1 app2 svc app1 app2Drill 3: Path-Based Routing (Target: 3 minutes)
Section titled “Drill 3: Path-Based Routing (Target: 3 minutes)”k create deployment frontend --image=nginxk create deployment backend --image=nginxk expose deployment frontend --port=80k expose deployment backend --port=80
cat << 'EOF' | k apply -f -apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: drill3spec: rules: - host: myapp.local http: paths: - path: /frontend pathType: Prefix backend: service: name: frontend port: number: 80 - path: /backend pathType: Prefix backend: service: name: backend port: number: 80EOF
k get ingress drill3k delete ingress drill3 deploy frontend backend svc frontend backendDrill 4: Ingress with Default Backend (Target: 3 minutes)
Section titled “Drill 4: Ingress with Default Backend (Target: 3 minutes)”k create deployment default-app --image=nginxk create deployment api-app --image=nginxk expose deployment default-app --port=80k expose deployment api-app --port=80
cat << 'EOF' | k apply -f -apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: drill4spec: defaultBackend: service: name: default-app port: number: 80 rules: - http: paths: - path: /api pathType: Prefix backend: service: name: api-app port: number: 80EOF
k describe ingress drill4k delete ingress drill4 deploy default-app api-app svc default-app api-appDrill 5: Create Ingress Imperatively (Target: 2 minutes)
Section titled “Drill 5: Create Ingress Imperatively (Target: 2 minutes)”k create deployment drill5 --image=nginxk expose deployment drill5 --port=80
# Create ingress imperativelyk create ingress drill5 --rule="drill5.local/=drill5:80"
k get ingress drill5k describe ingress drill5
k delete ingress drill5 deploy drill5 svc drill5Drill 6: Ingress with TLS (Target: 4 minutes)
Section titled “Drill 6: Ingress with TLS (Target: 4 minutes)”# Create self-signed cert (for demo)openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /tmp/tls.key -out /tmp/tls.crt \ -subj "/CN=secure.local" 2>/dev/null
# Create TLS secretk create secret tls drill6-tls --cert=/tmp/tls.crt --key=/tmp/tls.key
k create deployment drill6 --image=nginxk expose deployment drill6 --port=80
cat << 'EOF' | k apply -f -apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: drill6spec: tls: - hosts: - secure.local secretName: drill6-tls rules: - host: secure.local http: paths: - path: / pathType: Prefix backend: service: name: drill6 port: number: 80EOF
k describe ingress drill6
k delete ingress drill6 secret drill6-tls deploy drill6 svc drill6rm /tmp/tls.key /tmp/tls.crtNext Module
Section titled “Next Module”Module 5.3: NetworkPolicies - Control pod-to-pod communication.