Skip to main content

Exercise 9: Ingress with Traefik

Objective​

Learn how to expose applications to external traffic using Ingress resources with Traefik, the default ingress controller in K3s.

Prerequisites​

Understanding Ingress​

Before we start, let's understand what Ingress does:

  • Without Ingress: Each service needs its own LoadBalancer IP
  • With Ingress: One entry point routes to many services based on hostname/path
                          ┌─────────────┐
│ Ingress │
Internet ──────────────►│ (Traefik) │
└──────┬──────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Service A │ │ Service B │ │ Service C │
│ api.app.com │ │ web.app.com │ │ app.com/api │
└─────────────┘ └─────────────┘ └─────────────┘

Exercise: Expose Applications via Ingress​

Step 1: Verify Traefik is Running​

First, confirm Traefik is installed:

# Check Traefik pods
kubectl get pods -n kube-system | grep traefik

# Check Traefik service
kubectl get svc -n kube-system | grep traefik

You should see traefik pods running and a LoadBalancer service.

Step 2: Create Namespace and Applications​

Let's create two web applications to demonstrate routing.

YAML Version: Create apps-setup.yaml:

apiVersion: v1
kind: Namespace
metadata:
name: practice-09
---
# Application 1: Main website
apiVersion: apps/v1
kind: Deployment
metadata:
name: website
namespace: practice-09
spec:
replicas: 2
selector:
matchLabels:
app: website
template:
metadata:
labels:
app: website
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: website-content
---
apiVersion: v1
kind: ConfigMap
metadata:
name: website-content
namespace: practice-09
data:
index.html: |
<!DOCTYPE html>
<html>
<head><title>Main Website</title></head>
<body>
<h1>Welcome to the Main Website</h1>
<p>This is served via Ingress!</p>
</body>
</html>
---
apiVersion: v1
kind: Service
metadata:
name: website
namespace: practice-09
spec:
selector:
app: website
ports:
- port: 80
targetPort: 80
type: ClusterIP
---
# Application 2: API service
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: practice-09
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: api-content
---
apiVersion: v1
kind: ConfigMap
metadata:
name: api-content
namespace: practice-09
data:
index.html: |
{"status": "ok", "service": "api", "message": "API is running via Ingress"}
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: practice-09
spec:
selector:
app: api
ports:
- port: 80
targetPort: 80
type: ClusterIP

Apply:

kubectl apply -f apps-setup.yaml

Wait for pods:

kubectl wait --for=condition=ready pod -l app=website -n practice-09 --timeout=60s
kubectl wait --for=condition=ready pod -l app=api -n practice-09 --timeout=60s

Step 3: Create Ingress for Host-Based Routing​

Route traffic based on hostname (most common pattern).

YAML Version: Create ingress-host.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: host-routing
namespace: practice-09
annotations:
spec.ingressClassName: traefik
spec:
rules:
# Route website.local to the website service
- host: website.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: website
port:
number: 80
# Route api.local to the api service
- host: api.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api
port:
number: 80

Apply:

kubectl apply -f ingress-host.yaml

Verify:

kubectl get ingress -n practice-09
kubectl describe ingress host-routing -n practice-09

Step 4: Test the Ingress​

Get the Traefik LoadBalancer IP:

TRAEFIK_IP=$(kubectl get svc traefik -n kube-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Traefik IP: $TRAEFIK_IP"

Test with curl using the Host header:

# Test website
curl -H "Host: website.local" http://$TRAEFIK_IP

# Test API
curl -H "Host: api.local" http://$TRAEFIK_IP

For browser access, add entries to /etc/hosts:

# Add to /etc/hosts (replace with your Traefik IP)
echo "$TRAEFIK_IP website.local api.local" | sudo tee -a /etc/hosts

Then open http://website.local or http://api.local in your browser.

Step 5: Create Ingress for Path-Based Routing​

Route traffic based on URL path (useful for single domain).

YAML Version: Create ingress-path.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-routing
namespace: practice-09
annotations:
spec.ingressClassName: traefik
spec:
rules:
- host: app.local
http:
paths:
# /api/* goes to api service
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
# Everything else goes to website
- path: /
pathType: Prefix
backend:
service:
name: website
port:
number: 80

Apply:

kubectl apply -f ingress-path.yaml

Test:

# Add to /etc/hosts if not done
echo "$TRAEFIK_IP app.local" | sudo tee -a /etc/hosts

# Test website (root path)
curl http://app.local/

# Test API (api path)
curl http://app.local/api

Step 6: Check Traefik Dashboard (Optional)​

If you have the Traefik dashboard exposed (see Expose Traefik Dashboard), you can view your ingress routes there.

Verification​

Verify everything is working:

# Check ingress resources
kubectl get ingress -n practice-09

# Check endpoints (services have backends)
kubectl get endpoints -n practice-09

# Check pods are receiving traffic
kubectl logs -l app=website -n practice-09 --tail=5
kubectl logs -l app=api -n practice-09 --tail=5

# Test all routes
curl -H "Host: website.local" http://$TRAEFIK_IP
curl -H "Host: api.local" http://$TRAEFIK_IP
curl -H "Host: app.local" http://$TRAEFIK_IP/
curl -H "Host: app.local" http://$TRAEFIK_IP/api

Understanding What Happened​

  • Ingress Resource: Defines routing rules (not the actual router)
  • Ingress Controller (Traefik): Reads Ingress resources and configures routing
  • Host-Based Routing: Routes based on the Host header (domain name)
  • Path-Based Routing: Routes based on the URL path
  • ClusterIP Services: Ingress routes to ClusterIP services (no LoadBalancer needed per service)

When to Use Each Pattern​

PatternUse Case
Host-basedMultiple apps on different subdomains
Path-basedSingle domain with API and frontend
CombinedComplex routing requirements

Cleanup​

Remove all resources:

kubectl delete namespace practice-09

Also clean up /etc/hosts entries if you added them:

# Edit /etc/hosts and remove the lines you added
sudo nano /etc/hosts

Next Steps​

→ Exercise 10: Complete Application

Additional Practice​

  1. Create an Ingress with both host and path routing combined
  2. Add a third service and route to it via a different path
  3. Explore Traefik IngressRoute CRD for more advanced features:
    kubectl get ingressroutes -A
  4. Check the Traefik documentation for middleware (authentication, rate limiting)