Technical Theory

Kubernetes Ingress

Introduction

This tutorial will walk you through the process of setting up an Ingress controller and defining Ingress resources to manage external access to your Kubernetes services. You should have a basic understanding of Kubernetes concepts like Pods, Services, and Deployments. You’ll also need a running Kubernetes cluster and kubectl configured to interact with it. We will use Minikube for a local deployment scenario, but the principles apply to any Kubernetes cluster.

Prerequisites

Before you begin, make sure you have the following:

  • A running Kubernetes cluster (e.g., Minikube, Kind, or a cloud-based cluster).
  • kubectl installed and configured to connect to your cluster.
  • Basic knowledge of Kubernetes concepts.

Task 1: Setting Up Minikube

If you don’t already have a Kubernetes cluster, Minikube is a great option for local development.

  1. Start Minikube:

    NODE_TYPE // bash
    minikube start
    NODE_TYPE // output
    😄  minikube v1.32.0 on Darwin 14.4 (amd64)
      Using the docker driver based on user configuration
    👍  Starting control plane node minikube in cluster minikube
    🚜  Pulling base image ...
    💾  Downloading Kubernetes v1.29.2 preload ...
        > preloaded.tar.lz4: 1.24 GiB / 1.24 GiB  100.00% 24.19 MiB p/s 00m53s
    🔥  Creating docker container (CPUs=2, Memory=2200MB) ...
    🐳  Preparing Kubernetes v1.29.2 on Docker 24.0.7 ...
        Generating certificates and keys ...
        Bootstrapping components ...
        Configuring cluster parameters ...
        Installing addons ...
         - dashboard: v2.7.0 is available
         - storage-provisioner: v1.0.0
    🔎  Verifying Kubernetes ...
    🌟  Enabled addons: storage-provisioner, default-storageclass
    🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
  2. Enable the Ingress addon:

    NODE_TYPE // bash
    minikube addons enable ingress
    NODE_TYPE // output
    😄  ingress is already enabled
    The minikube addons enable ingress command sets up an Ingress controller within your Minikube cluster, making it possible to test Ingress resources locally.

Task 2: Deploying Sample Applications

Let’s deploy two simple applications to demonstrate Ingress routing.

graph TD
    %% Admin Actions
    Admin["Admin / CLI"]:::adminBox

    subgraph K8s_Cluster["Kubernetes Cluster"]
        direction TB

        %% Application 1 Logic
        subgraph App1_Stack["App 1 Stack"]
            direction TB
            D1["Deployment: app1-deployment
replicas: 1"]:::deployBox S1["Service: app1-service
Type: ClusterIP"]:::serviceBox P1["Pod: app1-pod
Label: app=app1"]:::podBox end %% Application 2 Logic subgraph App2_Stack["App 2 Stack"] direction TB D2["Deployment: app2-deployment
replicas: 1"]:::deployBox S2["Service: app2-service
Type: ClusterIP"]:::serviceBox P2["Pod: app2-pod
Label: app=app2"]:::podBox end end %% Deployment Actions Admin ==>|kubectl apply| D1 Admin ==>|kubectl apply| D2 Admin ==>|kubectl apply| S1 Admin ==>|kubectl apply| S2 %% Relationships D1 -.->|Manages| P1 D2 -.->|Manages| P2 %% Service Selectors S1 -- "Selects (app=app1)" --> P1 S2 -- "Selects (app=app2)" --> P2 %% STYLING classDef adminBox fill:#37474F,stroke:#263238,stroke-width:2px,rx:8,ry:8,color:#FFF; classDef deployBox fill:#E8EAF6,stroke:#3F51B5,stroke-width:2px,rx:8,ry:8,color:#1A237E; classDef serviceBox fill:#C7E6E2,stroke:#009688,stroke-width:3px,color:#004D40,rx:10,ry:10,font-weight:bold; classDef podBox fill:#D8FDF8,stroke:#4ED8C7,stroke-width:2px,rx:8,ry:8,color:#222; style App1_Stack fill:#F9F9F9,stroke:#CCC,stroke-width:1px,stroke-dasharray: 5 5; style App2_Stack fill:#F9F9F9,stroke:#CCC,stroke-width:1px,stroke-dasharray: 5 5; style K8s_Cluster fill:#FFF,stroke:#666,stroke-width:2px; %% Link Styling linkStyle 0,1,2,3 stroke:#37474F,stroke-width:2px; linkStyle 4,5 stroke:#3F51B5,stroke-width:2px,stroke-dasharray: 3 3; linkStyle 6,7 stroke:#009688,stroke-width:3px;
  1. Create a deployment for the first application:

    NODE_TYPE // yaml
    # app1-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: app1-deployment
      labels:
        app: app1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: app1
      template:
        metadata:
          labels:
            app: app1
        spec:
          containers:
          - name: app1
            image: nginx:latest
            ports:
            - containerPort: 80

    Apply the deployment:

    NODE_TYPE // bash
    kubectl apply -f app1-deployment.yaml
    NODE_TYPE // output
    deployment.apps/app1-deployment created
  2. Create a service for the first application:

    NODE_TYPE // yaml
    # app1-service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: app1-service
    spec:
      selector:
        app: app1
      ports:
        - protocol: TCP
          port: 80
          targetPort: 80
      type: ClusterIP

    Apply the service:

    NODE_TYPE // bash
    kubectl apply -f app1-service.yaml
    NODE_TYPE // output
    service/app1-service created
  3. Repeat the process for the second application:

    NODE_TYPE // yaml
    # app2-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: app2-deployment
      labels:
        app: app2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: app2
      template:
        metadata:
          labels:
            app: app2
        spec:
          containers:
          - name: app2
            image: nginx:latest
            ports:
            - containerPort: 80

    Apply the deployment:

    NODE_TYPE // bash
    kubectl apply -f app2-deployment.yaml
    NODE_TYPE // output
    deployment.apps/app2-deployment created
    NODE_TYPE // yaml
    # app2-service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: app2-service
    spec:
      selector:
        app: app2
      ports:
        - protocol: TCP
          port: 80
          targetPort: 80
      type: ClusterIP

    Apply the service:

    NODE_TYPE // bash
    kubectl apply -f app2-service.yaml
    NODE_TYPE // output
    service/app2-service created

Task 3: Defining an Ingress Resource

Now, let’s create an Ingress resource to route traffic to these applications based on hostnames.

graph TD
    %% Define External Users
    User1["External User A
app1.example.com"]:::externalBox User2["External User B
app2.example.com"]:::externalBox %% Kubernetes Cluster subgraph K8s_Cluster["Kubernetes Cluster"] direction TB %% Ingress Controller (The Smart Router) IngressCtrl["Ingress Controller
(Nginx/ALB)
Evaluates Host Header"]:::ingressBox %% Logical Ingress Rules subgraph IngressRules["Ingress Resource: example-ingress"] Rule1["Host: app1.example.com
Path: /"]:::ruleBox Rule2["Host: app2.example.com
Path: /"]:::ruleBox end %% Backend Services Svc1["Service: app1-service"]:::serviceBox Svc2["Service: app2-service"]:::serviceBox %% Target Pods Pod1["App 1 Pods"]:::podBox Pod2["App 2 Pods"]:::podBox end %% TRAFFIC FLOW User1 ==> IngressCtrl User2 ==> IngressCtrl %% Routing based on Hostname IngressCtrl -->|Matches Rule 1| Svc1 IngressCtrl -->|Matches Rule 2| Svc2 %% Final Destination Svc1 -.-> Pod1 Svc2 -.-> Pod2 %% STYLING classDef externalBox fill:#37474F,stroke:#263238,stroke-width:2px,rx:8,ry:8,color:#FFF; classDef ingressBox fill:#E3F2FD,stroke:#1E88E5,stroke-width:3px,color:#0D47A1,rx:12,ry:12,font-weight:bold; classDef ruleBox fill:#FFF,stroke:#7986CB,stroke-width:1px,color:#222,rx:5,ry:5,font-size:12px; classDef serviceBox fill:#C7E6E2,stroke:#009688,stroke-width:3px,color:#004D40,rx:10,ry:10,font-weight:bold; classDef podBox fill:#D8FDF8,stroke:#4ED8C7,stroke-width:2px,rx:8,ry:8,color:#222; style IngressRules fill:#F1F1F1,stroke:#C6C6C6,stroke-width:1px,stroke-dasharray: 5 5; style K8s_Cluster fill:#F9F9F9,stroke:#666,stroke-width:2px; %% Link Styling linkStyle 0,1 stroke:#1E88E5,stroke-width:4px; linkStyle 2,3 stroke:#5C6BC0,stroke-width:2px; linkStyle 4,5 stroke:#009688,stroke-width:2px,stroke-dasharray: 5 5;
  1. Create an Ingress resource file:

    NODE_TYPE // yaml
    # ingress.yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: example-ingress
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /
    spec:
      rules:
      - host: app1.example.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app1-service
                port:
                  number: 80
      - host: app2.example.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app2-service
                port:
                  number: 80
    The nginx.ingress.kubernetes.io/rewrite-target: / annotation ensures that the entire path is rewritten to / when forwarding the request to the backend service.
  2. Apply the Ingress resource:

    NODE_TYPE // bash
    kubectl apply -f ingress.yaml
    NODE_TYPE // output
    ingress.networking.k8s.io/example-ingress created

Task 4: Accessing the Applications

To access the applications through the Ingress, you need to configure your local machine to resolve the hostnames to the Ingress controller’s IP address.

  1. Get the Ingress controller’s IP address in Minikube:

    NODE_TYPE // bash
    minikube ip
    NODE_TYPE // output
    192.168.49.2
  2. Edit your /etc/hosts file (or the equivalent on your operating system) and add the following entries, replacing 192.168.49.2 with the actual IP address from the previous step:

    NODE_TYPE // text
    192.168.49.2 app1.example.com
    192.168.49.2 app2.example.com
    You will need administrator privileges to modify the /etc/hosts file.
  3. Now, you can access the applications using your web browser or curl:

    NODE_TYPE // bash
    curl http://app1.example.com
    NODE_TYPE // output
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
        body {
            width: 35em;
            margin: 0 auto;
            font-family: Tahoma, Verdana, Arial, sans-serif;
        }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    NODE_TYPE // bash
    curl http://app2.example.com
    NODE_TYPE // output
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
        body {
            width: 35em;
            margin: 0 auto;
            font-family: Tahoma, Verdana, Arial, sans-serif;
        }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>

    You should see the default Nginx welcome page for both applications, confirming that the Ingress is correctly routing traffic based on the hostnames.

Conclusion

In this tutorial, you learned how to set up an Ingress controller and define Ingress resources to route traffic to different services based on hostnames. This allows you to expose multiple applications through a single IP address, simplifying the management of external access to your Kubernetes cluster.

Next Topic