Understanding Ingress in Kubernetes

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

ClusterIPNodePortLoadBalancerIngress
AccessInternal onlyNode IP + portExternal IPExternal HTTP/HTTPS
L7 routing
TLS termination
Host/path routing
CostFreeFree1 LB per service1 LB total
Use caseService-to-serviceDev/debuggingTCP/UDP servicesHTTP apps

Basic Ingress Resource

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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

PathTypeBehaviorExample ruleMatchesDoes NOT match
ExactExact match only/api/api/api/, /api/users
PrefixMatches prefix/api/api, /api/, /api/users/apiv2
ImplementationSpecificController decidesvariesvariesvaries

TLS / HTTPS

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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

ControllerBest forMaintained by
NGINX IngressGeneral purpose, most popularKubernetes community
TraefikDynamic config, Let’s Encrypt built-inTraefik Labs
AGIC (App Gateway)AKS / Azure nativeMicrosoft
AWS ALBEKS / AWS nativeAWS
HAProxyHigh performanceHAProxy Tech
KongAPI gateway featuresKong
Istio GatewayService mesh environmentsIstio

Install NGINX Ingress Controller

# Via Helm (recommended)
helm repo add ingress-nginx \
https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.replicaCount=2 \
--set controller.nodeSelector."kubernetes\.io/os"=linux
# Verify
kubectl get pods -n ingress-nginx
kubectl 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/charts
helm install traefik traefik/traefik \
--namespace traefik \
--create-namespace

AGIC on AKS (Azure)

# Enable via AKS addon
az 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/v1
kind: Ingress
metadata:
name: ingress-with-default
spec:
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 classes
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx-public
annotations:
ingressclass.kubernetes.io/is-default-class: "true" # default
spec:
controller: k8s.io/ingress-nginx
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx-internal
spec:
controller: k8s.io/ingress-nginx
parameters:
apiGroup: k8s.nginx.org
kind: IngressClassParameters
name: internal-lb-params
# Public ingress — uses public LB
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: public-api
spec:
ingressClassName: nginx-public # ← public controller
rules:
- host: api.acme.com
...
---
# Internal ingress — uses internal LB
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: internal-admin
spec:
ingressClassName: nginx-internal # ← internal controller
rules:
- host: admin.internal.acme.com
...

Real-World Production Setup

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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.

IngressGateway API
StabilityStable (v1)Stable (v1 from K8s 1.28)
L7 routingBasicAdvanced
TCP/UDP routing
Traffic splittingAnnotation-basedNative
Multi-teamLimitedRole-based (Gateway vs Route)
Header manipulationAnnotation-basedNative
FutureMaintenance modeActively developed
# Gateway API equivalent
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
spec:
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 created
kubectl get ingress -n production
# Check ingress details and events
kubectl describe ingress production-ingress -n production
# Check ingress controller logs
kubectl logs -n ingress-nginx \
deployment/ingress-nginx-controller -f
# Check if backend service exists and has endpoints
kubectl get svc api-service -n production
kubectl get endpoints api-service -n production
# Test routing from inside cluster
kubectl run test --image=curlimages/curl -it --rm -- \
curl -H "Host: api.acme.com" http://ingress-nginx-controller.ingress-nginx
# Check TLS certificate
kubectl 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.

Leave a Reply