Ingress, Gateway API and exposing applications

Put HTTP traffic into the cluster: Ingress resources and controllers, host and path routing, TLS termination, and when the Gateway API earns its extra objects.

An Ingress object is only half the story

Ingress is a routing rule expressed as a Kubernetes object. On its own it does nothing. You also need an Ingress controller — a Deployment inside the cluster that watches Ingress objects and programs a real proxy such as nginx, Envoy or Traefik.

# the controller is a normal workload; install it once per cluster
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx --create-namespace

kubectl get pods -n ingress-nginx
kubectl get ingressclass
kubectl get svc -n ingress-nginx        # the LoadBalancer the whole world hits
ApproachLayerCost and caveats
ClusterIPL4, internal onlyCheapest. Reachable only from inside the cluster
NodePortL4, every nodeOpens a high port on every node; you manage the load balancer yourself
LoadBalancerL4, one address per ServiceOne cloud LB per service gets expensive fast; no HTTP awareness
IngressL7, shared entry pointOne address for many hosts and paths, with TLS. Needs a controller
Gateway APIL7, shared entry pointSame idea with typed roles and richer matching

Hosts, paths and TLS

An Ingress matches on host and path, then forwards to a Service and port. Everything else — rewrites, timeouts, body size, sticky sessions — is expressed through controller-specific annotations, which is the main reason the Ingress API is being superseded.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: storefront
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts: ["shop.example.com"]
      secretName: shop-tls          # the TLS certificate as a Secret
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 8080
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80
    - host: blog.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: blog
                port:
                  number: 80
  • pathType: Prefix matches on path segments, not raw string prefixes: /api does not match /apifoo. Use Exact when you mean an exact path.
  • The longest matching path wins, so order of rules does not matter.
  • TLS certificates are Secrets. Cert-manager can issue and renew them from an annotation, which removes the main operational chore.
  • A request that matches no rule goes to the controller's default backend and returns 404.
  • The Service behind an Ingress still needs working endpoints — an Ingress cannot route to pods that are not Ready.
kubectl get ingress
kubectl describe ingress storefront     # shows each rule and the resolved backend
kubectl get endpoints api               # an ingress with no endpoints returns 503

The Gateway API

The Gateway API splits one Ingress object into three roles: infrastructure teams own the GatewayClass and Gateway, application teams own HTTPRoute objects in their own namespace. Matching is typed instead of annotation-driven, so the same route means the same thing on every implementation.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gateway
  namespace: infra
spec:
  gatewayClassName: envoy
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      hostname: "*.example.com"
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
  namespace: storefront
spec:
  parentRefs:
    - name: shared-gateway
      namespace: infra
  hostnames: ["shop.example.com"]
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
        - headers:
            - name: x-canary
              value: "true"
      backendRefs:
        - name: api-canary
          port: 8080
          weight: 10
        - name: api
          port: 8080
          weight: 90
  • Header matching and traffic weighting are first-class in HTTPRoute, which is exactly what you want for canary releases.
  • A route is accepted or rejected per listener, and the status conditions on the route explain why. Read them before suspecting the proxy.
  • Gateway API ships TCPRoute, TLSRoute and GRPCRoute, so non-HTTP traffic has a typed answer too.
  • Adoption depends on the controller. Check that your implementation supports the version and features you need before migrating.
💡
Ingress annotations are controller-specific and do not port between nginx, Traefik and cloud load balancers. That is not a style preference: it means migrating a cluster can change routing behaviour. The Gateway API exists to move those knobs into typed fields, and it is worth the extra objects for anything long-lived.

FAQ

I created an Ingress and get 404. Where do I look?
First confirm a controller exists (kubectl get ingressclass) and that your Ingress names it. Then check the Service name and port, and that the Service has endpoints. Finally read the controller's own logs, which show the routing table it built.
Ingress or Gateway API for a new cluster?
Gateway API if your controller supports it and more than one team will publish routes, because the role split and typed matching prevent a lot of annotation archaeology. Ingress remains fine for a small single-team setup.

Networking, DNS and NetworkPolicy Cluster architecture and a local setup

Last refreshed 2026-09-18.