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

TypeDescriptionExample Match
ExactExact URL match/api matches only /api
PrefixURL prefix match/api matches /api, /api/users
ImplementationSpecificController-dependentVaries
# 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.

Further reading