Kubernetes Core Concepts

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
Field note: An Ingress resource is just the routing rule. It does not do anything useful unless an ingress controller is installed and watching for those rules.

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.

Field note: If you have worked with IIS ARR, NGINX, Apache reverse proxy, HAProxy, or cloud load balancer routing rules, Ingress will feel familiar. It is Kubernetes' way of declaring HTTP routing rules.

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.

Field note: In production, Ingress is usually connected to a real load balancer, DNS record, and TLS certificate. In this lab, we use port-forwarding so you can learn the routing model locally.

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.

Field note: Ingress routes to Services. If the Service has no endpoints, the Ingress may route correctly but still have no healthy backend Pods to receive traffic.

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.

Field note: DNS and hostnames matter for Ingress. If the request hostname does not match the Ingress rule, the request may not route where you expect.

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.

Field note: Ingress is where Kubernetes starts to look like traditional edge routing: DNS, hostnames, paths, TLS, load balancers, and reverse proxy behavior all come together.

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
Bottom line: Services provide stable internal access to Pods. Ingress defines HTTP routing into Services. The ingress controller is the component that actually receives traffic and applies the routing rules.

Check your understanding

Use these questions to test whether the Ingress concepts made sense.

  1. What problem does Ingress solve?
  2. Does an Ingress resource work by itself without an ingress controller?
  3. What does an ingress controller do?
  4. Does Ingress usually route directly to Pods or to Services?
  5. What is host-based routing?
  6. What is path-based routing?
  7. Why did we use port-forwarding in the kind lab?
  8. Why did we pass a Host header when testing with curl?
  9. What should you check if an Ingress backend Service has no endpoints?
  10. What command shows Ingress resources?
  11. What namespace did the NGINX Ingress Controller run in during this lab?
  12. What are three things involved in production HTTPS Ingress?
Answer key
  1. Ingress defines HTTP and HTTPS routing into Services inside the cluster.
  2. No. An ingress controller is needed to implement the routing rules.
  3. It watches Ingress resources and routes incoming traffic according to those rules.
  4. Ingress usually routes to Services, and Services route to Pods.
  5. Routing based on the requested hostname.
  6. Routing based on the URL path.
  7. kind runs Kubernetes nodes as Docker containers, so port-forwarding gives a simple reliable way to test locally.
  8. The Ingress rule matched the hostname training.localhost, so the request needed that Host header.
  9. Check whether the Service selector matches the Pod labels and whether the Pods are running and ready.
  10. kubectl get ingress.
  11. ingress-nginx.
  12. Examples: DNS name, TLS certificate, Kubernetes TLS Secret, Ingress TLS configuration, ingress controller, and load balancer.
← Previous: ConfigMaps and Secrets Series index Next: Namespaces →