Ingress
An Ingress exposes HTTP/HTTPS services to the outside world. Acts as a layer-7 reverse proxy: host/path routing, TLS termination, optional auth. Requires an Ingress Controller (nginx, traefik, AWS ALB, etc.) to actually serve traffic.
Resource + controller + TLS + best practices
EXAMPLE
# 1) Install an Ingress Controller (one per cluster)
# nginx-ingress (most common)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml
# Or via Helm
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace
# Other controllers:
# - Traefik (CRD-driven, modern)
# - HAProxy Ingress
# - AWS Load Balancer Controller (ALB / NLB)
# - GCE Ingress (on GKE)
# - Azure Application Gateway Ingress
# - Istio Gateway (service mesh)
# 2) Basic Ingress — host + path routing
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
# 3) Multiple hosts → multiple services
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: { name: multi-host }
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http: { paths: [{ path: /, pathType: Prefix, backend: { service: { name: api-svc, port: { number: 80 } } } }] }
- host: app.example.com
http: { paths: [{ path: /, pathType: Prefix, backend: { service: { name: web-svc, port: { number: 80 } } } }] }
- host: admin.example.com
http: { paths: [{ path: /, pathType: Prefix, backend: { service: { name: admin-svc, port: { number: 80 } } } }] }
# 4) Path-based routing on one host
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix
backend: { service: { name: api-svc, port: { number: 80 } } }
- path: /admin
pathType: Prefix
backend: { service: { name: admin-svc, port: { number: 80 } } }
- path: /
pathType: Prefix
backend: { service: { name: web-svc, port: { number: 80 } } }
# pathType:
# Prefix — matches if path starts with this prefix (most common)
# Exact — must match exactly
# ImplementationSpecific — controller decides
# 5) TLS — terminate at the ingress
apiVersion: v1
kind: Secret
metadata: { name: app-tls }
type: kubernetes.io/tls
data:
tls.crt: <base64-cert>
tls.key: <base64-key>
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-tls
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
spec:
ingressClassName: nginx
tls:
- hosts: [app.example.com]
secretName: app-tls
rules:
- host: app.example.com
http: { paths: [{ path: /, pathType: Prefix, backend: { service: { name: web, port: { number: 80 } } } }] }
# 6) cert-manager — automatic Let's Encrypt
# 1. Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
# 2. Define a ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata: { name: letsencrypt-prod }
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef: { name: letsencrypt-account-key }
solvers:
- http01: { ingress: { class: nginx } }
# 3. Annotate the Ingress; cert-manager creates + renews the cert automatically
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
# 7) Common annotations (nginx-ingress)
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/use-regex: 'true'
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
nginx.ingress.kubernetes.io/force-ssl-redirect: 'true'
nginx.ingress.kubernetes.io/proxy-body-size: '10m'
nginx.ingress.kubernetes.io/proxy-read-timeout: '60'
nginx.ingress.kubernetes.io/proxy-connect-timeout: '5'
nginx.ingress.kubernetes.io/limit-rps: '30' # rate limit
nginx.ingress.kubernetes.io/auth-type: 'basic'
nginx.ingress.kubernetes.io/auth-secret: 'basic-auth'
nginx.ingress.kubernetes.io/cors-allow-origin: 'https://app.example.com'
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Custom-Header: yes";
# 8) Sticky sessions
annotations:
nginx.ingress.kubernetes.io/affinity: 'cookie'
nginx.ingress.kubernetes.io/session-cookie-name: 'route'
nginx.ingress.kubernetes.io/session-cookie-expires: '172800'
nginx.ingress.kubernetes.io/session-cookie-max-age: '172800'
# 9) Canary releases — shift % traffic to a new version
# Production Ingress: app.example.com → web-v1 service
# Canary Ingress:
metadata:
name: app-canary
annotations:
nginx.ingress.kubernetes.io/canary: 'true'
nginx.ingress.kubernetes.io/canary-weight: '10' # 10% of traffic
spec:
rules:
- host: app.example.com
http: { paths: [{ path: /, pathType: Prefix, backend: { service: { name: web-v2, port: { number: 80 } } } }] }
# 10) Gateway API — the modern replacement for Ingress
# Promoted to GA in Kubernetes 1.29. Richer routing (HTTPRoute, TCPRoute, GRPCRoute), better multi-tenancy.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: app }
spec:
parentRefs: [{ name: example-gateway }]
hostnames: ['app.example.com']
rules:
- matches: [{ path: { type: PathPrefix, value: / } }]
backendRefs: [{ name: web, port: 80 }]
# Gateway API is portable across controllers + has clearer separation between
# 'infra owners' (Gateway) and 'app owners' (HTTPRoute).
# 11) Common controllers — quick guide
# nginx-ingress : most common; mature; rich annotation set
# Traefik : great defaults; native CRDs (IngressRoute)
# AWS ALB Controller : provisions an AWS ALB; tight cloud integration
# Istio Gateway : if you're already running a service mesh
# Cloudflare Tunnel : zero-config tunnel for non-cluster traffic
# Contour / Envoy : high-performance Envoy-based
# 12) Debug
kubectl get ingress
kubectl describe ingress app
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
kubectl get events -A --sort-by='.lastTimestamp' | head
curl -v -H 'Host: app.example.com' http://<ingress-lb-ip>/
# 13) Best practices
# • Use Gateway API for new clusters
# • Terminate TLS at the ingress; pod-internal traffic is plaintext (or use mTLS via mesh)
# • cert-manager for auto-issued + renewed Let's Encrypt certs
# • Set readiness probes on backend pods — ingress only routes to ready ones
# • One Ingress per app — easier to delete + reason about
# • Use NetworkPolicies to restrict who can call the backend service directly
# • Rate limit at the ingress; deeper limits at the app
# • Use external-dns to auto-create DNS records from Ingress hosts
# 14) Anti-patterns
# ❌ One mega-Ingress with 50 hosts — slow updates, hard to manage
# ❌ Multiple Ingress resources mounting overlapping paths — undefined behavior
# ❌ Forgetting ingressClassName — controller may not pick it up
# ❌ TLS cert renewed manually — use cert-manager
# ❌ Exposing internal admin endpoints via the same Ingress as user traffic — use a separate one with auth
Why it matters
Use cert-manager + Let’s Encrypt for free auto-renewed TLS — the days of forgetting to renew a cert are over. For new clusters, lean into Gateway API: clearer roles, richer routing, controller-portable.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
kind: Ingress
spec:
rules:
- host: api.example.com
http: { paths: [{ path: /, pathType: Prefix, backend: { service: { name: api, port: { number: 80 } } } }] }
Try it Yourself »
Discussion
Loading…