A type: LoadBalancer Service gives you one external IP per Service — expensive and unmanageable at scale. Ingress solves this: a single entry point (one IP/LB) that routes HTTP/HTTPS traffic to multiple backend Services based on hostname and path — like a virtual host configuration.

1. How Ingress Works

Ingress has two components:

  1. Ingress resource — a declarative YAML describing routing rules (host, path → backend Service)
  2. Ingress Controller — the actual software (nginx, envoy, traefik) that reads the rules and routes traffic

The Ingress resource is useless without a controller. Creating an Ingress object does nothing unless a controller is watching for it.

Client Cloud LB External IP Ingress Controller (nginx / envoy) L7 routing: host + path TLS termination api-svc → Pods web-svc → Pods blog-svc → Pods api.example.com/v1 → api-svc example.com/ → web-svc
Ingress = L7 (HTTP) routing. Unlike Services (L4: IP + port), Ingress inspects HTTP headers — hostname, path, sometimes headers/cookies. This enables: multiple apps on one IP, path-based routing, TLS termination, and virtual hosting. It only works for HTTP/HTTPS traffic.

2. Ingress Resource

Simple Fanout (Path-Based Routing)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /    # controller-specific
spec:
  ingressClassName: nginx                             # which controller handles this
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-svc
                port:
                  number: 80

Name-Based Virtual Hosting

spec:
  rules:
    - host: api.example.com           # Route by hostname
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 80
    - host: blog.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: blog-svc
                port:
                  number: 80

Path Types

pathTypeMatchingExample
PrefixMatches URL path prefix (by path segment)/api matches /api, /api/, /api/v1
ExactMatches exactly/api matches only /api, not /api/
ImplementationSpecificUp to the controllerDepends on nginx/envoy config
Prefix matching is per-segment: /api matches /api and /api/v1 but NOT /apikeys (different segment boundary). This is stricter than simple string prefix matching. The / separates segments.

Default Backend

spec:
  defaultBackend:                   # Handles requests that match no rule
    service:
      name: default-svc
      port:
        number: 80
  rules:
    - ...                          # Specific rules
CKA/CKAD exam: generate Ingress quickly with imperative command:
kubectl create ingress app --rule="app.example.com/api=api-svc:80" --rule="app.example.com/=web-svc:80" --dry-run=client -o yaml

3. TLS Termination

Ingress handles HTTPS by terminating TLS at the controller. Traffic between controller → backend Pods is typically unencrypted (within the cluster).

Setup

# 1. Create a TLS Secret:
kubectl create secret tls app-tls \
  --cert=./tls.crt \
  --key=./tls.key

# 2. Reference it in the Ingress:
spec:
  tls:
    - hosts:
        - app.example.com         # Must match a rule host
        - api.example.com
      secretName: app-tls         # TLS Secret in same namespace
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-svc
                port:
                  number: 80

TLS Modes

ModeDescriptionConfig
Terminate at IngressClient → HTTPS → Controller → HTTP → PodDefault (spec.tls with Secret)
PassthroughClient → HTTPS → Controller passes encrypted to PodAnnotation: ssl-passthrough: "true"
Re-encryptClient → HTTPS → Controller → HTTPS → PodController-specific backend TLS annotations
In production, use cert-manager to automatically provision and rotate TLS certificates from Let's Encrypt (or internal CA). cert-manager creates the TLS Secret for you and renews before expiry. Annotate the Ingress: cert-manager.io/cluster-issuer: letsencrypt-prod and it handles everything.

HTTP → HTTPS Redirect

# Most controllers support forced redirect:
metadata:
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"     # NGINX
    # or for other controllers:
    # traefik.ingress.kubernetes.io/redirect-scheme: https

4. Ingress Controllers

The Ingress resource is just a spec. The controller is the implementation. You must install one — nothing happens without it.

ControllerProxyStrengthsManaged By
ingress-nginxNGINXMost popular, battle-tested, extensive annotationsK8s community
TraefikTraefikAuto-discovery, middleware chain, Let's Encrypt built-inTraefik Labs
ContourEnvoyHTTPProxy CRD (richer than Ingress), multi-team delegationVMware/Heptio
EmissaryEnvoyAPI Gateway features, rate limiting, authAmbassador Labs
AWS ALB ControllerAWS ALBNative AWS integration, no in-cluster proxy PodsAWS
GKE IngressGoogle Cloud LBNative GCP, global HTTPS LBGoogle

IngressClass

When multiple controllers exist in a cluster, ingressClassName determines which one handles a given Ingress:

# IngressClass resource (installed by the controller):
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"  # default if none specified
spec:
  controller: k8s.io/ingress-nginx

# Reference in Ingress:
spec:
  ingressClassName: nginx

NGINX vs Envoy-Based Controllers

AspectNGINX (ingress-nginx)Envoy (Contour, Emissary)
Config reloadReload nginx process (brief connection drain)Hot config via xDS API (zero-drop)
L7 featuresVia annotations (100+ supported)Via CRDs (HTTPProxy, Mapping)
ObservabilityAccess logs, basic metricsRich L7 metrics, distributed tracing headers
PerformanceExcellent for most workloadsBetter at high connection counts, HTTP/2
CommunityLargest user base, most StackOverflow answersGrowing, favored by platform teams
NGINX reloads config by restarting workers. When an Ingress or backend changes, ingress-nginx regenerates the config and reloads — causing a brief disruption (~100ms) on active connections. Envoy-based controllers push config via gRPC (xDS) — truly zero-downtime updates. At very high traffic, this difference matters.
Most teams start with ingress-nginx (simple, well-documented). Switch to Envoy-based when you need: advanced traffic splitting, gRPC routing, per-route rate limiting, or you're hitting config-reload issues at high scale. The Gateway API (next lesson) is the future standard that works across all controllers.

5. Common NGINX Ingress Annotations

AnnotationPurposeExample Value
nginx.ingress.kubernetes.io/rewrite-targetRewrite URL path before forwarding/ or /$2
nginx.ingress.kubernetes.io/ssl-redirectForce HTTPS"true"
nginx.ingress.kubernetes.io/proxy-body-sizeMax request body"50m"
nginx.ingress.kubernetes.io/proxy-read-timeoutBackend read timeout"60"
nginx.ingress.kubernetes.io/use-regexEnable regex in path"true"
nginx.ingress.kubernetes.io/affinitySession stickiness"cookie"
nginx.ingress.kubernetes.io/limit-rpsRate limiting (requests/sec)"10"
nginx.ingress.kubernetes.io/auth-urlExternal auth (forward auth)URL of auth service

Rewrite Example

# Route /api/v1/users → backend receives /users
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /api(/|$)(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: api-svc
                port:
                  number: 80
Annotations are controller-specific and NOT portable. If you switch from nginx to Traefik, all annotations must change. This is one of the reasons the Gateway API (next lesson) was created — it provides a standard spec that works across controllers without annotations.

Summary

ConceptKey Point
Ingress resourceDeclarative routing rules (host + path → Service)
Ingress ControllerActual proxy that implements the rules (must be installed)
IngressClassSelects which controller handles an Ingress
Path typesPrefix (per-segment), Exact, ImplementationSpecific
TLSReference a kubernetes.io/tls Secret in spec.tls
AnnotationsController-specific features (rewrite, rate limit, auth) — not portable
NGINX vs EnvoyNGINX: simple, proven. Envoy: hot-reload, richer L7, better at scale.
cert-managerAutomates TLS cert provisioning from Let's Encrypt

📝 Quiz: Ingress & Ingress Controllers

Q1: You create an Ingress resource but it doesn't work — no traffic is routed. What's likely missing?

An Ingress Controller is not installed. The Ingress resource is just a configuration object — without a controller watching for it and implementing the routing, nothing happens. Install one (e.g., kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.0/deploy/static/provider/cloud/deploy.yaml).

Q2: What's the difference between pathType: Prefix with path /api matching /api vs /apikeys?

/api Prefix matches /api, /api/, and /api/v1 but NOT /apikeys. Prefix matching works per path segment (separated by /). /apikeys is a different segment — it doesn't start with /api/.

Q3: Your Ingress has TLS configured for app.example.com. A client connects via HTTP. What happens?

Depends on configuration. By default, many controllers serve both HTTP and HTTPS. To force HTTPS redirect, add: nginx.ingress.kubernetes.io/ssl-redirect: "true". Without this annotation, the HTTP request may be served unencrypted (controller-dependent behavior).

Q4: You have two Ingress controllers: nginx and traefik. How does a new Ingress know which controller should handle it?

Set spec.ingressClassName to the name of the IngressClass that corresponds to the desired controller (e.g., ingressClassName: nginx or ingressClassName: traefik). If not set, the controller marked with ingressclass.kubernetes.io/is-default-class: "true" handles it.

Q5: Why might you choose an Envoy-based Ingress controller over NGINX?

Key reasons: (1) Zero-downtime config updates — Envoy uses xDS (hot config push) while NGINX reloads workers. (2) Better gRPC and HTTP/2 support. (3) Richer L7 observability (per-route metrics, distributed tracing). (4) Advanced traffic management (traffic splitting, retries, circuit breaking via CRDs rather than annotations).

Q6: What's the main limitation of the Ingress API that led to the creation of the Gateway API?

Annotations for everything. The Ingress spec is too simple — it only covers basic host/path routing and TLS. Anything beyond that (rewriting, rate limiting, auth, traffic splitting, header matching) requires controller-specific annotations that aren't portable. The Gateway API provides a richer, standardized spec with typed resources for these features.