Ingress: Getting Traffic Into the Cluster
Services give workloads stable internal network access. Ingress adds HTTP routing into the cluster from outside, usually through an ingress controller that receives traffic and forwards it to Services.
What this lesson covers
In the Services lesson, you learned how a Service gives stable access to changing Pods. But a ClusterIP Service is only reachable inside the cluster. Ingress helps route external HTTP or HTTPS traffic to Services inside the cluster.
By the end of this lesson, you should understand:
- What Ingress is
- What an ingress controller is
- Why Ingress is different from a Service
- How host-based routing works
- How path-based routing works
- How Ingress routes to Services
- How to install an ingress controller in a local kind lab
- How to create and test an Ingress resource
- How to troubleshoot common Ingress issues
- How to clean up the lab resources
The short version
Ingress is a Kubernetes API object that defines HTTP and HTTPS routing rules.
An ingress controller is the actual software component that implements those rules.
Browser or client
sends HTTP request
Ingress controller receives request
Ingress rule matches host/path
Service receives traffic
Pods handle the request
That means Ingress does not replace Services. Ingress usually routes to Services, and Services route to Pods.
Service vs Ingress
A Service gives stable network access to Pods.
An Ingress gives HTTP routing into Services.
Deployment creates Pods Service gives stable access to Pods Ingress defines HTTP routing to Services Ingress controller actually receives and forwards HTTP traffic
A good mental model is:
- Deployment: runs the application Pods.
- Service: gives those Pods a stable internal address.
- Ingress: defines external HTTP routing rules.
- Ingress controller: enforces those routing rules.
Why Ingress exists
Without Ingress, exposing multiple HTTP applications can get messy.
You could create separate NodePort or LoadBalancer Services for each application, but that often creates too many ports, too many load balancers, and too much provider-specific setup.
Ingress gives you a cleaner pattern:
app1.example.com -> service/app1 app2.example.com -> service/app2 example.com/api -> service/api example.com/web -> service/web
This is similar to reverse proxy routing in traditional infrastructure.
Host-based routing
Host-based routing sends traffic to different Services based on the requested hostname.
training.localhost -> training-service api.training.localhost -> api-service
The incoming HTTP Host header determines which backend Service receives the request.
Path-based routing
Path-based routing sends traffic to different Services based on the URL path.
training.localhost/ -> web-service training.localhost/api -> api-service training.localhost/admin -> admin-service
This is useful when multiple applications or components share the same hostname but use different paths.
Before you start the lab
This lab assumes:
- Docker Desktop is running
- Your kind cluster exists
- kubectl is installed
- Your current context is kind-stealth-k8s-lab
Check your context:
kubectl config current-context
You should see:
kind-stealth-k8s-lab
Check your node:
kubectl get nodes
If your cluster no longer exists, recreate it:
kind create cluster --name stealth-k8s-lab
Important kind networking note
In a local kind cluster, the Kubernetes nodes run as Docker containers. That means external access behaves differently than it does in a cloud cluster.
To make this Ingress lab reliable, we will install an ingress controller and then use kubectl port-forward to forward local traffic to the ingress controller.
This keeps the lab simple and avoids needing a special kind cluster configuration with host port mappings.
Step 1: Install the NGINX Ingress Controller
Install the NGINX Ingress Controller for the local lab:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.1/deploy/static/provider/kind/deploy.yaml
This creates resources in the ingress-nginx namespace.
What this command does
- Creates the ingress-nginx namespace
- Creates service accounts, roles, and role bindings
- Deploys the NGINX Ingress Controller
- Creates supporting Services and admission webhook resources
The install may take a minute or two to become ready.
Step 2: Wait for the ingress controller to be ready
Run:
kubectl get pods -n ingress-nginx
You should see the ingress controller Pod.
Wait for it to become ready:
kubectl wait --namespace ingress-nginx --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=180s
If the wait command times out, check the Pods again:
kubectl get pods -n ingress-nginx kubectl describe pod -n ingress-nginx <controller-pod-name>
Step 3: Create a test application Deployment
Create a simple web application using nginx:
kubectl create deployment ingress-web-lab --image=nginx:latest
Scale it to two replicas:
kubectl scale deployment ingress-web-lab --replicas=2
Check the Pods:
kubectl get pods -o wide
You should see two nginx Pods running.
Step 4: Create a Service for the application
Create a ClusterIP Service for the Deployment:
kubectl expose deployment ingress-web-lab --port=80 --target-port=80 --type=ClusterIP
Check the Service:
kubectl get services
Check endpoints:
kubectl get endpoints ingress-web-lab
You should see endpoints for the nginx Pods.
Step 5: Create an Ingress resource
Create a file named:
ingress-lab.yaml
Paste this into the file:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-web-lab
spec:
ingressClassName: nginx
rules:
- host: training.localhost
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ingress-web-lab
port:
number: 80
Apply it:
kubectl apply -f ingress-lab.yaml
Check the Ingress:
kubectl get ingress
Describe it:
kubectl describe ingress ingress-web-lab
What this Ingress does
- Uses the nginx ingress class
- Matches requests for the host training.localhost
- Matches the path /
- Forwards matching requests to the Service named ingress-web-lab on port 80
Step 6: Port-forward to the ingress controller
In this local kind lab, we will forward local port 8080 to the NGINX Ingress Controller.
Run:
kubectl port-forward -n ingress-nginx service/ingress-nginx-controller 8080:80
Keep this PowerShell window open while testing.
Step 7: Test the Ingress with curl
Open a second PowerShell window and run:
curl.exe -H "Host: training.localhost" http://localhost:8080
You should see the nginx welcome page HTML returned.
Why we pass the Host header
The Ingress rule is matching on the hostname training.localhost.
Since we are testing through localhost:8080, we manually send the Host header so the ingress controller can match the rule.
Step 8: Test in a browser
You can also test with a browser by adding a temporary hosts file entry.
Open Notepad as Administrator and edit:
C:\Windows\System32\drivers\etc\hosts
Add:
127.0.0.1 training.localhost
Save the file.
With the port-forward still running, open:
http://training.localhost:8080
You should see the nginx welcome page.
When you are done testing, you can remove the hosts file entry.
Step 9: Add a second application
Create another Deployment using httpd:
kubectl create deployment ingress-api-lab --image=httpd:latest
Expose it with a Service:
kubectl expose deployment ingress-api-lab --port=80 --target-port=80 --type=ClusterIP
Check the Service endpoints:
kubectl get endpoints ingress-api-lab
Step 10: Update the Ingress for path-based routing
Replace the contents of ingress-lab.yaml with this:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-web-lab
spec:
ingressClassName: nginx
rules:
- host: training.localhost
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ingress-web-lab
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: ingress-api-lab
port:
number: 80
Apply it:
kubectl apply -f ingress-lab.yaml
Describe the Ingress again:
kubectl describe ingress ingress-web-lab
You should now see two paths:
- / routes to ingress-web-lab
- /api routes to ingress-api-lab
Step 11: Test path-based routing
In the second PowerShell window, test the root path:
curl.exe -H "Host: training.localhost" http://localhost:8080/
Then test the API path:
curl.exe -H "Host: training.localhost" http://localhost:8080/api
You should see responses from different backend applications.
In this basic lab, the exact page content is not as important as understanding that the ingress controller is routing based on the path.
Step 12: Break the Ingress on purpose
Edit ingress-lab.yaml and change the backend Service name for the /api path:
service:
name: service-that-does-not-exist
port:
number: 80
Apply it:
kubectl apply -f ingress-lab.yaml
Describe the Ingress:
kubectl describe ingress ingress-web-lab
Test the broken path:
curl.exe -H "Host: training.localhost" http://localhost:8080/api
You may see an error response because the route points to a Service that does not exist.
Step 13: Fix the Ingress
Change the backend Service name back to:
name: ingress-api-lab
Apply it:
kubectl apply -f ingress-lab.yaml
Test again:
curl.exe -H "Host: training.localhost" http://localhost:8080/api
The route should work again.
Step 14: Check ingress controller logs
Get the ingress controller Pod name:
kubectl get pods -n ingress-nginx
View logs from the controller:
kubectl logs -n ingress-nginx <controller-pod-name>
The controller logs can help when routes are not behaving as expected.
Step 15: Stop port-forwarding
Go back to the PowerShell window running port-forward and press:
Ctrl+C
This stops forwarding local traffic to the ingress controller.
Step 16: Clean up the lab applications
Delete the Ingress:
kubectl delete -f ingress-lab.yaml
Delete the Services:
kubectl delete service ingress-web-lab kubectl delete service ingress-api-lab
Delete the Deployments:
kubectl delete deployment ingress-web-lab kubectl delete deployment ingress-api-lab
Confirm cleanup:
kubectl get ingress kubectl get services kubectl get deployments kubectl get pods
Optional: Remove the ingress controller
If you want to remove the NGINX Ingress Controller from your lab cluster, run:
kubectl delete namespace ingress-nginx
You can also leave it installed for future Ingress labs.
Common Ingress troubleshooting commands
Use this flow when Ingress does not work:
kubectl get ingress kubectl describe ingress <ingress-name> kubectl get ingressclass kubectl get services kubectl describe service <service-name> kubectl get endpoints <service-name> kubectl get pods --show-labels kubectl get pods -n ingress-nginx kubectl logs -n ingress-nginx <controller-pod-name> kubectl get events --sort-by=.metadata.creationTimestamp
This usually tells you:
- Whether the Ingress exists
- Whether an ingress controller is installed
- Whether the ingress class matches the controller
- Whether the backend Service exists
- Whether the Service has endpoints
- Whether the selected Pods are running
- Whether the request hostname and path match the Ingress rule
Common Ingress problems
No ingress controller installed
Creating an Ingress resource does not automatically install a controller. Without a controller, nothing is available to enforce the routing rules.
Wrong ingress class
If the Ingress specifies an ingress class that does not match the installed controller, the controller may ignore it.
Backend Service does not exist
The Ingress may point to a Service name that is misspelled or missing.
Service has no endpoints
The Service exists, but it does not match any healthy Pods.
Host header does not match
The Ingress rule may require a specific hostname. If the request uses a different hostname, the rule may not match.
DNS points to the wrong place
In real environments, DNS must point to the load balancer or ingress entry point.
What about TLS?
In production, Ingress commonly handles HTTPS traffic with TLS certificates.
That usually involves:
- A DNS name
- A certificate
- A Kubernetes Secret containing certificate material
- An Ingress rule referencing the TLS Secret
- An ingress controller configured to terminate TLS
We are intentionally staying with HTTP in this beginner lab. TLS, certificates, cert-manager, and production-grade ingress patterns come later.
What you just learned
In this lesson, you:
- Installed the NGINX Ingress Controller
- Created a Deployment and Service
- Created an Ingress resource
- Used host-based routing
- Tested Ingress with curl and a Host header
- Tested Ingress in a browser with a hosts file entry
- Added a second application
- Configured path-based routing
- Broke and fixed an Ingress backend Service reference
- Checked ingress controller logs
- Cleaned up the lab resources
Check your understanding
Use these questions to test whether the Ingress concepts made sense.
- What problem does Ingress solve?
- Does an Ingress resource work by itself without an ingress controller?
- What does an ingress controller do?
- Does Ingress usually route directly to Pods or to Services?
- What is host-based routing?
- What is path-based routing?
- Why did we use port-forwarding in the kind lab?
- Why did we pass a Host header when testing with curl?
- What should you check if an Ingress backend Service has no endpoints?
- What command shows Ingress resources?
- What namespace did the NGINX Ingress Controller run in during this lab?
- What are three things involved in production HTTPS Ingress?
Answer key
- Ingress defines HTTP and HTTPS routing into Services inside the cluster.
- No. An ingress controller is needed to implement the routing rules.
- It watches Ingress resources and routes incoming traffic according to those rules.
- Ingress usually routes to Services, and Services route to Pods.
- Routing based on the requested hostname.
- Routing based on the URL path.
- kind runs Kubernetes nodes as Docker containers, so port-forwarding gives a simple reliable way to test locally.
- The Ingress rule matched the hostname training.localhost, so the request needed that Host header.
- Check whether the Service selector matches the Pod labels and whether the Pods are running and ready.
- kubectl get ingress.
- ingress-nginx.
- Examples: DNS name, TLS certificate, Kubernetes TLS Secret, Ingress TLS configuration, ingress controller, and load balancer.