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:
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.yamlVerify with the following command:
$ kubectl get pods -n projectcontour -o wideYou 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=envoyBefore 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 80apiVersion: 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 80This 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: 80To 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.localRemember 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.
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: 80Now, requests to /dashboard will go to the app-home Service, and requests to /api will go to the app-api Service.
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.