Exposing one service is easy. Exposing several HTTP services with TLS, hostnames, redirects, and path routing is where a plain Service starts to feel awkward. Ingress gives Kubernetes a single HTTP/HTTPS entry point and lets a controller do the real routing work.
Why Ingress?
Without Ingress, exposing multiple services requires multiple LoadBalancers (expensive) or NodePorts (limited):
Without Ingress:
┌─────────────────────────────────────────────────┐
│ External Traffic │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ LB $$$ │ │ LB $$$ │ │ LB $$$ │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Service │ │ Service │ │ Service │ │
│ │ API │ │ Web │ │ Admin │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────┘
Multiple LoadBalancers = High Cost
With Ingress:
┌─────────────────────────────────────────────────┐
│ External Traffic │
│ ┌─────────────────────────────────────┐ │
│ │ Single LoadBalancer │ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────────┐ │
│ │ Ingress Controller │ │
│ │ /api → API /web → Web /admin → │ │
│ └──────────────┬──────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Service │ │ Service │ │ Service │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────┘
Single entry point = Cost effective
Ingress Benefits
- Cost Efficiency: Single LoadBalancer for multiple services
- Centralized Routing: Path and host-based routing rules
- TLS Termination: Manage SSL certificates in one place
- Advanced Features: Rate limiting, authentication, rewrites
Ingress Components
1. Ingress Controller
The controller implements the Ingress rules. Popular options:
- NGINX Ingress Controller (most common)
- Traefik
- HAProxy
- AWS ALB Ingress Controller
- GCE Ingress Controller
2. Ingress Resource
The configuration that defines routing rules.
Installing NGINX Ingress Controller
# Using Helm
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
# Verify installation
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
Basic Ingress Configuration
Simple Path-Based Routing
# basic-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: webapp-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
Host-Based Routing
# host-based-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
namespace: production
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 3000
Path Types
| Type | Description | Example Match |
|---|---|---|
| Exact | Exact URL match | /api matches only /api |
| Prefix | URL prefix match | /api matches /api, /api/users |
| ImplementationSpecific | Controller-dependent | Varies |
# path-types-example.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-types-ingress
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Exact
backend:
service:
name: api-exact-service
port:
number: 8080
- path: /api/
pathType: Prefix
backend:
service:
name: api-prefix-service
port:
number: 8080
TLS/SSL Configuration
Basic TLS Setup
# tls-ingress.yaml
apiVersion: v1
kind: Secret
metadata:
name: webapp-tls
namespace: production
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTi... # Base64 encoded certificate
tls.key: LS0tLS1CRUdJTi... # Base64 encoded private key
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
- api.example.com
secretName: webapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
Automatic TLS with Cert-Manager
# cert-manager-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auto-tls-ingress
namespace: production
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls-auto
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
Advanced Annotations
NGINX Ingress Annotations
# advanced-annotations.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: advanced-ingress
namespace: production
annotations:
# SSL/TLS
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
# Timeouts
nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
# Body size
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
# Rate limiting
nginx.ingress.kubernetes.io/limit-rps: "100"
nginx.ingress.kubernetes.io/limit-connections: "10"
# CORS
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://example.com"
# Rewrites
nginx.ingress.kubernetes.io/rewrite-target: /$2
# Sticky sessions
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "route"
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /api(/|$)(.*)
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
URL Rewriting
# rewrite-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rewrite-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
# /api/users -> /users on backend
- path: /api(/|$)(.*)
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
Basic Authentication
# basic-auth-ingress.yaml
apiVersion: v1
kind: Secret
metadata:
name: basic-auth
namespace: production
type: Opaque
data:
# htpasswd -c auth admin
auth: YWRtaW46JGFwcjEkSDZ... # base64 encoded htpasswd
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth-ingress
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
spec:
ingressClassName: nginx
rules:
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 3000
Complete Production Example
# production-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: production-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- www.example.com
- api.example.com
- admin.example.com
secretName: production-tls
rules:
# Main website
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
# API
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-v1-service
port:
number: 8080
- path: /v2
pathType: Prefix
backend:
service:
name: api-v2-service
port:
number: 8080
# Admin panel
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 3000
Default Backend
Handle unmatched requests:
# default-backend-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-default
spec:
ingressClassName: nginx
defaultBackend:
service:
name: default-service
port:
number: 80
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
Troubleshooting
# Check Ingress status
kubectl get ingress -A
kubectl describe ingress webapp-ingress -n production
# Check Ingress Controller logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
# Check Ingress Controller service
kubectl get svc -n ingress-nginx
# Test DNS resolution
nslookup myapp.example.com
# Test connectivity
curl -v https://myapp.example.com
# Check TLS certificate
openssl s_client -connect myapp.example.com:443 -servername myapp.example.com
# Check backend service
kubectl get endpoints api-service -n production
Handy commands
# Ingress Management
kubectl get ingress -A
kubectl describe ingress webapp-ingress -n production
kubectl delete ingress webapp-ingress -n production
# Ingress Controller
kubectl get pods -n ingress-nginx
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
# TLS Secrets
kubectl create secret tls webapp-tls --cert=tls.crt --key=tls.key -n production
kubectl get secrets -n production | grep tls
# Testing
curl -H "Host: myapp.example.com" http://INGRESS_IP/
curl -k https://myapp.example.com/
What matters in practice
- Ingress provides HTTP/HTTPS routing to services
- Ingress Controller implements the routing rules
- Path-based and host-based routing supported
- TLS termination centralizes certificate management
- Annotations enable advanced features (rate limiting, auth, rewrites)
- Cert-manager automates TLS certificate provisioning
Where to go next
Once external access is under control, packaging becomes the next source of friction. That is where Helm comes in.