This is Level 5 of our Kubernetes networking series. So far, we've built up a solid foundation:
LEVEL 1 — Pod networking
LEVEL 2 — Pod-to-Pod communication
LEVEL 3 — Service (a stable internal endpoint)
LEVEL 4 — DNS (service name → Service IP)
But we still have a glaring gap: how does a real user on the internet actually reach your Kubernetes application? That's exactly what this article covers — Ingress, Ingress Controllers, and the newer Gateway API.
Table of Contents
- The Problem: The Internet Can't Reach a ClusterIP
- The Basic Solution: Ingress and Gateway API
- What Is Ingress?
- A Routing Example
- Ingress Is Not the Actual Proxy
- A Simple Analogy: Traffic Police
- A Basic Ingress YAML Example
- Breaking Down the Key Fields
- Host-Based Routing
- Path-Based Routing
- Why Not Just Use a LoadBalancer Service for Everything?
- The Complete Traffic Flow
- Where Does DNS Fit In?
- The Ingress Controller
- A Typical Architecture
- Ingress vs Service
- Ingress vs LoadBalancer Service
- HTTPS and TLS Termination
- Why Terminate TLS at the Edge?
- Referencing a TLS Certificate
- Routing Multiple Domains
- The Gateway API
- GatewayClass, Gateway, and HTTPRoute
- Ingress vs Gateway API
- Important Distinctions: Ingress Is Not CNI or Service
- Troubleshooting Ingress Layer by Layer
- Common Ingress Mistakes
- The Complete Kubernetes Networking Picture (Levels 1–5)
- The Mental Model to Memorize
- Level 5 Checkpoint
- What's Next: NetworkPolicy
The Problem: The Internet Can't Reach a ClusterIP
Suppose you want users to reach your application at myapp.example.com. Inside your cluster, you have:
Service: frontend
ClusterIP: 10.96.20.10
frontend Service
├── Pod 1
├── Pod 2
└── Pod 3
A user on the internet can't simply visit http://10.96.20.10 — that's a private Kubernetes Service IP, invisible outside the cluster. We need something sitting at the edge of the cluster to bridge that gap.
The Basic Solution: Ingress and Gateway API
Historically, Kubernetes solved this with Ingress. More recently, Kubernetes introduced a more expressive alternative: the Gateway API. At a high level, both follow the same shape:
Internet
↓
Ingress / Gateway
↓
Service
↓
Pods
That's the core mental model for this entire article.
What Is Ingress?
Ingress is a Kubernetes API object that describes how incoming HTTP/HTTPS traffic should be routed to Services. For example:
app.example.com → frontend Service
api.example.com → backend Service
Or path-based:
example.com/api → api Service
example.com/ → frontend Service
In short, Ingress lets you declaratively describe HTTP routing rules for your cluster.
A Routing Example
Suppose you have a frontend Service and a backend Service, and you want:
app.example.com → frontend
app.example.com/api → backend
You can express this entirely through an Ingress object:
Internet
|
↓
Ingress Layer
/ \
↓ ↓
frontend Service backend Service
| |
Pods Pods
Ingress Is Not the Actual Proxy
This is a critical point that trips up a lot of beginners: an Ingress object doesn't magically start receiving internet traffic on its own. It's just a set of rules. You need an Ingress Controller to actually enforce them — implementations include NGINX, HAProxy, Traefik, and various cloud-provider load balancer integrations.
Ingress = rules/configuration
Ingress Controller = the component that actually implements those rules
A Simple Analogy: Traffic Police
Imagine a large building with a single main entrance from the street:
Internet → Main entrance
At the entrance, someone asks "where are you going?" If you say app.example.com, you're directed to the frontend. If you say api.example.com, you're directed to the backend.
The Ingress rules are the instructions written down. The Ingress Controller is the person actually standing there enforcing them.
A Basic Ingress YAML Example
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
Read this as: "If the HTTP request is for app.example.com, send it to the frontend Service on port 80."
Breaking Down the Key Fields
host — which domain this rule matches:
host: app.example.com
path — which URL path this rule matches. For example, path: / with pathType: Prefix could match /, /login, /products, /about, and so on, depending on the exact path-matching configuration.
backend.service — where the matched traffic gets sent:
backend:
service:
name: frontend
port:
number: 80
So together: app.example.com → frontend Service → Pods.
Host-Based Routing
One of the most useful Ingress features is routing based on hostname. Suppose you have three domains:
app.example.com
api.example.com
admin.example.com
You can route each one to a different backend:
app.example.com → frontend Service
api.example.com → backend Service
admin.example.com → admin Service
A single entry point can serve many completely different applications this way.
Path-Based Routing
You can also route based on the URL path rather than the hostname:
example.com/ → frontend
example.com/api → backend
example.com/admin → admin
Ingress
|
┌────────────┼────────────┐
↓ ↓ ↓
/ /api /admin
↓ ↓ ↓
frontend backend admin
This pattern is extremely common in real-world Kubernetes setups.
Why Not Just Use a LoadBalancer Service for Everything?
A fair question: "Why not just create a LoadBalancer Service for every application?" You technically can — but imagine you have 50 applications:
LoadBalancer 1 → App 1
LoadBalancer 2 → App 2
...
LoadBalancer 50 → App 50
That's a lot of external load balancers to manage (and often, a lot of cost). Instead, a single edge layer can route traffic to many applications at once:
Internet
↓
Load Balancer
↓
Ingress
/ | \
↓ ↓ ↓
App1 App2 App3
One entry point, many destinations — far simpler to operate.
The Complete Traffic Flow
This is the most important diagram in this article. Suppose a user opens https://app.example.com:
User
| HTTPS
↓
DNS
| app.example.com → public IP
↓
Load Balancer
↓
Ingress Controller
| host = app.example.com
↓
frontend Service
↓
Endpoint / Pod
↓
Application
Notice how many layers we've now connected across this entire series:
Internet → DNS → Load Balancer → Ingress → Service → Pod
Where Does DNS Fit In?
This is a different DNS context than the one we covered in Level 4 — it's worth being precise about the distinction.
External DNS (public internet):
app.example.com → 203.0.113.50 (public IP / load balancer)
Cluster DNS (internal, from Level 4):
frontend.default.svc.cluster.local → Service IP
Don't mix these two up — they're separate DNS systems solving separate problems, even though both are technically "DNS."
The Ingress Controller
If you create an Ingress object but nothing is actually watching and implementing it, the rules just sit there inert. You need a running Ingress Controller:
Ingress object
↓
Ingress Controller
↓
Actual traffic handling
The controller continuously watches Kubernetes API objects (Ingress resources) and configures its own proxy/load-balancing dataplane to match.
A Typical Architecture
Internet
|
↓
Cloud Load Balancer
|
↓
Ingress Controller
|
┌─────────┼─────────┐
↓ ↓ ↓
Service A Service B Service C
| | |
Pods Pods Pods
The exact shape of this architecture varies depending on your cloud provider and cluster setup, but this general pattern is extremely common.
Ingress vs Service
An important distinction to internalize:
Service answers: "How do I reach this group of Pods?"
Service → Pods
Ingress answers: "How do external HTTP/HTTPS requests get routed to Services?"
Internet → Ingress → Service → Pods
In short: Service is an internal/application endpoint abstraction; Ingress is HTTP/HTTPS edge routing.
Ingress vs LoadBalancer Service
These two aren't competitors — they typically work together:
Internet
↓
Cloud Load Balancer
↓
Ingress Controller
↓
Service
↓
Pods
The cloud load balancer's job is simply to get traffic into the cluster. Once it arrives, the Ingress Controller decides where within the cluster that HTTP request should actually go.
HTTPS and TLS Termination
Ingress is commonly used to terminate TLS. When a user visits https://app.example.com, the encrypted connection reaches the Ingress layer first:
Client
| HTTPS
↓
Ingress Controller
| HTTP or HTTPS
↓
Service
↓
Pod
This process — decrypting HTTPS at the edge — is called TLS termination.
Why Terminate TLS at the Edge?
Terminating TLS at the Ingress layer centralizes several responsibilities in one place: certificate management, HTTPS handling, HTTP routing, redirects, and both host- and path-based routing. Your application can then simply receive plain HTTP internally, if that fits your security model.
That said, don't assume Ingress always means "HTTP behind the scenes" — you can also configure TLS to be re-encrypted or passed through all the way to the backend Pod, depending on your architecture and requirements.
Referencing a TLS Certificate
An Ingress can reference a Kubernetes TLS Secret directly:
tls:
- hosts:
- app.example.com
secretName: app-tls
The referenced Secret holds the certificate and private key material the Ingress implementation uses:
Client
| HTTPS
↓
Ingress
| TLS termination
↓
Service
Routing Multiple Domains
Suppose you're running several distinct applications under different subdomains:
shop.example.com
api.example.com
admin.example.com
A single Ingress layer can route all of them:
Ingress
|
┌──────────────┼──────────────┐
↓ ↓ ↓
shop.example.com api.example.com admin.example.com
↓ ↓ ↓
shop backend admin
Service Service Service
This is host-based routing at work again, just applied across an entire portfolio of applications.
The Gateway API
Now let's look at the newer Kubernetes networking API: the Gateway API. Think of it as an evolution of Ingress, not something unrelated:
Ingress → older/simpler HTTP routing API
Gateway API → more expressive, extensible traffic-routing model
Gateway API introduces a small family of dedicated resources: GatewayClass, Gateway, and HTTPRoute.
GatewayClass, Gateway, and HTTPRoute
GatewayClass answers: "What kind of Gateway implementation are we using?" — essentially, which controller manages Gateways of this class.
Gateway represents the actual traffic entry point:
Internet → Gateway
It defines listeners, such as HTTP on port 80 or HTTPS on port 443.
HTTPRoute describes the routing rules themselves — for example:
app.example.com → frontend Service
app.example.com/api → backend Service
Chained together:
Gateway → HTTPRoute → Service → Pod
Ingress vs Gateway API
You don't need to memorize every field of either API right now — just understand the high-level difference:
| Ingress | Gateway API | |
|---|---|---|
| Maturity | Older, widely adopted | Newer, more expressive API family |
| Scope | Mainly HTTP/HTTPS routing | Broader traffic-routing model |
| Structure | Simpler, single object | More structured (GatewayClass + Gateway + Routes) |
A useful mental model:
Ingress: "Here are my HTTP routing rules."
Gateway API: "Here is my traffic infrastructure, and here are the routes attached to it."
The Gateway API's basic resource relationship looks like this:
GatewayClass → Gateway → HTTPRoute → Service → Pods
You don't need to implement any of this from scratch right now — the goal here is understanding the traffic flow conceptually.
Important Distinctions: Ingress Is Not CNI or Service
Two easy things to conflate — worth separating clearly.
Ingress is not CNI:
CNI → how Pods get network connectivity
Ingress → how external HTTP/HTTPS traffic is routed to Services
Ingress is not Service:
Service → stable endpoint → Pods
Ingress → external HTTP/HTTPS → Service
The full combined chain is:
Internet → Ingress → Service → Pod
Troubleshooting Ingress Layer by Layer
When https://app.example.com doesn't work, resist the urge to restart things randomly. Instead, work through the chain one layer at a time.
Layer 1 — External DNS: Does app.example.com resolve to the expected public IP?
nslookup app.example.com
Layer 2 — Load Balancer: Can the public IP actually reach your load balancer or entry point?
Layer 3 — Ingress Controller: Is it actually running?
kubectl get pods -A
Look specifically for your ingress controller's Pods.
Layer 4 — Ingress rules: Check the configuration itself.
kubectl get ingress
kubectl describe ingress <name>
Verify the host, path, backend Service, and TLS configuration all match what you expect.
Layer 5 — Service: Does the target Service actually exist?
kubectl get svc
Layer 6 — Endpoints: Does the Service have live backend endpoints?
kubectl get endpointslices
Layer 7 — Pod: Is the application actually listening on the expected port?
Service → Pod → Application
Put together, this gives you a reliable troubleshooting chain:
DNS → Load Balancer → Ingress Controller → Ingress/Gateway rule → Service → Endpoint → Pod → Application
Common Ingress Mistakes
Mistake 1 — Wrong hostname: The Ingress rule expects api.example.com, but the user requests app.example.com — no rule matches.
Mistake 2 — Wrong Service name: The Ingress points to backend-service, but the actual Service is named backend.
Mistake 3 — Wrong Service port: The Ingress specifies port 80, but the Service actually exposes a different port.
Mistake 4 — Service has no endpoints: The Service backend exists, but its selector doesn't match any running Pods — traffic reaches the Service and simply has nowhere to go.
Mistake 5 — External DNS points to the wrong place: app.example.com resolves to the wrong public IP entirely. In this case, your Kubernetes cluster can be perfectly healthy while users still can't reach the application — the problem never even makes it inside the cluster.
The Complete Kubernetes Networking Picture (Levels 1–5)
We can now connect everything we've covered across this series so far:
INTERNET
|
↓
External DNS
|
↓
Public IP / LB
|
↓
Ingress / Gateway
|
↓
Service
|
↓
Pod IP
|
↓
Pod networking
|
↓
Application
And internally, on the Service side:
Service
↓
EndpointSlice
├── Pod A
├── Pod B
└── Pod C
This is a genuine milestone — you now have a complete, end-to-end mental model from a browser tab all the way down to a running container.
The Mental Model to Memorize
Don't try to memorize every YAML field from this article. Instead, memorize the shape and the responsibility of each layer:
External DNS → Public endpoint → Ingress/Gateway → Service → EndpointSlice → Pod
- DNS → "Where is it?"
- Ingress/Gateway → "Which application should receive this request?"
- Service → "Which Pods provide this application?"
- EndpointSlice → "Which endpoints currently exist?"
- Pod networking → "How does the packet actually reach the Pod?"
Level 5 Checkpoint
1. What problem does Ingress solve?
Routing external HTTP/HTTPS traffic to Kubernetes Services.
2. Is Ingress itself a proxy?
Not necessarily — the Ingress object defines rules; an Ingress Controller is what actually implements them.
3. What is host-based routing?
Routing based on hostname — e.g., app.example.com → frontend, api.example.com → backend.
4. What is path-based routing?
Routing based on URL path — e.g., example.com/ → frontend, example.com/api → backend.
5. What is TLS termination?
When the edge component (the Ingress Controller) receives HTTPS traffic and handles the TLS connection before forwarding the request onward.
6. Service vs Ingress?
Ingress → external HTTP/HTTPS → Service → Pods.
7. What is the Gateway API?
A newer, more expressive Kubernetes API family for configuring traffic infrastructure and the routes attached to it.
8. What are the key Gateway API resources?
At this level, remember GatewayClass, Gateway, and HTTPRoute.
What's Next: NetworkPolicy
In Level 6, we'll tackle a very different kind of question:
Pods can currently talk to each other freely. What if I don't want every Pod to be able to talk to every other Pod?
We'll cover allow/deny traffic rules, ingress vs egress policies, selectors, namespaces, CIDR-based rules, default-deny policies, and one crucial caveat: NetworkPolicy only works when your networking implementation actually supports and enforces it.
Conclusion
Ingress and the Gateway API are what finally connect the outside world to your Kubernetes Services. An Ingress (or Gateway) object is just a declaration of routing intent — host rules, path rules, TLS settings — while an Ingress Controller (or Gateway implementation) is the actual component that turns those rules into working traffic routing. Once you can trace a request all the way from external DNS through the load balancer, the Ingress Controller, the Service, and finally into a Pod, Kubernetes networking stops feeling like a black box.
The next time something breaks in production, don't guess — walk the chain layer by layer: DNS, load balancer, Ingress Controller, Ingress rule, Service, endpoints, Pod. That habit will save you far more time than randomly restarting components.
Enjoyed this deep dive? Follow along for Level 6, where we lock down Pod-to-Pod traffic using NetworkPolicy. Drop your questions in the comments, and share this with a teammate still confused about why their Ingress "isn't working" even though the Pods are healthy. Let's keep building this Kubernetes networking roadmap together. 🚀
Top comments (0)