Ingress in Kubernetes
What is Ingress?
Ingress is a Kubernetes API object that manages external HTTP/HTTPS access to services inside a cluster. It acts as a smart entry point — routing traffic based on hostnames, paths, and rules.
WITHOUT Ingress: WITH Ingress: Internet Internet │ │ ├──▶ LoadBalancer (Service A) │ ├──▶ LoadBalancer (Service B) ┌───▼────────────────┐ ├──▶ LoadBalancer (Service C) │ Single Ingress │ └──▶ LoadBalancer (Service D) │ (1 LoadBalancer) │ (4 cloud LBs = 4x cost) └───┬────────────────┘ │ ┌────────┼────────┐ ▼ ▼ ▼ Service A Service B Service C
One entry point, many services — saves cost and complexity.
Core Components
┌─────────────────────────────────────────────────────────┐│ KUBERNETES CLUSTER ││ ││ ┌─────────────────────────────────────────────────┐ ││ │ INGRESS CONTROLLER │ ││ │ (NGINX / Traefik / AGIC / HAProxy) │ ││ │ - Watches Ingress resources │ ││ │ - Configures the actual proxy/LB │ ││ │ - Handles TLS termination │ ││ └──────────────────────┬──────────────────────────┘ ││ │ reads ││ ┌──────────────────────▼──────────────────────────┐ ││ │ INGRESS RESOURCE │ ││ │ - Rules (host + path → service) │ ││ │ - TLS config │ ││ │ - Annotations (controller-specific settings) │ ││ └──────────────────────┬──────────────────────────┘ ││ │ routes to ││ ┌──────────┼──────────┐ ││ ▼ ▼ ▼ ││ Service A Service B Service C ││ │ │ │ ││ Pod(s) Pod(s) Pod(s) │└─────────────────────────────────────────────────────────┘ ▲ LoadBalancer Service (single external IP)
Ingress vs Other Networking Types
| ClusterIP | NodePort | LoadBalancer | Ingress | |
|---|---|---|---|---|
| Access | Internal only | Node IP + port | External IP | External HTTP/HTTPS |
| L7 routing | ❌ | ❌ | ❌ | ✅ |
| TLS termination | ❌ | ❌ | ❌ | ✅ |
| Host/path routing | ❌ | ❌ | ❌ | ✅ |
| Cost | Free | Free | 1 LB per service | 1 LB total |
| Use case | Service-to-service | Dev/debugging | TCP/UDP services | HTTP apps |
Basic Ingress Resource
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: basic-ingress namespace: production annotations: nginx.ingress.kubernetes.io/rewrite-target: /spec: ingressClassName: nginx # ← which controller handles this rules: - host: api.acme.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 80
Routing Patterns
Host-Based Routing
Different domains → different services:
spec: rules: # api.acme.com → api-service - host: api.acme.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 80 # app.acme.com → frontend-service - host: app.acme.com http: paths: - path: / pathType: Prefix backend: service: name: frontend-service port: number: 3000 # admin.acme.com → admin-service - host: admin.acme.com http: paths: - path: / pathType: Prefix backend: service: name: admin-service port: number: 8080
Path-Based Routing
Same domain, different paths → different services:
spec: rules: - host: acme.com http: paths: # acme.com/api/* → api-service - path: /api pathType: Prefix backend: service: name: api-service port: number: 80 # acme.com/auth/* → auth-service - path: /auth pathType: Prefix backend: service: name: auth-service port: number: 80 # acme.com/ → frontend - path: / pathType: Prefix backend: service: name: frontend-service port: number: 3000
Combined Host + Path Routing
spec: rules: - host: api.acme.com http: paths: - path: /v1 pathType: Prefix backend: service: name: api-v1-service port: number: 80 - path: /v2 pathType: Prefix backend: service: name: api-v2-service port: number: 80
Path Types Explained
| PathType | Behavior | Example rule | Matches | Does NOT match |
|---|---|---|---|---|
Exact | Exact match only | /api | /api | /api/, /api/users |
Prefix | Matches prefix | /api | /api, /api/, /api/users | /apiv2 |
ImplementationSpecific | Controller decides | varies | varies | varies |
TLS / HTTPS
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: tls-ingress namespace: production annotations: # Auto-provision cert via cert-manager cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/ssl-redirect: "true"spec: ingressClassName: nginx # TLS config tls: - hosts: - api.acme.com - app.acme.com secretName: acme-tls-secret # cert stored here by cert-manager rules: - host: api.acme.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 80 - host: app.acme.com http: paths: - path: / pathType: Prefix backend: service: name: frontend-service port: number: 3000
TLS flow:
Client ──HTTPS──▶ Ingress Controller (terminates TLS) │ plain HTTP │ ▼ Backend Service
Ingress Controllers
The Ingress resource is just config — you need a controller to actually implement it.
Popular Controllers
| Controller | Best for | Maintained by |
|---|---|---|
| NGINX Ingress | General purpose, most popular | Kubernetes community |
| Traefik | Dynamic config, Let’s Encrypt built-in | Traefik Labs |
| AGIC (App Gateway) | AKS / Azure native | Microsoft |
| AWS ALB | EKS / AWS native | AWS |
| HAProxy | High performance | HAProxy Tech |
| Kong | API gateway features | Kong |
| Istio Gateway | Service mesh environments | Istio |
Install NGINX Ingress Controller
# Via Helm (recommended)helm repo add ingress-nginx \ https://kubernetes.github.io/ingress-nginxhelm repo updatehelm install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx \ --create-namespace \ --set controller.replicaCount=2 \ --set controller.nodeSelector."kubernetes\.io/os"=linux# Verifykubectl get pods -n ingress-nginxkubectl get svc -n ingress-nginx# NAME TYPE EXTERNAL-IP# ingress-nginx-controller LoadBalancer 20.10.5.100 ← your entry point
Install Traefik
helm repo add traefik https://traefik.github.io/chartshelm install traefik traefik/traefik \ --namespace traefik \ --create-namespace
AGIC on AKS (Azure)
# Enable via AKS addonaz aks enable-addons \ --resource-group myRG \ --name myAKSCluster \ --addons ingress-appgw \ --appgw-name myAppGateway \ --appgw-subnet-cidr "10.2.0.0/16"
NGINX Annotations (Most Common)
Annotations let you configure controller-specific behaviour:
metadata: annotations: # Redirect HTTP → HTTPS nginx.ingress.kubernetes.io/ssl-redirect: "true" # Rewrite path before forwarding nginx.ingress.kubernetes.io/rewrite-target: /$2 # Rate limiting nginx.ingress.kubernetes.io/limit-rps: "10" nginx.ingress.kubernetes.io/limit-connections: "5" # Timeouts nginx.ingress.kubernetes.io/proxy-connect-timeout: "10" nginx.ingress.kubernetes.io/proxy-read-timeout: "60" nginx.ingress.kubernetes.io/proxy-send-timeout: "60" # Body size limit nginx.ingress.kubernetes.io/proxy-body-size: "10m" # CORS nginx.ingress.kubernetes.io/enable-cors: "true" nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.acme.com" # Whitelist IPs nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8" # Auth nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: basic-auth-secret # Custom headers nginx.ingress.kubernetes.io/configuration-snippet: | add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff";
Default Backend
Handles requests that don’t match any rule:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: ingress-with-defaultspec: ingressClassName: nginx # Catch-all — shown when no rule matches defaultBackend: service: name: custom-404-service port: number: 80 rules: - host: api.acme.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 80
Multiple Ingress Controllers
You can run multiple controllers in one cluster using IngressClass:
# Define two ingress classesapiVersion: networking.k8s.io/v1kind: IngressClassmetadata: name: nginx-public annotations: ingressclass.kubernetes.io/is-default-class: "true" # defaultspec: controller: k8s.io/ingress-nginx---apiVersion: networking.k8s.io/v1kind: IngressClassmetadata: name: nginx-internalspec: controller: k8s.io/ingress-nginx parameters: apiGroup: k8s.nginx.org kind: IngressClassParameters name: internal-lb-params
# Public ingress — uses public LBapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: public-apispec: ingressClassName: nginx-public # ← public controller rules: - host: api.acme.com ...---# Internal ingress — uses internal LBapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: internal-adminspec: ingressClassName: nginx-internal # ← internal controller rules: - host: admin.internal.acme.com ...
Real-World Production Setup
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: production-ingress namespace: production annotations: # TLS cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/ssl-redirect: "true" # Security headers nginx.ingress.kubernetes.io/configuration-snippet: | add_header Strict-Transport-Security "max-age=31536000" always; add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; # Rate limiting nginx.ingress.kubernetes.io/limit-rps: "50" # Timeouts nginx.ingress.kubernetes.io/proxy-read-timeout: "30" nginx.ingress.kubernetes.io/proxy-send-timeout: "30" # Body size nginx.ingress.kubernetes.io/proxy-body-size: "5m"spec: ingressClassName: nginx tls: - hosts: - acme.com - api.acme.com - app.acme.com secretName: acme-wildcard-tls rules: - host: acme.com http: paths: - path: / pathType: Prefix backend: service: name: frontend port: number: 3000 - host: api.acme.com http: paths: - path: /v1 pathType: Prefix backend: service: name: api-v1 port: number: 8080 - path: /v2 pathType: Prefix backend: service: name: api-v2 port: number: 8080 - host: app.acme.com http: paths: - path: / pathType: Prefix backend: service: name: webapp port: number: 3000
Ingress vs Gateway API
Kubernetes introduced Gateway API as the next generation of Ingress — more expressive and powerful.
| Ingress | Gateway API | |
|---|---|---|
| Stability | Stable (v1) | Stable (v1 from K8s 1.28) |
| L7 routing | Basic | Advanced |
| TCP/UDP routing | ❌ | ✅ |
| Traffic splitting | Annotation-based | Native |
| Multi-team | Limited | Role-based (Gateway vs Route) |
| Header manipulation | Annotation-based | Native |
| Future | Maintenance mode | Actively developed |
# Gateway API equivalentapiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: api-routespec: parentRefs: - name: main-gateway hostnames: - api.acme.com rules: - matches: - path: type: PathPrefix value: /v2 backendRefs: - name: api-v2 port: 8080 weight: 90 - name: api-v2-canary port: 8080 weight: 10 # ← native traffic splitting, no annotations
Troubleshooting
# Check ingress is createdkubectl get ingress -n production# Check ingress details and eventskubectl describe ingress production-ingress -n production# Check ingress controller logskubectl logs -n ingress-nginx \ deployment/ingress-nginx-controller -f# Check if backend service exists and has endpointskubectl get svc api-service -n productionkubectl get endpoints api-service -n production# Test routing from inside clusterkubectl run test --image=curlimages/curl -it --rm -- \ curl -H "Host: api.acme.com" http://ingress-nginx-controller.ingress-nginx# Check TLS certificatekubectl describe certificate acme-tls-secret -n production# Common issues checklist# ❌ 404 — path not matching, check pathType# ❌ 502 — backend pod not running or wrong port# ❌ 503 — no healthy endpoints# ❌ SSL error — cert not ready, check cert-manager# ❌ No address — ingress controller not installed
Summary
User Request │ ▼DNS (api.acme.com → Ingress LB IP) │ ▼Ingress Controller (NGINX / Traefik / AGIC) │ reads Ingress resources │ terminates TLS │ matches host + path rules │ ├──▶ /api → api-service → pods ├──▶ /auth → auth-service → pods └──▶ / → frontend → pods
Ingress is the front door of your Kubernetes cluster — one external IP, intelligent routing, TLS termination, and full control over how traffic reaches your services.