Skip to main content

Ingress

An API object that manages external HTTP and HTTPS access to services inside a cluster. Ingress sits in front of multiple services and acts as a smart router — directing traffic based on hostnames, URL paths, or headers without exposing each service directly to the internet.

Ingress — The Cluster's Front Door

What Problem Does Ingress Solve?

Without Ingress, every service you want to expose externally needs its own LoadBalancer — and each LoadBalancer provisions a separate cloud IP. For Swiggy running 50+ microservices, that would mean 50 cloud IPs and 50 monthly billing line items.

Ingress fixes this with one entry point that routes smartly:

◈ DIAGRAM
+------------------------------------------+
| Internet |
+------------------------------------------+
|
v
+------------------------------------------+
| Ingress Controller (NGINX / Traefik) | <- One cloud LoadBalancer IP
+------------------------------------------+
| |
v v
+-----------------+ +------------------+
| /api/orders | | /api/payments |
| order-svc:80 | | payment-svc:80 |
+-----------------+ +------------------+

Core Components You Need to Know

Ingress Resource — The YAML configuration object where you define all routing rules.

Ingress Controller — The actual software process that reads those rules and enforces them. Common choices:

  • NGINX Ingress Controller (most widely used, works on any cluster)
  • Traefik (popular in self-managed clusters)
  • AWS ALB Ingress Controller (native for EKS clusters on AWS)
Remember

Kubernetes does NOT ship with an Ingress Controller by default. Creating an Ingress resource without a controller does absolutely nothing. You must install a controller separately before any Ingress rules take effect.

A Real Ingress Resource — Explained Line by Line

YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: razorpay-routing
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # Strips path prefix before forwarding to backend
spec:
ingressClassName: nginx # Which controller handles this Ingress
tls:
- hosts:
- api.razorpay.in
secretName: razorpay-tls-cert # TLS certificate stored as a Kubernetes Secret
rules:
- host: api.razorpay.in
http:
paths:
- path: /payments
pathType: Prefix # Matches /payments, /payments/upi, /payments/refund
backend:
service:
name: payment-service
port:
number: 80
- path: /accounts
pathType: Prefix
backend:
service:
name: account-service
port:
number: 80

Path Types — Common Confusion Point

Type Behavior Example
Prefix Matches any path starting with the value /api matches /api/v1, /api/users
Exact Must match the full path exactly /api only matches /api — not /api/v1
ImplementationSpecific Controller decides the matching behavior Avoid unless you know exactly what your controller does
Common Mistake

Using Exact when you meant Prefix causes mysterious 404s on sub-paths. "Why doesn't /payments/upi work?" — because your rule says Exact: /payments, which only matches the bare /payments path and nothing beneath it.

SSL Termination Flow

◈ DIAGRAM
+------------------------------------------+
| Browser sends HTTPS request | <- Encrypted TLS traffic
+------------------------------------------+
|
v
+------------------------------------------+
| Ingress Controller | <- Decrypts TLS using the Secret cert
| (handles TLS handshake) |
+------------------------------------------+
|
v
+------------------------------------------+
| Backend Pod receives plain HTTP | <- No TLS complexity in app code
+------------------------------------------+

The Ingress Controller handles all TLS handshakes. Your backend pods only see plain HTTP traffic — which significantly simplifies your application code and certificate management.

Multi-Host Routing — One Ingress, Multiple Domains

A single Ingress resource can handle multiple hostnames, which is how PhonePe routes across its microservices:

YAML
rules:
- host: api.phonepay.in
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-gateway
port:
number: 80
- host: admin.phonepay.in
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-portal
port:
number: 80

Quick Troubleshooting Commands

Scenario Command
List all Ingress rules across namespaces kubectl get ingress -A
Inspect routing config in detail kubectl describe ingress razorpay-routing -n production
Check Ingress Controller logs kubectl logs -n ingress-nginx deploy/ingress-nginx-controller
Verify TLS certificate is attached kubectl get secret razorpay-tls-cert -n production
Test path routing from inside cluster kubectl run curl --image=curlimages/curl -it --rm -- curl http://payment-service/payments
Security

Never expose the Kubernetes API server, internal dashboards (Kubernetes Dashboard, Argo CD), or monitoring UIs (Grafana) via Ingress without authentication middleware. NGINX Ingress supports nginx.ingress.kubernetes.io/auth-url and auth-signin annotations for OAuth2 proxy integration — use them on every internal tool exposed externally.

Tip

Add nginx.ingress.kubernetes.io/ssl-redirect: "true" as an annotation to automatically redirect all HTTP traffic to HTTPS. Without this, both HTTP and HTTPS routes work — a common compliance issue on Razorpay-scale fintech platforms that require encrypted-only traffic.

Frequently Asked Questions

What problem does Ingress solve that a plain Service of type LoadBalancer doesn't?

Giving every Service a LoadBalancer means provisioning a separate cloud load balancer per service — expensive and unwieldy once you have more than a handful. Ingress lets one external load balancer (fronting an Ingress controller like nginx or ALB) route to many services based on hostname or URL path, so `api.example.com` and `app.example.com` can share a single entry point while still hitting different backend Services internally.

Why does adding an Ingress resource alone often do nothing?

Ingress is just an API spec — it requires an Ingress controller actually running in the cluster to watch Ingress objects and configure the underlying proxy (nginx, Traefik, AWS Load Balancer Controller, etc.). A common mistake is writing Ingress YAML in a fresh cluster and expecting traffic to route, when no controller was ever installed. Also worth knowing: different controllers support different annotations, so Ingress manifests aren't fully portable between controller implementations.