Learning Kubernetes - Introduction and Explanation of Ingress
Episode 19 of 47

Learning Kubernetes - Introduction and Explanation of Ingress

In this episode, we'll discuss Kubernetes Ingress, the API object for managing external HTTP/HTTPS access to services. We'll learn about Ingress controllers, routing rules, TLS termination, and best practices for exposing applications.

Arman Dwi Pangestu
Arman Dwi PangestuMarch 22, 2026
0 views
11 min read

Introduction

In the previous episode, we learned about Service for exposing and accessing applications within and outside the cluster. In episode 19, we'll discuss Ingress, which provides sophisticated HTTP/HTTPS routing to Services.

Note: Here I'll be using a Kubernetes Cluster installed through K3s.

While Services can expose applications using LoadBalancer or NodePort, Ingress provides a more powerful and flexible way to manage external access. Ingress acts as a smart HTTP router, enabling features like host-based routing, path-based routing, TLS termination, and more - all with a single external IP.

What Is Ingress?

Ingress is a Kubernetes API object that manages external HTTP and HTTPS access to Services in a cluster. It provides HTTP routing rules to direct traffic to different Services based on hostnames, paths, and other criteria.

Think of Ingress like a reverse proxy or API gateway - it sits at the edge of your cluster, receives external HTTP/HTTPS traffic, and routes it to the appropriate Services based on rules you define. Instead of creating multiple LoadBalancers (one per Service), you use a single Ingress with routing rules.

Key characteristics of Ingress:

  • HTTP/HTTPS routing - Routes traffic based on host and path
  • Single entry point - One external IP for multiple Services
  • TLS termination - Handles SSL/TLS certificates
  • Name-based virtual hosting - Multiple domains on one IP
  • Path-based routing - Different paths to different Services
  • Load balancing - Distributes traffic across Service endpoints
  • Requires Ingress Controller - Needs controller implementation

Ingress vs Service

Let's understand the key differences:

AspectIngressLoadBalancer Service
LayerLayer 7 (HTTP/HTTPS)Layer 4 (TCP/UDP)
RoutingHost and path-basedPort-based only
External IPsOne IP for many ServicesOne IP per Service
TLSBuilt-in TLS terminationRequires external setup
CostSingle load balancerMultiple load balancers
ProtocolHTTP/HTTPS onlyAny TCP/UDP protocol

Example scenario:

  • Ingress: Route api.example.com to API Service, web.example.com to Web Service - one IP
  • LoadBalancer: Each Service gets its own external IP and load balancer

Ingress Controller

Ingress resources don't work alone - they require an Ingress Controller to function.

What Is Ingress Controller?

An Ingress Controller is a specialized load balancer that reads Ingress resources and implements the routing rules. It's the actual component that handles traffic.

Popular Ingress Controllers:

  • NGINX Ingress Controller - Most popular, feature-rich
  • Traefik - Modern, dynamic configuration
  • HAProxy Ingress - High performance
  • Kong - API gateway features
  • Istio Gateway - Service mesh integration
  • AWS ALB Ingress - AWS Application Load Balancer
  • GCE Ingress - Google Cloud Load Balancer

Important

Important: You must install an Ingress Controller before Ingress resources will work. Kubernetes doesn't include one by default.

Installing NGINX Ingress Controller

For K3s (comes with Traefik by default):

Important

If you're using K3s, it's already default running Ingress Controller using Traefik like this:

KubernetesTraefik Ingress Controller
  devnull@devnull ~ sudo kubectl get svc -n kube-system   
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
kube-dns         ClusterIP      10.43.0.10      <none>        53/UDP,53/TCP,9153/TCP       121d
metrics-server   ClusterIP      10.43.122.234   <none>        443/TCP                      121d
traefik          LoadBalancer   10.43.230.117   10.10.10.4    80:31009/TCP,443:30158/TCP   121d
 
  devnull@devnull ~ sudo kubectl get ingressclass
NAME      CONTROLLER                      PARAMETERS   AGE
traefik   traefik.io/ingress-controller   <none>       121d
 
  devnull@devnull ~ sudo kubectl describe ingressclass traefik
Name:         traefik
Labels:       app.kubernetes.io/instance=traefik-kube-system
          app.kubernetes.io/managed-by=Helm
          app.kubernetes.io/name=traefik
          helm.sh/chart=traefik-34.2.1_up34.2.0
Annotations:  ingressclass.kubernetes.io/is-default-class: true
          meta.helm.sh/release-name: traefik
          meta.helm.sh/release-namespace: kube-system
Controller:   traefik.io/ingress-controller
Events:       <none>
Kubernetesbash
# K3s includes Traefik, but you can install NGINX
sudo kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/cloud/deploy.yaml

For other clusters:

Kubernetesbash
sudo kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/baremetal/deploy.yaml

Verify installation:

Kubernetesbash
sudo kubectl get pods -n ingress-nginx

Creating Basic Ingress

Let's create a simple Ingress to expose a Service.

Step 1: Create Deployment and Service

Kubernetesweb-app.yml
apiVersion: apps/v1
kind: Deployment
metadata:
    name: web-app
spec:
    replicas: 3
    selector:
        matchLabels:
            app: web
    template:
        metadata:
            labels:
                app: web
        spec:
            containers:
                - name: app-hostname
                  image: ghcr.io/armandwipangestu/app-hostname:1.0.0
                  ports:
                      - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
    name: web-service
spec:
    selector:
        app: web
    ports:
        - port: 80
          targetPort: 80

Apply:

Kubernetesbash
sudo kubectl apply -f web-app.yml

Step 2: Create Ingress

Kubernetesbasic-ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: web-ingress
spec:
    rules:
        - host: web.example.com
          http:
              paths:
                  - path: /
                    pathType: Prefix
                    backend:
                        service:
                            name: web-service
                            port:
                                number: 80

Apply:

Kubernetesbash
sudo kubectl apply -f basic-ingress.yml

Verify:

Kubernetesbash
sudo kubectl get ingress

Output:

Kubernetesbash
NAME          CLASS    HOSTS              ADDRESS         PORTS   AGE
web-ingress   <none>   web.example.com    203.0.113.10    80      30s

Now traffic to web.example.com routes to web-service.

Tip

If you want to access web.example.com, you can add static record on /etc/hosts that mapping to NodeIP, for example

Linuxbash
10.10.10.4 web.example.com

After that tried to access with curl like this

Linuxbash
curl http://web.example.com

Or if you don't want to used /etc/hosts, you can change directly the header from curl like this

Linuxbash
curl -H "Host: web.example.com" http://10.10.10.4

Then you will get response from Deployment Pod like this

Linuxbash
Hello from pod: web-app-5f9556ff68-frrlb

Path-Based Routing

Route different paths to different Services.

Example: Multiple Services

Kubernetespath-based-ingress.yml
# ================================
# API Service
# ================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-service
  template:
    metadata:
      labels:
        app: api-service
    spec:
      containers:
        - name: api-service
          image: ghcr.io/armandwipangestu/api-service:1.0.1
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: api-service
spec:
  selector:
    app: api-service
  ports:
    - port: 80
      targetPort: 3000
---
# ================================
# Web Service
# ================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-service
  template:
    metadata:
      labels:
        app: web-service
    spec:
      containers:
        - name: web-service
          image: ghcr.io/armandwipangestu/web-service:1.0.1
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web-service
  ports:
    - port: 80
      targetPort: 3000
---
# ================================
# Frontend Service
# ================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend-service
  template:
    metadata:
      labels:
        app: frontend-service
    spec:
      containers:
        - name: frontend-service
          image: ghcr.io/armandwipangestu/frontend-service:1.0.1
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  selector:
    app: frontend-service
  ports:
    - port: 80
      targetPort: 3000
---
# ================================
# Ingress
# ================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
spec:
  rules:
    - host: example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
          - path: /web
            pathType: Prefix
            backend:
              service:
                name: web-service
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

This routes:

  • example.com/api/* → api-service
  • example.com/web/* → web-service
  • example.com/* → frontend-service

Host-Based Routing

Route different hostnames to different Services.

Example: Multiple Hosts

Kuberneteshost-based-ingress.yml
# ================================
# API Service
# ================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-service
  template:
    metadata:
      labels:
        app: api-service
    spec:
      containers:
        - name: api-service
          image: ghcr.io/armandwipangestu/api-service:1.0.1
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: api-service
spec:
  selector:
    app: api-service
  ports:
    - port: 80
      targetPort: 3000
---
# ================================
# Web Service
# ================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-service
  template:
    metadata:
      labels:
        app: web-service
    spec:
      containers:
        - name: web-service
          image: ghcr.io/armandwipangestu/web-service:1.0.1
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web-service
  ports:
    - port: 80
      targetPort: 3000
---
# ================================
# Frontend Service
# ================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend-service
  template:
    metadata:
      labels:
        app: frontend-service
    spec:
      containers:
        - name: frontend-service
          image: ghcr.io/armandwipangestu/frontend-service:1.0.1
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  selector:
    app: frontend-service
  ports:
    - port: 80
      targetPort: 3000
---
# ================================
# Ingress
# ================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-host-ingress
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
    - host: web.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: frontend-service
                port:
                  number: 80

This routes:

  • api.example.com → api-service
  • web.example.com → web-service
  • admin.example.com → admin-service

Path Types

Ingress supports three path types:

Prefix

Matches based on URL path prefix:

Kubernetesyml
pathType: Prefix
path: /api

Matches: /api, /api/users, /api/v1/users

Exact

Matches exact path only:

Kubernetesyml
pathType: Exact
path: /api

Matches: /api only Doesn't match: /api/, /api/users

ImplementationSpecific

Depends on Ingress Controller implementation:

Kubernetesyml
pathType: ImplementationSpecific
path: /api

Behavior varies by controller.

TLS/HTTPS Configuration

Enable HTTPS with TLS certificates.

Step 1: Generate Self-Signed Certificate

Linuxbash
mkdir cert-self-signed
nvim cert-self-signed/openssl.cnf

After that add configuration like this to the file openssl.cnf

openssl.cnf
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext
 
[dn]
CN = example.com
 
[req_ext]
subjectAltName = @alt_names
 
[alt_names]
DNS.1 = example.com
DNS.2 = *.example.com

Next generate using this command

Linuxbash
openssl req -x509 -nodes -days 10365 \
    -newkey rsa:2048 \
    -keyout cert-self-signed/tls.key \
    -out cert-self-signed/tls.crt \
    -config cert-self-signed/openssl.cnf \
    -extensions req_ext

Step 2: Create TLS Secret from Self-Signed Certificate

Kubernetesbash
sudo kubectl create secret tls example-tls \       
    --cert=cert-self-signed/tls.crt \
    --key=cert-self-signed/tls.key

Step 3: Create Deployment and Ingress with TLS

Kubernetestls-ingress.yml
# ================================
# Web Service
# ================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-service
  template:
    metadata:
      labels:
        app: web-service
    spec:
      containers:
        - name: web-service
          image: ghcr.io/armandwipangestu/web-service:1.0.1
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web-service
  ports:
    - port: 80
      targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-ingress
spec:
  tls:
    - hosts:
        - example.com
        - www.example.com
      secretName: example-tls
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-service
                port:
                  number: 80

Step 4: Run and Test HTTPS Request

Kubernetesbash
sudo kubectl apply -f tls-ingress.yml

Tip

HTTPS Request if you want valid used cert you should used SNI or Server Name Indication, because if change HTTP Header like curl request before using this

Linuxbash
curl -H "Host: example.com" https://10.10.10.4 -k -v

That will not work and it will be fallback to default cert K3s which is from Traefik (if used default Ingress Controller on K3s), because that HTTP Header happen after process TLS Handshake finish.

Linuxbash
curl -k --resolve example.com:443:10.10.10.4 https://example.com -v

Annotations

Customize Ingress behavior with annotations (controller-specific).

NGINX Ingress Annotations

Kubernetesannotated-ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: annotated-ingress
    annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /
        nginx.ingress.kubernetes.io/ssl-redirect: "true"
        nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
        nginx.ingress.kubernetes.io/proxy-body-size: "50m"
        nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
        nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
        nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
spec:
    rules:
        - host: example.com
          http:
              paths:
                  - path: /api
                    pathType: Prefix
                    backend:
                        service:
                            name: api-service
                            port:
                                number: 8080

Common annotations:

  • rewrite-target: Rewrite URL path
  • ssl-redirect: Redirect HTTP to HTTPS
  • proxy-body-size: Max request body size
  • rate-limit: Rate limiting
  • whitelist-source-range: IP whitelisting

Default Backend

Specify a default Service for unmatched requests.

Kubernetesdefault-backend-ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: ingress-with-default
spec:
    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

Requests not matching any rule go to default-service.

Practical Examples

Example 1: Complete Microservices Setup

Kubernetesmicroservices-ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: microservices-ingress
    annotations:
        nginx.ingress.kubernetes.io/ssl-redirect: "true"
        nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
    tls:
        - hosts:
              - app.example.com
          secretName: app-tls
    rules:
        - host: app.example.com
          http:
              paths:
                  - path: /api/users(/|$)(.*)
                    pathType: Prefix
                    backend:
                        service:
                            name: user-service
                            port:
                                number: 8080
                  - path: /api/orders(/|$)(.*)
                    pathType: Prefix
                    backend:
                        service:
                            name: order-service
                            port:
                                number: 8080
                  - path: /api/products(/|$)(.*)
                    pathType: Prefix
                    backend:
                        service:
                            name: product-service
                            port:
                                number: 8080
                  - path: /
                    pathType: Prefix
                    backend:
                        service:
                            name: frontend-service
                            port:
                                number: 3000

Example 2: Multi-Environment Ingress

Kubernetesmulti-env-ingress.yml
# Production Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: production-ingress
    namespace: production
spec:
    tls:
        - hosts:
              - api.example.com
          secretName: prod-tls
    rules:
        - host: api.example.com
          http:
              paths:
                  - path: /
                    pathType: Prefix
                    backend:
                        service:
                            name: api-service
                            port:
                                number: 8080
---
# Staging Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: staging-ingress
    namespace: staging
spec:
    rules:
        - host: staging-api.example.com
          http:
              paths:
                  - path: /
                    pathType: Prefix
                    backend:
                        service:
                            name: api-service
                            port:
                                number: 8080

Example 3: Ingress with Authentication

Kubernetesauth-ingress.yml
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:
    rules:
        - host: admin.example.com
          http:
              paths:
                  - path: /
                    pathType: Prefix
                    backend:
                        service:
                            name: admin-service
                            port:
                                number: 80

Create auth secret:

Kubernetesbash
htpasswd -c auth admin
sudo kubectl create secret generic basic-auth --from-file=auth

Tip

If you used default K3s Ingress Controller which is Traefik, you can create Ingress Basic Auth using CRD or Custom Resource Definition like this

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: basic-auth
spec:
basicAuth:
  secret: basic-auth

After that you can access admin.example.com using command curl like this

bash
curl -k --resolve admin.example.com:443:10.10.10.4 https://admin.example.com -u admin:admin -v

Viewing Ingress Details

Get Ingress

Kubernetesbash
sudo kubectl get ingress

Describe Ingress

Kubernetesbash
sudo kubectl describe ingress web-ingress

Output:

Kubernetesbash
Name:             web-ingress
Namespace:        default
Address:          203.0.113.10
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host              Path  Backends
  ----              ----  --------
  web.example.com
                    /   web-service:80 (10.42.0.10:80,10.42.0.11:80,10.42.0.12:80)
Annotations:        <none>
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  Sync    2m    nginx-ingress-controller  Scheduled for sync

View Ingress YAML

Kubernetesbash
sudo kubectl get ingress web-ingress -o yaml

Common Mistakes and Pitfalls

Mistake 1: No Ingress Controller

Problem: Ingress created but not working.

Solution: Install Ingress Controller first:

Kubernetesbash
sudo kubectl get pods -n ingress-nginx

Mistake 2: Wrong Service Name

Problem: Ingress can't find Service.

Solution: Verify Service exists:

Kubernetesbash
sudo kubectl get service <service-name>

Mistake 3: DNS Not Configured

Problem: Can't access via hostname.

Solution: Configure DNS or use /etc/hosts:

bash
203.0.113.10 example.com

Mistake 4: Missing TLS Secret

Problem: HTTPS not working.

Solution: Verify secret exists:

Kubernetesbash
sudo kubectl get secret example-tls

Mistake 5: Path Conflicts

Problem: Wrong Service receives traffic.

Solution: Order paths from most specific to least specific:

Kubernetesyml
paths:
    - path: /api/v2  # More specific first
    - path: /api     # Less specific after
    - path: /        # Catch-all last

Best Practices

Use TLS for Production

Always enable HTTPS:

Kubernetesyml
spec:
    tls:
        - hosts:
              - example.com
          secretName: example-tls

Organize by Environment

Separate Ingress per environment:

Kubernetesyml
# production namespace
name: production-ingress
namespace: production
 
# staging namespace
name: staging-ingress
namespace: staging

Use Meaningful Names

Choose descriptive Ingress names:

Kubernetesyml
# Good
name: api-ingress
name: web-app-ingress
name: admin-portal-ingress
 
# Avoid
name: ingress1
name: ing
name: test

Set Resource Limits on Services

Ingress routes to Services, ensure Pods have limits:

Kubernetesyml
resources:
    requests:
        memory: "256Mi"
        cpu: "250m"
    limits:
        memory: "512Mi"
        cpu: "500m"

Implement Rate Limiting

Protect against abuse:

Kubernetesyml
annotations:
    nginx.ingress.kubernetes.io/limit-rps: "10"
    nginx.ingress.kubernetes.io/limit-connections: "5"

Use Cert-Manager for TLS

Automate certificate management:

Kubernetesbash
# Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml

Monitor Ingress

Check Ingress Controller logs:

Kubernetesbash
sudo kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx

Troubleshooting Ingress

Check Ingress Controller

Kubernetesbash
sudo kubectl get pods -n ingress-nginx
sudo kubectl logs -n ingress-nginx <controller-pod>

Test Service Directly

Kubernetesbash
sudo kubectl port-forward service/<service-name> 8080:80
curl http://localhost:8080

Verify DNS Resolution

Kubernetesbash
nslookup example.com
dig example.com

Check Ingress Events

Kubernetesbash
sudo kubectl describe ingress <ingress-name>

Conclusion

In episode 19, we've explored Ingress in Kubernetes in depth. We've learned what Ingress is, how it differs from Services, and how to use it for sophisticated HTTP/HTTPS routing.

Key takeaways:

  • Ingress manages external HTTP/HTTPS access to Services
  • Requires Ingress Controller to function (NGINX, Traefik, etc.)
  • Provides host-based and path-based routing
  • Single external IP for multiple Services
  • Built-in TLS termination for HTTPS
  • Three path types: Prefix, Exact, ImplementationSpecific
  • Customizable via annotations (controller-specific)
  • More cost-effective than multiple LoadBalancers
  • Layer 7 (HTTP/HTTPS) routing vs Service Layer 4 (TCP/UDP)
  • Always use TLS for production
  • Install Ingress Controller before creating Ingress resources

Ingress is essential for exposing HTTP/HTTPS applications in Kubernetes. By understanding Ingress, you can build sophisticated routing configurations, manage TLS certificates, and efficiently expose multiple Services through a single entry point.

Are you getting a clearer understanding of Ingress in Kubernetes? Keep your learning momentum going and look forward to the next episode!