Introduction
Services provide stable application endpoints inside a cluster. Ingress adds HTTP routing rules in front of Services, allowing several applications to share one entry point by hostname or URL path.
You will inspect the prepared ingress-nginx controller, deploy two backends, add host and path routing, and trace a request through Ingress, Service, and Pod.
Keep the request path in mind throughout the lab: client → ingress-nginx entry point → Ingress rule → Service → ready Pod. When a request fails, checking those links in order is much easier than treating Ingress as a single black box.
Inspect the Ingress Controller
In this step, inspect the controller already supplied by the CKA environment instead of installing a second copy.
An Ingress object is configuration, not a proxy process. It has no effect unless a compatible controller watches it. Start by proving that both pieces of infrastructure exist: an IngressClass named nginx and an available ingress-nginx controller Deployment.
cd /home/labex/project/ingress-routing
kubectl get ingressclasses
kubectl get deployments,pods,services -n ingress-nginx
kubectl wait --for=condition=Available deployment/ingress-nginx-controller -n ingress-nginx --timeout=60s
An Ingress resource stores routing intent. The ingress-nginx controller watches resources whose ingressClassName is nginx and configures the actual proxy.
The controller Service exposes the proxy through a NodePort in this local cluster. You will discover that port later instead of hard-coding it.
The prepared environment reports an nginx class, one available controller, and a NodePort Service:
NAME CONTROLLER
nginx k8s.io/ingress-nginx
deployment.apps/ingress-nginx-controller 1/1 ...
service/ingress-nginx-controller NodePort ... 80:.../TCP,443:.../TCP
The two Completed admission Pods you may also see are installation Jobs that already finished; they are not failed controller replicas.
Deploy Two Service Backends
In this step, create two single-replica web workloads and matching ClusterIP Services from one multi-document file.
These backends use concepts from Kubernetes for Beginners, so the learning focus is their routing relationship rather than Deployment syntax. The file contains five YAML documents separated by ---: one namespace, then a Deployment and Service for each application. Read each Service.spec.selector beside the corresponding Pod label; they must match before Ingress can reach anything.
cd /home/labex/project/ingress-routing
cat <<'EOF' > backends.yaml
apiVersion: v1
kind: Namespace
metadata: {name: practice-ingress}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: storefront, namespace: practice-ingress}
spec:
replicas: 1
selector: {matchLabels: {app: storefront}}
template:
metadata: {labels: {app: storefront}}
spec:
containers: [{name: web, image: nginx:1.27-alpine, imagePullPolicy: IfNotPresent, ports: [{name: http, containerPort: 80}]}]
---
apiVersion: v1
kind: Service
metadata: {name: storefront, namespace: practice-ingress}
spec: {selector: {app: storefront}, ports: [{name: http, port: 80, targetPort: http}]}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: reports, namespace: practice-ingress}
spec:
replicas: 1
selector: {matchLabels: {app: reports}}
template:
metadata: {labels: {app: reports}}
spec:
containers: [{name: web, image: httpd:2.4-alpine, imagePullPolicy: IfNotPresent, ports: [{name: http, containerPort: 80}]}]
---
apiVersion: v1
kind: Service
metadata: {name: reports, namespace: practice-ingress}
spec: {selector: {app: reports}, ports: [{name: http, port: 80, targetPort: http}]}
EOF
Apply all five documents together, then wait for both Deployments. Waiting separately makes it clear which backend failed if one image or selector is wrong:
kubectl apply -f backends.yaml
kubectl rollout status deployment/storefront -n practice-ingress --timeout=60s
kubectl rollout status deployment/reports -n practice-ingress --timeout=60s
Ingress routes only to Services, so each backend must already have ready endpoints.
Confirm that prerequisite explicitly before creating routing rules:
kubectl get services,endpointslices -n practice-ingress
Each Service should have a corresponding EndpointSlice with one ready address.
service/reports ClusterIP ... 80/TCP
service/storefront ClusterIP ... 80/TCP
endpointslice.../reports-... IPv4 80 10.244.104...
endpointslice.../storefront-... IPv4 80 10.244.104...
Generated suffixes and addresses differ, but each Service name should have one non-empty EndpointSlice. Ingress cannot repair a Service with no backend; this check deliberately proves the lower layer first.
Add Host-Based Routing
In this step, route store.practice.test to the storefront Service.
An Ingress rule has three routing decisions: host, path, and backend Service port. ingressClassName: nginx chooses the controller. The rewrite annotation is controller-specific; it changes the upstream request path to /, which both demo servers can answer.
cd /home/labex/project/ingress-routing
cat <<'EOF' > ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: practice-routing
namespace: practice-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: store.practice.test
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: storefront
port: {name: http}
EOF
Apply the rule and use describe to read the controller's interpretation, including the resolved backend endpoint:
kubectl apply -f ingress.yaml
kubectl describe ingress practice-routing -n practice-ingress
pathType: Prefix matches / and its subpaths. The rewrite annotation sends the selected request to / on the backend, where the demo web server has a page. The host is an HTTP routing key; it does not need public DNS for this local test.
describe should resolve the rule all the way to a Pod address:
Rules:
Host Path Backends
store.practice.test
/ storefront:http (10.244.104...:80)
An empty top-level Address immediately after creation is not a failure in this local setup. The resolved storefront:http backend and its Pod address are the important evidence.
Add Path Routing and Test Requests
In this step, add /reports routing and test both rules through the node entry point.
The JSON Patch path ends in /-. For an array, - means append a new item rather than replace the existing / route. The new backend points to the reports Service by its named http port.
Run this append patch once. Unlike kubectl apply, appending to /- is not idempotent: repeating it adds another identical /reports item rather than recognizing the previous one.
The next two assignments discover the endpoint instead of hard-coding it. minikube ip -p labex-v135 returns the node IP for the named Minikube profile, and command substitution $(...) stores each command's output in a shell variable. The JSONPath query selects the Service port named http and extracts its NodePort.
kubectl patch ingress practice-routing -n practice-ingress --type='json' -p='[{"op":"add","path":"/spec/rules/0/http/paths/-","value":{"path":"/reports","pathType":"Prefix","backend":{"service":{"name":"reports","port":{"name":"http"}}}}}]'
NODE_IP=$(minikube ip -p labex-v135)
HTTP_NODE_PORT=$(kubectl get service ingress-nginx-controller -n ingress-nginx -o jsonpath='{.spec.ports[?(@.name=="http")].nodePort}')
echo "Ingress endpoint: ${NODE_IP}:${HTTP_NODE_PORT}"
The two variables avoid embedding environment-specific addresses in the instructions. Now send requests with the host header required by the rule. For each curl command, -s hides the progress meter, --retry 5 retries transient failures, --retry-connrefused also retries an initially refused connection, and --retry-delay 2 waits two seconds between attempts. -H supplies the HTTP Host header, and the pipe sends the response to grep so only identifying page text is shown.
curl -s --retry 5 --retry-connrefused --retry-delay 2 -H 'Host: store.practice.test' "http://${NODE_IP}:${HTTP_NODE_PORT}/" | grep 'Welcome to nginx'
curl -s --retry 5 --retry-connrefused --retry-delay 2 -H 'Host: store.practice.test' "http://${NODE_IP}:${HTTP_NODE_PORT}/reports" | grep 'It works'
The two responses prove that one entry point routes to different Services.
The first response comes from NGINX and the second from Apache httpd. Distinct page text is deliberate evidence that the path rule selected a different backend, not merely that the proxy returned HTTP 200.
Expected matching lines are:
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
<title>It works! Apache httpd</title>
<p>It works!</p>
This result also explains why the custom Host header matters: both requests reach the same node IP and NodePort; the host and path are what select the rule and backend.
Summary
You reused the prepared ingress-nginx controller, connected Ingress rules to ready Services, and tested host- and path-based routing through one entry point.


