Most architecture docs have one: a box labeled "Ingress" with an arrow pointing straight at a row of Pods. It looks fine until someone debugging a 502 asks where TLS ends, which proxy holds the connection, or why a NetworkPolicy blocks traffic that "goes through the Ingress". A good Kubernetes ingress diagram answers those questions before anyone has to ask. This post walks the request path hop by hop, maps a minimal Ingress manifest onto boxes and arrows, lists the mistakes that come up most in review, and shows how to build the same diagram in Archboard.
The request path, hop by hop
Follow https://shop.example.com/api/orders from the client down to a container:
- Client → DNS. The client resolves
shop.example.com. The record (A/AAAA, CNAME or a provider alias) points at the cloud load balancer, not at a node. - DNS → cloud load balancer. The client connects to the load balancer. In most clusters it exists because the ingress controller is exposed through a Service of
type: LoadBalancer, and the cloud integration provisions it to forward to node ports or directly to the controller Pods. - Load balancer → ingress controller. The controller is an ordinary workload (a Deployment or DaemonSet) running a reverse proxy such as NGINX, Envoy, HAProxy or Traefik. This is the component that accepts connections and proxies requests.
- Ingress rules (host/path). The controller watches Ingress objects of its class and turns them into proxy configuration. The Ingress object itself never touches a packet. It is configuration.
- Service (ClusterIP). Each rule points at a Service name and port. The Service gives a stable name and selects Pods by label.
- EndpointSlices. The control plane records the Pod IPs behind each Service, with a ready condition for each, in EndpointSlice objects. Many controllers, ingress-nginx included, read EndpointSlices and send traffic straight to Pod IPs instead of the ClusterIP. The Service still has to exist: it is how the controller finds those endpoints.
- Pods. The request lands on a container port in one of the ready Pods.
A note on ingress-nginx: the community ingress-nginx controller was retired in March 2026 and no longer gets releases, bug fixes or security patches. It is still the most familiar example; for a new cluster, pick a maintained controller or Gateway API.
TLS termination: pick a boundary and draw it
There are three common places for HTTPS to end:
| Where TLS ends | Who holds the certificate | What to draw |
|---|---|---|
| Cloud load balancer | The provider's certificate service | Boundary ends at the LB; label LB → controller as HTTP or re-encrypted HTTPS |
| Ingress controller | A Secret of type kubernetes.io/tls, referenced in spec.tls |
Secret attached to the Ingress; boundary ends at the controller |
| Pod (passthrough) | The application | Controller forwards raw TLS by SNI; boundary ends in the Pod. A controller-specific feature, not part of the Ingress spec |
The Secret must live in the same namespace as the Ingress that references it. If you terminate at the load balancer and re-encrypt to the controller, draw two TLS segments, not one.
Host routing vs path routing
- Host routing sends different hostnames to different backends (
shop.example.comvsadmin.example.com). Draw one group of edges per host. - Path routing splits one host by URL path. With
pathType: Prefix, matching works on path elements split by/, so/apimatches/api/ordersbut not/apiv2.Exactmatches only that path;ImplementationSpecificis up to the controller. - When several paths match, the longest one wins. Most real Ingresses combine host and path rules.
Where NetworkPolicy applies
NetworkPolicy filters Pod traffic at L3/L4, and only when the cluster's network plugin enforces it. It knows nothing about hosts or paths and does not attach to the Ingress object. On the diagram, the hop it governs is controller Pods → application Pods. If the app namespace has a default-deny ingress policy, add one that allows traffic from the controller's namespace, for example with a namespaceSelector on kubernetes.io/metadata.name: ingress-nginx. The load balancer → controller hop is governed by cloud firewall rules plus any policy in the controller namespace.
Multiple ingress classes
Clusters often run more than one controller: public and internal, or old and new during a migration. Each controller claims an IngressClass, and each Ingress picks one with spec.ingressClassName. One class can be marked default with the annotation ingressclass.kubernetes.io/is-default-class: "true". Give each controller its own path from its own load balancer and label every Ingress with its class; otherwise readers assume one proxy handles everything.
Gateway API, briefly
Gateway API (gateway.networking.k8s.io) is the newer, role-oriented alternative: a GatewayClass describes the implementation, a Gateway defines listeners and TLS (usually owned by the platform team), and HTTPRoutes carry routing rules (usually owned by app teams). Its core HTTP routing has been GA since v1.0 in 2023. The Ingress API is still GA and supported, but frozen: it gets no new features. The diagram barely changes: the Gateway takes the place of the controller's listener, and HTTPRoutes take the place of Ingress rules.
A minimal Ingress, mapped to the Kubernetes ingress diagram
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
namespace: shop
spec:
ingressClassName: nginx
tls:
- hosts:
- shop.example.com
secretName: shop-example-com-tls
rules:
- host: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: orders-api
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: storefront
port:
number: 80
| Field | Box or arrow on the diagram |
|---|---|
metadata.namespace: shop |
Namespace frame around the Ingress, Secret, Services and Deployments |
spec.ingressClassName: nginx |
Which controller picks it up: a dashed "watches" edge from that controller to the Ingress |
spec.tls[].hosts + secretName |
Secret node attached to the Ingress; TLS boundary ending at the controller |
rules[].host |
The DNS record label and the entry edge from the load balancer |
paths[0]: /api → orders-api:8080 |
Edge from controller to the orders-api Service, labeled /api |
paths[1]: / → storefront:80 |
Edge from controller to the storefront Service, labeled / |
backend.service.port.number |
Port label on each controller → Service edge |
/api/orders matches both paths; /api is longer, so it goes to orders-api.
Common mistakes
- Drawing the Ingress as the proxy. LB → Ingress → Pods reads as if the Ingress were a Pod. The controller is the proxy; the Ingress is configuration it reads. Put the controller in the data path and hang the Ingress off it with a dashed edge.
- Omitting the Service. Ingress backends reference Services, not Pods. Even when traffic goes straight to Pod IPs, the Service selector is what breaks when labels drift, so keep it on the diagram.
- No TLS boundary. If the diagram does not show where HTTPS ends, nobody can tell whether traffic inside the VPC is plaintext.
- A floating NetworkPolicy. A policy with no edge to the hop it governs explains nothing.
Building it in Archboard
Archboard's Kubernetes pack has first-class nodes with ports for the objects above, including IngressClass, Gateway and Route, and the Network pack covers DNS and the load balancer. The finished version is the diagram below, and Open in Archboard copies it into your workspace; /examples/kubernetes-ingress-flow is a smaller Ingress → Service → Pods sketch. To build it yourself:
- Create a diagram and place a client (a Core Box or Icon, or a C4 Person), then a DNS node and a load balancer node from the Network pack.
- Add a Namespace frame named
ingress-nginxwith a Deployment and a Pod for the controller. The load balancer edge goes to this Pod, because it is the proxy. - Add a second Namespace frame named
shop. Inside it, place the Ingress, a Secret (shop-example-com-tls) and two Service nodes,orders-apiandstorefront, each connected to a Deployment and a Pod. - Connect ports along the data path: client → DNS → load balancer → controller Pod → each Service (label the edges
/apiand/) → Pods. Label the Service → Pod edges "EndpointSlices" so readers can see how traffic reaches the Pods. - Connect the controller to the Ingress with a dashed "watches" edge, and the Secret to the Ingress.
- Add a NetworkPolicy node in the
shopframe and connect it to the controller → application hop it allows. - Mark the TLS boundary with a Core Frame or Box around the encrypted segment, labeled with where it terminates.
- Paste the Ingress YAML into a Markdown block on the canvas, next to the boxes it describes.
Diagram contents
- Kubernetes cluster
- ingress-nginx
- shop
- Clients
- DNS · shop.example.com
- Cloud load balancer
- ingress-nginx-controller
- shop-example-com-tls
- orders-api
- storefront
- allow-from-ingress
- DNS lookup
- HTTPS :443
- TCP :443
- /api
- /
- EndpointSlices
- allows ingress-nginx → :8080
- watches
- TLS certificate
The Free plan allows 15 diagram nodes per diagram, plus 150 notes and text. The full build above uses 18, so drop the three Deployment nodes, the controller's included, and connect Services directly to Pods; the traffic path stays intact. Export as SVG or PNG (watermarked on Free), or as structured JSON to keep next to your manifests. For the wider cluster view, see /examples/kubernetes-microservices and Archboard for Kubernetes. The upstream reference is the Kubernetes Ingress documentation.
FAQ
Should the Ingress sit inside the application namespace? Yes. Ingress is namespaced and can only reference Services and TLS Secrets in its own namespace. The controller usually runs in a namespace of its own.
Does traffic pass through the Service's ClusterIP? It depends on the controller. Many proxy directly to Pod IPs from EndpointSlices. Draw the Service anyway and label the edge.
Ingress or Gateway API for a new cluster? If your controller supports it, Gateway API: the Ingress API is frozen. The diagram method is the same.