Ingress
Why Ingress Exists
By this point a request reaches a pod through a well-understood chain: Deployment → ReplicaSet → Pods → Service, exposed with NodePort or LoadBalancer. That's enough to get one application reachable. It falls apart once a company runs more than one.
Picture a company with six applications — Frontend, Backend, Admin, Payment, Inventory, and Authentication — each behind its own Service. Exposing all six with NodePort means handing out a different port on every node for each one:
| Application | NodePort |
|---|---|
| Frontend | 30080 |
| Backend | 30081 |
| Admin | 30082 |
| Payment | 30083 |
| Inventory | 30084 |
Nobody is going to remember 192.168.1.10:30080 for the shop and 192.168.1.10:30082 for the admin panel. That's problem one: NodePort sprawl — a growing list of arbitrary high ports that users are expected to memorize.
Switching each Service to type: LoadBalancer doesn't fix it, it just moves the pain to the cloud bill. Every LoadBalancer Service provisions its own cloud load balancer — four Services means four AWS ELBs/NLBs, four public IPs, and four separate line items on the invoice. Real companies running hundreds of Services — Amazon, Netflix, Google — obviously don't provision hundreds of cloud load balancers. That's problem two: cost, one load balancer per Service doesn't scale financially any more than it scales operationally.
The fix Kubernetes engineers landed on was simple to state: why not create one entry point for the entire cluster? That entry point is Ingress.
What Is Ingress?
Officially, an Ingress is a Kubernetes API object that manages external access to Services, typically HTTP and HTTPS. Put simply, it's a smart router for Kubernetes Services — a single entry point that inspects each incoming request and routes it to the right Service based on its hostname or URL path.
Without Ingress, every Service needs its own path to the outside world:
Browser → Frontend Service
Browser → Backend Service
Browser → Payment Service
Browser → Admin Service
With Ingress, there's exactly one door in, and Ingress decides what's behind it:
Browser
│
▼
Ingress
┌──────────┼──────────┐
▼ ▼ ▼
Frontend Backend Payment
Service Service Service
A useful analogy: a shopping mall without a reception desk forces every customer to figure out on their own which door leads to Electronics, which leads to the Food Court, and which leads to Clothing — confusing and error-prone at any scale. Add a reception desk, and customers just say where they want to go; reception routes them. Ingress plays the same role for HTTP traffic — the browser only needs to know one address, and Ingress figures out which Service the request actually belongs to.
Architecture and Request Flow
Ingress sits in front of Services, never in front of Pods directly:
Internet
│
▼
Ingress
│
▼
Services
│
▼
Pods
Slotted into the full chain from earlier lessons, a request travels:
Browser → Ingress → Service → Deployment → ReplicaSet → Pods → Containers
In practice this means: a user opens company.com, Ingress checks its rules, and depending on which rule matches — /shop, /admin, /api — it automatically forwards the request to the matching Service.
Path-Based vs Host-Based Routing
Ingress supports two ways of deciding where a request goes.
Path-based routing keeps everything under one domain and splits by URL path:
company.com/shop → Shop Service
company.com/admin → Admin Service
company.com/api → Backend Service
Host-based routing uses separate subdomains, each mapping to its own Service:
shop.company.com → Shop Service
admin.company.com → Admin Service
api.company.com → Backend Service
| Path-Based Routing | Host-Based Routing | |
|---|---|---|
| URL shape | company.com/shop | shop.company.com |
| DNS requirement | One domain, no extra DNS records | A DNS record per subdomain |
| TLS certificates | One certificate covers all paths | Needs a certificate per host (or a wildcard) |
| Best for | Grouping features under one product/domain | Separating distinct apps or teams that deserve their own identity |
| Example | company.com/admin → Admin Service | admin.company.com → Admin Service |
Neither is strictly better — most production Ingress setups mix both, using host-based routing to separate major applications and path-based routing within each host.
Ingress YAML, Field by Field
A minimal Ingress that routes everything under company.local to a single Service:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
spec:
rules:
- host: company.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80| Field | Meaning |
|---|---|
apiVersion: networking.k8s.io/v1 | Ingress belongs to the Networking API group |
kind: Ingress | Creates an Ingress resource |
metadata.name | The Ingress object's name (app-ingress) |
spec.rules | The list of routing rules — host and path combinations |
rules[].host | Requests for this hostname (company.local) follow the rules underneath it |
paths[].path | The URL path to match, e.g. / |
paths[].pathType | Prefix means every URL starting with the given path matches |
paths[].backend.service.name | The Service that receives matching traffic |
paths[].backend.service.port.number | The port on that Service to forward to |
Create it and check that it registered:
kubectl apply -f ingress.yaml
kubectl get ingressNAME HOSTS ADDRESS
app-ingress company.local
Routing to Multiple Applications
A single Ingress object can front an arbitrary number of Services — add one rule (or one path) per application:
Internet
│
▼
Ingress
├── /shop → Shop Service
├── /admin → Admin Service
├── /payment → Payment Service
└── /inventory → Inventory Service
│
▼
Pods
One Ingress, many applications — that's the whole point.
Benefits
- Single entry point for the entire cluster
- Path-based routing
- Host-based routing
- HTTPS support with SSL termination
- Lower cloud cost — no more one load balancer per Service
- Centralized, easy domain management
An Ingress object only defines rules — it does not send traffic to anything by itself. Nothing reads host: or path: and forwards a request unless something is watching the Ingress API and acting on it. That something is the Ingress Controller, covered next.
NGINX Ingress Controller
An Ingress Alone Does Nothing
If you create an Ingress and nothing else, requests to it simply fail:
Browser
│
▼
Ingress
│
▼
❌ Nothing Happens
The reason is that Ingress is only a configuration object stored in the Kubernetes API — it's the equivalent of a traffic sign. A sign describes a rule (host: company.com → send to this Service), but a sign doesn't stop cars or direct them anywhere. Traffic rules without traffic police get ignored. Kubernetes needs something actively watching Ingress objects and turning those rules into real routing decisions — that component is the Ingress Controller.
What Is an Ingress Controller?
Officially, an Ingress Controller is a Kubernetes controller that watches Ingress resources and configures a reverse proxy or load balancer to route traffic accordingly. More simply: it reads Ingress rules and forwards requests to the correct Services — it's the software that makes Ingress actually work.
Ingress → Rules Only → Needs → Ingress Controller → Traffic Routing
Without a controller installed, requests to an Ingress just dead-end. With one running:
Browser
│
▼
NGINX Ingress Controller
│
▼
Ingress Rules
│
▼
Service
│
▼
Pods
Why NGINX?
NGINX is already a familiar web server, reverse proxy, and load balancer — which makes it a natural fit for implementing Ingress: it just needs to read Ingress objects and turn them into the reverse-proxy config it already knows how to serve. It's also the controller most Kubernetes beginners learn first and the de facto default across tutorials and starter clusters. It isn't the only option, though:
| Controller | Maintainer | Notes |
|---|---|---|
| NGINX Ingress Controller | Kubernetes community | The default choice for most clusters; what this lesson installs |
| AWS Load Balancer Controller | AWS | Provisions native AWS ALBs/NLBs instead of an in-cluster proxy |
| Traefik | Traefik Labs | Modern reverse proxy with automatic service discovery |
| HAProxy Ingress | HAProxy | Built on the HAProxy load balancer |
| Kong Ingress Controller | Kong | Adds full API gateway features (auth, rate limiting) on top of routing |
| Istio Gateway | Istio | Ingress handled as part of a full service mesh |
Controller Architecture and Objects
The controller sits in front of Ingress: it reads the rules, then forwards to the Service, which forwards to Pods.
Internet
│
▼
NGINX Ingress Controller
│
reads Ingress
│
▼
Service
│
▼
Pods
Under the hood, "installing the NGINX Ingress Controller" means deploying real Kubernetes objects into the cluster — a Deployment running the NGINX controller pods, and a Service (typically LoadBalancer or NodePort) that exposes those pods to the outside world. It's worth keeping the four layers distinct, since they're easy to conflate:
Ingress → Configuration (what to route)
NGINX Controller → Software (does the routing)
Service → Networking (stable address for a group of pods)
Pod → Application (what actually handles the request)
Request Flow
Walking through a real request to shop.company.com:
- Browser → NGINX Controller — the request hits the controller's exposed IP/port first.
- NGINX checks Ingress rules — it looks for a rule matching
host: shop.company.com. - Find the backend — the matching rule points to
shop-service. - Forward — NGINX sends the request to
shop-service, which load-balances it to a healthy pod's container; the response travels back the same path to the browser.
Installing on Kind
Spin up a local cluster and install the NGINX Ingress Controller's Kind-specific manifest (it's tuned to work with Kind's networking setup):
kind create cluster --name dev-cluster
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
# Wait for the controller pod to be ready before using it
kubectl wait --namespace ingress-nginx \
--for=condition=Ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120sVerify the install landed correctly — it creates its own ingress-nginx namespace, a controller pod, and a Service:
kubectl get ns
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginxDeploying a Sample Application Behind It
With the controller running, expose a throwaway nginx deployment through it:
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
spec:
ingressClassName: nginx
rules:
- host: nginx.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80kubectl apply -f ingress.yaml
kubectl get ingress
kubectl describe ingress nginx-ingresskubectl describe prints the resolved rules and, once the controller has picked the object up, the backend it's routing to — that's the confirmation the controller is actually watching this Ingress.
Understanding ingressClassName
ingressClassName: nginxA cluster can have more than one Ingress Controller running at once — NGINX, Traefik, an AWS controller, Istio's gateway — each watching for Ingress objects it should handle. ingressClassName is how an Ingress object declares which controller is responsible for it, so controllers don't fight over the same rules or silently ignore objects meant for a different one.
ingressClassName isn't optional in practice — omit it (or typo it) and, once more than one controller is installed, your Ingress can sit there unprocessed with no error message. If kubectl describe ingress shows no address and no events from a controller, check ingressClassName first.
Complete Architecture
Putting every layer together, from the internet down to the container handling the request:
Internet
│
▼
NGINX Ingress Controller
│
▼
Ingress Rules
│
▼
Service
│
▼
Deployment
│
▼
ReplicaSet
│
▼
Pods
│
▼
Containers
This is what it looks like at production scale, with one controller fronting several independent applications:
Users → Load Balancer → NGINX Ingress Controller → Frontend Service → Frontend Pods
Users → Load Balancer → NGINX Ingress Controller → API Service → Backend Pods
Users → Load Balancer → NGINX Ingress Controller → Payment Service → Payment Pods
One controller, one entry point, routing everything — which is exactly the problem this lesson started with.
Benefits
- Centralized routing for the whole cluster
- SSL/TLS termination in one place
- Path-based and host-based routing
- Built-in load balancing and reverse proxying
- Simple, centralized domain management
- Battle-tested and production-ready
Four ideas worth keeping straight going forward:
- Service exposes one application, inside or outside the cluster.
- Ingress defines routing rules for multiple Services.
- Ingress Controller (e.g. NGINX) reads those rules and performs the actual routing.
- NGINX itself is the reverse proxy and load balancer doing the work underneath the controller.