Module 12 — Ingress
Overview
Ingress exposes HTTP/HTTPS routes from outside the cluster to Services inside the cluster. Unlike NodePort or LoadBalancer Services (which work at L4), Ingress operates at L7 — it understands HTTP paths, hostnames, and TLS. The CKA exam tests Ingress resource creation, routing rules, and TLS configuration.
1. Ingress Concepts
1.1 The Problem Ingress Solves
Without Ingress, exposing multiple HTTP services requires multiple LoadBalancers or NodePorts:
| Without Ingress:
app.example.com → LoadBalancer 1 → app-service
api.example.com → LoadBalancer 2 → api-service
blog.example.com → LoadBalancer 3 → blog-service
(3 external IPs, 3 cloud LBs = expensive)
With Ingress:
app.example.com ─┐
api.example.com ─┼──▶ Single Ingress Controller ──▶ Services
blog.example.com ─┘ (1 external IP)
|
1.2 Two Components
| Component |
What it is |
Who creates it |
| Ingress Resource |
YAML object defining routing rules (host, path → Service) |
You (the admin) |
| Ingress Controller |
The actual reverse proxy that reads Ingress resources and routes traffic |
Deployed separately (not built into Kubernetes) |
Important: An Ingress resource does nothing without an Ingress Controller running in the cluster. Kubernetes does NOT ship with one by default.
1.3 How It Works
| External Client
│
│ https://app.example.com/api
▼
┌──────────────────────────┐
│ Ingress Controller │ (e.g., NGINX, Traefik, HAProxy)
│ (runs as Deployment + │
│ Service type LB/NP) │
│ │
│ Reads Ingress rules: │
│ host: app.example.com │
│ path: /api → api-svc │
│ path: / → web-svc │
└──────────┬───────────────┘
│
┌─────┴──────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ api-svc │ │ web-svc │
│ :8080 │ │ :80 │
└─────────┘ └─────────┘
|
2. Ingress Controllers
2.1 Common Controllers
| Controller |
Maintained by |
Notes |
| NGINX Ingress Controller |
Kubernetes community (ingress-nginx) |
Most common, CKA default |
| NGINX Inc |
F5/NGINX |
Commercial version |
| Traefik |
Traefik Labs |
Auto-discovery, Let's Encrypt |
| HAProxy |
HAProxy Technologies |
High performance |
| Contour |
VMware/Projectcontour |
Envoy-based |
| AWS ALB Ingress Controller |
AWS |
Maps to Application Load Balancer |
2.2 Installing the NGINX Ingress Controller
| # Using the official manifest
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/baremetal/deploy.yaml
# Verify
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
# NAME TYPE CLUSTER-IP PORT(S)
# ingress-nginx-controller NodePort 10.96.x.x 80:30080/TCP,443:30443/TCP
|
On cloud providers, use the cloud-specific manifest to get a LoadBalancer:
| # Cloud (creates a LoadBalancer Service)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml
|
2.3 Verifying the Controller
| # Check the IngressClass (required since K8s 1.18+)
kubectl get ingressclass
# NAME CONTROLLER PARAMETERS AGE
# nginx k8s.io/ingress-nginx <none> 5m
# Check controller pods
kubectl get pods -n ingress-nginx
# Check controller logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
|
CKA Tip: The exam environment usually has an Ingress Controller pre-installed. Check with kubectl get ingressclass to find the class name.
3. IngressClass
Since Kubernetes 1.18, Ingress resources must reference an IngressClass to specify which controller handles them.
| apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx
annotations:
ingressclass.kubernetes.io/is-default-class: "true" # makes this the default
spec:
controller: k8s.io/ingress-nginx
|
Reference it in Ingress resources:
| spec:
ingressClassName: nginx # matches IngressClass metadata.name
|
If an IngressClass is marked as default, you can omit ingressClassName and it will be used automatically.
4. Path-Based Routing
Route traffic to different Services based on the URL path.
4.1 Basic Example
| https://example.com/app → app-service:80
https://example.com/api → api-service:8080
https://example.com/ → default-service:80
|
| apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-routing
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # strip the path prefix
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /app
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: default-service
port:
number: 80
|
4.2 pathType
| pathType |
Behavior |
Example |
Prefix |
Matches the URL path prefix (by path element) |
/api matches /api, /api/, /api/v1 |
Exact |
Matches the exact URL path only |
/api matches only /api, NOT /api/ or /api/v1 |
ImplementationSpecific |
Matching depends on the IngressClass |
Controller-defined |
| # Prefix match
- path: /api
pathType: Prefix # matches /api, /api/, /api/users, /api/v2/items
# Exact match
- path: /api
pathType: Exact # matches ONLY /api
|
CKA Tip: Use Prefix in most cases. Exact is for when you need strict matching.
4.3 Rewrite Target
Without rewrite, a request to /app/page is forwarded as /app/page to the backend. If the backend expects /page, you need rewrite:
| metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: example.com
http:
paths:
- path: /app(/|$)(.*) # capture group
pathType: ImplementationSpecific
backend:
service:
name: app-service
port:
number: 80
|
| Request |
Forwarded to backend as |
/app |
/ |
/app/page |
/page |
/app/api/v1 |
/api/v1 |
5. Host-Based Routing
Route traffic to different Services based on the hostname.
5.1 Basic Example
| app.example.com → app-service:80
api.example.com → api-service:8080
blog.example.com → blog-service:80
|
| apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: host-routing
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- host: blog.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: blog-service
port:
number: 80
|
5.2 Wildcard Hosts
| rules:
- host: "*.example.com" # matches app.example.com, api.example.com, etc.
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wildcard-service
port:
number: 80
|
5.3 Combining Host and Path Routing
| rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: app-api
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: app-frontend
port:
number: 80
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 80
|
5.4 Default Backend
Handles requests that don't match any rule:
| spec:
defaultBackend:
service:
name: default-service
port:
number: 80
rules:
- host: app.example.com
# ...
|
If no defaultBackend is set and no rule matches, the Ingress Controller returns a 404.
6. TLS Termination
6.1 How TLS Works with Ingress
| Client ──── HTTPS ────▶ Ingress Controller ──── HTTP ────▶ Backend Service
(TLS terminated here)
|
The Ingress Controller handles TLS encryption/decryption. Traffic between the controller and backend Services is typically plain HTTP (within the cluster).
6.2 Creating a TLS Secret
TLS certificates are stored as Kubernetes Secrets of type kubernetes.io/tls:
| # From existing certificate files
kubectl create secret tls app-tls \
--cert=tls.crt \
--key=tls.key \
-n default
|
| # Or declaratively
apiVersion: v1
kind: Secret
metadata:
name: app-tls
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-certificate>
tls.key: <base64-encoded-private-key>
|
CKA Tip: The Secret must have keys named exactly tls.crt and tls.key. The Secret must be in the same namespace as the Ingress resource.
6.3 Configuring TLS on an Ingress
| apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com # hostname(s) the cert covers
secretName: app-tls # TLS Secret name
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
|
6.4 Multiple TLS Certificates
| spec:
tls:
- hosts:
- app.example.com
secretName: app-tls
- hosts:
- api.example.com
secretName: api-tls
- hosts:
- "*.example.com" # wildcard cert
secretName: wildcard-tls
rules:
- host: app.example.com
# ...
- host: api.example.com
# ...
|
6.5 Force HTTPS Redirect
Redirect all HTTP traffic to HTTPS:
| metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true" # default is true
nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # force even behind LB
|
6.6 Self-Signed Certificate for Testing
| # Generate a self-signed cert
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key \
-out tls.crt \
-subj "/CN=app.example.com"
# Create the Secret
kubectl create secret tls app-tls --cert=tls.crt --key=tls.key
|
7. Useful Annotations (NGINX Ingress)
| Annotation |
Purpose |
Example |
nginx.ingress.kubernetes.io/rewrite-target |
Rewrite the URL path |
/$2 |
nginx.ingress.kubernetes.io/ssl-redirect |
Redirect HTTP → HTTPS |
"true" |
nginx.ingress.kubernetes.io/proxy-body-size |
Max request body size |
"10m" |
nginx.ingress.kubernetes.io/proxy-read-timeout |
Backend read timeout |
"60" |
nginx.ingress.kubernetes.io/affinity |
Session affinity |
"cookie" |
nginx.ingress.kubernetes.io/auth-type |
Basic auth |
"basic" |
nginx.ingress.kubernetes.io/backend-protocol |
Protocol to backend |
"HTTPS" |
CKA Tip: You don't need to memorize annotations. The exam question will tell you if a specific annotation is needed, or you can find them in the Kubernetes docs.
8. Imperative Ingress Creation
| # Create a simple Ingress (Kubernetes 1.19+)
kubectl create ingress my-ingress \
--class=nginx \
--rule="app.example.com/=app-service:80" \
--rule="app.example.com/api=api-service:8080"
# With TLS
kubectl create ingress tls-ingress \
--class=nginx \
--rule="app.example.com/=app-service:80,tls=app-tls"
# With default backend
kubectl create ingress default-ingress \
--class=nginx \
--default-backend=default-service:80 \
--rule="app.example.com/=app-service:80"
# Dry-run to generate YAML
kubectl create ingress my-ingress \
--class=nginx \
--rule="app.example.com/=app-service:80" \
--dry-run=client -o yaml > ingress.yaml
|
9. Troubleshooting Ingress
9.1 Debugging Checklist
| # 1. Is the Ingress Controller running?
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
# 2. Is the IngressClass available?
kubectl get ingressclass
# 3. Does the Ingress resource exist and have an address?
kubectl get ingress
kubectl describe ingress <name>
# 4. Are the backend Services and Endpoints healthy?
kubectl get svc <backend-service>
kubectl get endpoints <backend-service>
# 5. Check Ingress Controller logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
# 6. Test from inside the cluster
kubectl run debug --image=busybox -it --rm --restart=Never -- \
wget -qO- --header="Host: app.example.com" http://<ingress-controller-svc-ip>
|
9.2 Common Issues
| Problem |
Cause |
Fix |
| Ingress has no ADDRESS |
No Ingress Controller running |
Install an Ingress Controller |
| 404 for all requests |
No matching rule or wrong host header |
Check rules[].host and test with correct Host header |
| 503 Service Unavailable |
Backend Service has no Endpoints |
Check Service selector and pod readiness |
| TLS not working |
Secret not found or wrong namespace |
Verify Secret exists in the same namespace as Ingress |
| Wrong IngressClass |
ingressClassName doesn't match any controller |
kubectl get ingressclass and use the correct name |
| Path routing not working |
Missing pathType or wrong path |
Ensure pathType: Prefix and paths are ordered correctly |
10. Practice Exercises
Exercise 1 — Simple Ingress with Path Routing
| # 1. Create two deployments and services
kubectl create deployment app --image=nginx
kubectl expose deployment app --port=80
kubectl create deployment api --image=httpd
kubectl expose deployment api --port=80
# 2. Create an Ingress with path-based routing
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-ingress
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /app
pathType: Prefix
backend:
service:
name: app
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
EOF
# 3. Verify
kubectl get ingress
kubectl describe ingress path-ingress
# 4. Test (from inside the cluster or using the NodePort)
kubectl run debug --image=busybox -it --rm --restart=Never -- \
wget -qO- --header="Host: myapp.example.com" http://<ingress-controller-ip>/app
# 5. Clean up
kubectl delete ingress path-ingress
kubectl delete deployment app api
kubectl delete svc app api
|
Exercise 2 — Host-Based Routing
| # 1. Create deployments and services
kubectl create deployment frontend --image=nginx
kubectl expose deployment frontend --port=80
kubectl create deployment backend --image=httpd
kubectl expose deployment backend --port=80
# 2. Create Ingress with host-based routing
kubectl create ingress host-ingress \
--class=nginx \
--rule="frontend.example.com/=frontend:80" \
--rule="backend.example.com/=backend:80"
# 3. Verify
kubectl get ingress host-ingress
kubectl describe ingress host-ingress
# 4. Clean up
kubectl delete ingress host-ingress
kubectl delete deployment frontend backend
kubectl delete svc frontend backend
|
Exercise 3 — TLS Ingress
| # 1. Generate a self-signed certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=secure.example.com"
# 2. Create the TLS Secret
kubectl create secret tls secure-tls --cert=tls.crt --key=tls.key
# 3. Create a deployment and service
kubectl create deployment secure-app --image=nginx
kubectl expose deployment secure-app --port=80
# 4. Create Ingress with TLS
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
spec:
ingressClassName: nginx
tls:
- hosts:
- secure.example.com
secretName: secure-tls
rules:
- host: secure.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: secure-app
port:
number: 80
EOF
# 5. Verify
kubectl get ingress tls-ingress
kubectl describe ingress tls-ingress
# 6. Test HTTPS (self-signed, so use -k to skip cert verification)
curl -k --resolve secure.example.com:443:<node-ip> https://secure.example.com:<https-nodeport>
# 7. Clean up
kubectl delete ingress tls-ingress
kubectl delete secret secure-tls
kubectl delete deployment secure-app
kubectl delete svc secure-app
rm tls.crt tls.key
|
Exercise 4 — Imperative Ingress Creation
| # 1. Create backend
kubectl create deployment web --image=nginx
kubectl expose deployment web --port=80
# 2. Create Ingress imperatively
kubectl create ingress quick-ingress \
--class=nginx \
--rule="web.example.com/=web:80"
# 3. Verify
kubectl get ingress quick-ingress -o yaml
# 4. Clean up
kubectl delete ingress quick-ingress
kubectl delete deployment web
kubectl delete svc web
|
Exercise 5 — Troubleshoot a Broken Ingress
| # 1. Create a deployment (but DON'T create the service)
kubectl create deployment broken --image=nginx
# 2. Create an Ingress pointing to a non-existent service
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: broken-ingress
spec:
ingressClassName: nginx
rules:
- host: broken.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: broken-svc
port:
number: 80
EOF
# 3. Describe the Ingress — look for warnings
kubectl describe ingress broken-ingress
# 4. Fix: create the missing service
kubectl expose deployment broken --name=broken-svc --port=80
# 5. Verify the Ingress now has endpoints
kubectl describe ingress broken-ingress
# 6. Clean up
kubectl delete ingress broken-ingress
kubectl delete deployment broken
kubectl delete svc broken-svc
|
11. Key Takeaways for the CKA Exam
| Point |
Detail |
| Ingress = L7 routing |
HTTP/HTTPS host and path routing to Services |
| Ingress Controller required |
Ingress resources do nothing without a controller |
ingressClassName |
Required since K8s 1.18 — check with kubectl get ingressclass |
pathType is mandatory |
Use Prefix for most cases, Exact for strict matching |
| TLS Secret must be same namespace |
Type kubernetes.io/tls with keys tls.crt and tls.key |
kubectl create ingress |
Fastest way to create on the exam |
--rule="host/path=svc:port,tls=secret" |
Imperative format for rules with TLS |
| Default backend |
Catches unmatched requests — optional but useful |
| Annotations are controller-specific |
NGINX annotations won't work on Traefik and vice versa |
| Check Endpoints for 503 |
503 usually means the backend Service has no healthy pods |
| Host header matters |
When testing, use --header="Host: ..." or --resolve with curl |
Previous: 11-networking-model.md — Networking Model & DNS
Next: 13-network-policies.md — Network Policies