Skip to content

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

1
2
3
4
5
6
7
8
# 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.

1
2
3
4
5
6
7
8
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

1
2
3
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
1
2
3
4
5
6
7
# 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

1
2
3
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:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
# From existing certificate files
kubectl create secret tls app-tls \
  --cert=tls.crt \
  --key=tls.key \
  -n default
1
2
3
4
5
6
7
8
9
# 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:

1
2
3
4
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

1
2
3
4
5
6
7
8
# 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