Computer scienceSystem administration and DevOpsKubernetesNetworking and traffic management

HTTP load balancing with Ingress

6 minutes read

Previously, we discussed how you can expose applications running in Kubernetes to the outside world using Services of type NodePort and LoadBalancer. But what if you have multiple applications hosted on your cluster with multiple exposed Services? For NodePort Services, that means a unique port per exposed Service. For LoadBalancer Services, you'd need to allocate a load balancer for each Service you expose, which isn't cost-effective. Let's see how you can use the Ingress resource to solve this.

Introduction to Ingress

The Service object operates at Layer 4 of the OSI model. That means it only forwards TCP/UDP traffic based on IP address and port, without looking inside the request. For this reason, exposing several HTTP applications requires multiple Services, each with its own external entry point.

Ingress is an API object that provides HTTP and HTTPS routing to Kubernetes Services. It operates at the application layer (Layer 7), so it can inspect HTTP traffic and route requests based on rules such as hostnames and URL paths. It provides a mechanism to expose multiple Services to the outside world from a single IP address. It also supports TLS termination and automated SSL certificate management for your Services via tools like cert-manager. We will not cover that setup in this topic.

You can think of Ingress as a layer 7 load balancer for Kubernetes Services. Here's how the setup works with and without Ingress:

An LB service multiple services with ingress and only a single one without.

As you can see, we only need a single load balancer. Then, we define rules that determine how incoming requests are routed to different Services.

Preparing for Ingress

Unlike most Kubernetes objects, Ingress is split into a resource specification and a controller implementation. An Ingress controller implements the rules defined in the Ingress resource. Kubernetes doesn't include an Ingress controller by default, although managed offerings (GKE, AKS, EKS, etc) provide one out of the box. On a self-managed or local cluster, you need to install one from providers such as HAProxy, Contour, Cilium, and many others. For all these clusters, you must ensure that it is possible to create Services of type: LoadBalancer and get an external IP.

Minikube ships with an ingress controller addon, which you can enable with: minikube addons enable ingress. For Kind, you can use Contour:

$ kubectl apply -f https://projectcontour.io/quickstart/contour.yaml

Verify with the following command:

$ kubectl get pods -n projectcontour -o wide

You can also see that the command created a LoadBalancer Service:

$ kubectl get -n projectcontour svc envoy -o wide
NAME    TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)                      AGE     SELECTOR
envoy   LoadBalancer   34.118.231.186   34.163.12.68   80:30624/TCP,443:32665/TCP   2m48s   app=envoy

Before moving on to creating an Ingress resource, let's also create some Deployments and Services:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-home
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app-home
  template:
    metadata:
      labels:
        app: app-home
    spec:
      containers:
        - image: hashicorp/http-echo:latest
          name: http-echo
          ports:
            - containerPort: 5678
          args:
            - "-text=Hello from Home service."

Expose the Deployment:

$ kubectl expose deploy app-home --port 80 --target-port 5678

# access the app in your local browser
$ kubectl port-forward svc/app-home 80
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app-api
  template:
    metadata:
      labels:
        app: app-api
    spec:
      containers:
        - image: hashicorp/http-echo:latest
          name: http-echo
          ports:
            - containerPort: 5678
          args:
            - "-text=Hello from API service."

Expose the Deployment:

$ kubectl expose deploy app-api --port 80 --target-port 5678

# access the app in your local browser
$ kubectl port-forward svc/app-api 80

This creates ClusterIP Services for each Deployment. Next, we'll create Ingress rules for routing external traffic to these Services. There are two types: host-based and path-based routing.

Host-based routing

Host-based routing directs traffic based on the Host header of the incoming request. This header contains the domain name and the port number that the client is trying to access. Here's how you configure host-based routing:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: host-ingress
spec:
  rules:
    - host: app-home.local
      http:
        paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: app-home
                port:
                  number: 80

    - host: app-api.local
      http:
        paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: app-api
                port:
                  number: 80

To resolve app-home.local and app-api.local, you need to map those names to the external IP of the load balancer (or minikube ip) in your hosts file. On Linux and macOS, that file is /etc/hosts and on Windows, it is C:\Windows\System32\drivers\etc\hosts. You need admin/root rights to modify this file. On macOS, you may also need to run: sudo killall -HUP mDNSResponder after changing this file. Edit the file and add a line as follows:

<ip-address> app-home.local app-api.local

Remember to undo these changes afterward. Now, when you open your browser and navigate to app-home.local or app-api.local, the browser directs you to the app-home or app-api Services we created earlier.

Accessing different services from the same host.

Next, let's see how we can route to different Services based on the path.

Path-based routing

Path-based routing directs traffic based on the URL path of incoming requests. Here's an example:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: path-ingress
spec:
  rules:
    - http:
        paths:
          - path: /dashboard
            pathType: Exact
            backend:
              service:
                name: app-home
                port:
                  number: 80

          - path: /api
            pathType: Exact
            backend:
              service:
                name: app-api
                port:
                  number: 80

Now, requests to /dashboard will go to the app-home Service, and requests to /api will go to the app-api Service.

Accessing different services from different paths on the same host.

The pathType field has three possible values: Prefix, Exact, and ImplementationSpecific. With pathType: Prefix, the rule matches if the request path begins with the specified path (split on / segments). With pathType: Exact, the request path must match the specified path exactly. With ImplementationSpecific, the IngressClass handles the interpretation, which allows controllers to support custom matching semantics (for example, regular expressions).

Note that more advanced routing (header-based, method-based, weighted traffic splitting, etc.) is not part of the core Ingress API. Controllers typically expose such features through controller-specific annotations, or the newer Gateway API better serves them.

Conclusion

We've covered how Kubernetes exposes HTTP and HTTPS workloads through the Ingress resource. This lets us consolidate many Services behind a single external IP, replacing multiple LoadBalancer or NodePort Services with declarative routing rules.

The Ingress API splits into two parts: the resource, which describes the desired routing, and the controller, which implements it. With host-based and path-based rules, plus features like TLS termination, a single Ingress controller can act as the entry point for an entire cluster's HTTP traffic. Together, these primitives give you a flexible, cost-effective way to expose Kubernetes applications to the outside world.

How did you like the theory?
Report a typo