DEV Community

Cover image for Kubernetes Networking [Level-5: Ingress/Gateway]
ADITYA RAJ
ADITYA RAJ

Posted on

Kubernetes Networking [Level-5: Ingress/Gateway]

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)
Enter fullscreen mode Exit fullscreen mode

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

  1. The Problem: The Internet Can't Reach a ClusterIP
  2. The Basic Solution: Ingress and Gateway API
  3. What Is Ingress?
  4. A Routing Example
  5. Ingress Is Not the Actual Proxy
  6. A Simple Analogy: Traffic Police
  7. A Basic Ingress YAML Example
  8. Breaking Down the Key Fields
  9. Host-Based Routing
  10. Path-Based Routing
  11. Why Not Just Use a LoadBalancer Service for Everything?
  12. The Complete Traffic Flow
  13. Where Does DNS Fit In?
  14. The Ingress Controller
  15. A Typical Architecture
  16. Ingress vs Service
  17. Ingress vs LoadBalancer Service
  18. HTTPS and TLS Termination
  19. Why Terminate TLS at the Edge?
  20. Referencing a TLS Certificate
  21. Routing Multiple Domains
  22. The Gateway API
  23. GatewayClass, Gateway, and HTTPRoute
  24. Ingress vs Gateway API
  25. Important Distinctions: Ingress Is Not CNI or Service
  26. Troubleshooting Ingress Layer by Layer
  27. Common Ingress Mistakes
  28. The Complete Kubernetes Networking Picture (Levels 1–5)
  29. The Mental Model to Memorize
  30. Level 5 Checkpoint
  31. 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
Enter fullscreen mode Exit fullscreen mode
frontend Service
       ├── Pod 1
       ├── Pod 2
       └── Pod 3
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Or path-based:

example.com/api → api Service
example.com/    → frontend Service
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You can express this entirely through an Ingress object:

                  Internet
                     |
                     ↓
              Ingress Layer
                 /       \
                ↓         ↓
        frontend Service  backend Service
               |              |
             Pods           Pods
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

A Simple Analogy: Traffic Police

Imagine a large building with a single main entrance from the street:

Internet → Main entrance
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You can route each one to a different backend:

app.example.com   → frontend Service
api.example.com   → backend Service
admin.example.com → admin Service
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
                    Ingress
                       |
          ┌────────────┼────────────┐
          ↓            ↓            ↓
         /            /api        /admin
          ↓            ↓            ↓
      frontend       backend      admin
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Notice how many layers we've now connected across this entire series:

Internet → DNS → Load Balancer → Ingress → Service → Pod
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Cluster DNS (internal, from Level 4):

frontend.default.svc.cluster.local → Service IP
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Ingress answers: "How do external HTTP/HTTPS requests get routed to Services?"

Internet → Ingress → Service → Pods
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The referenced Secret holds the certificate and private key material the Ingress implementation uses:

Client
   | HTTPS
   ↓
Ingress
   | TLS termination
   ↓
Service
Enter fullscreen mode Exit fullscreen mode

Routing Multiple Domains

Suppose you're running several distinct applications under different subdomains:

shop.example.com
api.example.com
admin.example.com
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Chained together:

Gateway → HTTPRoute → Service → Pod
Enter fullscreen mode Exit fullscreen mode

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."
Enter fullscreen mode Exit fullscreen mode

The Gateway API's basic resource relationship looks like this:

GatewayClass → Gateway → HTTPRoute → Service → Pods
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Ingress is not Service:

Service → stable endpoint → Pods
Ingress → external HTTP/HTTPS → Service
Enter fullscreen mode Exit fullscreen mode

The full combined chain is:

Internet → Ingress → Service → Pod
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Look specifically for your ingress controller's Pods.

Layer 4 — Ingress rules: Check the configuration itself.

kubectl get ingress
kubectl describe ingress <name>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Layer 6 — Endpoints: Does the Service have live backend endpoints?

kubectl get endpointslices
Enter fullscreen mode Exit fullscreen mode

Layer 7 — Pod: Is the application actually listening on the expected port?

Service → Pod → Application
Enter fullscreen mode Exit fullscreen mode

Put together, this gives you a reliable troubleshooting chain:

DNS → Load Balancer → Ingress Controller → Ingress/Gateway rule → Service → Endpoint → Pod → Application
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And internally, on the Service side:

Service
   ↓
EndpointSlice
   ├── Pod A
   ├── Pod B
   └── Pod C
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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)