Ingress API
Although technically it is possible to expose internal applications via NodePort or LoadBalancer Services, this happens very rarely. There are two main reasons for that:
- Costs – since each LoadBalancer Services is associated with a single external address, this can translate into a sizeable fee when running in a public cloud environment.
- Functionality – simple L4 load balancing provided by Services lacks a lot of the features that are typically associated with an application proxy or gateway. This means that each exposed application will need to take care of things like TLS management, rate-limiting, authentication and intelligent traffic routing on its own.
Ingress was designed as a generic, vendor-independent API to configure an HTTP load balancer that would be available to multiple Kubernetes applications. Running an Ingress would amortise the costs and efforts of implementing an application gateway functionality and provide an easy to consume, native Kubernetes experience to cluster operators and users. At the very least, a user is expected to define a single rule telling the Ingress which backend Service to use. This would result in all incoming HTTP requests to be routed to one of the healthy Endpoint of this Service:
Similar to Service type LoadBalancer, Kuberenetes only defines the Ingress API and leaves implementation to cluster add-ons. In public cloud environments, these functions are implemented by existing application load balancers, e.g. Application Gateway in AKS, Application Load Balancer in EKS or Google Front Ends (GFEs) for GKE. However, unlike a LoadBalancer controller, Kubernetes distributions do not limit the type of Ingress controller that can be deployed to perform these functions. There are over a dozen of Ingress controller implementations from the major load balancer, proxy and service mesh vendors which makes choosing the right Ingress controller a very daunting task. Several attempts have been made to compile a decision matrix to help with this choice – one done by Flant and one by learnk8s.io. Multiple Ingress controllers can be deployed in a single cluster and Ingress resources are associated with a particular controller based on the .spec.ingressClassName field.
Ingress controller’s implementation almost always includes the following two components:
- Controller – a process that communicates with the API server and collects all of the information required to successfully provision its proxies.
- Proxy – a data plane component, managed by the controller (via API, plugins or plain text files), can be scaled up and down by the Horizontal Pod Autoscaler.
Typically, during the installation process, an Ingress Controller creates a Service type LoadBalancer and uses the allocated IP to update the .status.loadBalancer field of all managed Ingresses.
Lab
For this lab exercise, we’ll use one of the most popular open-source Ingress controllers – ingress-nginx.
Preparation
Assuming that the lab environment is already set up, ingress-nginx can be set up with the following commands:
Install a LoadBalancer controller to allocate external IP for the Ingress controller
Wait for Ingress controller to fully initialise
Set up a couple of test Deployment and associated Ingress resources to be used in the walkthrough.
The above command sets up two ingress resources – one doing the path-based routing and one doing the host-based routing. Use the following command to confirm that both Ingresses have been set up and assigned with an external IP:
Now we can verify the path-based routing functionality:
And the host-based routing:
To confirm that the HTTP routing is correct, take note of the Server name field of the response, which should match the name of the backend Pod:
Walkthrough
Let’s start by looking at the Ingress controller logs to see what happens when a new Ingress resource gets added to the API server:
Most of the above log is self-explanatory – we see that the controller performs some initial validations, updates the configuration, triggers a proxy reload and updates the status field of the managed Ingress. We can see where the allocated IP is coming from by looking at the associated LoadBalancer service:
Now that we know what happens when a new Ingress is processed, let’s take a look inside the Ingress controller pod
Here we see to main components described above – a controller called nginx-ingress-controller and a proxy process /usr/local/nginx/sbin/nginx. We also see that the proxy is started with the -c argument, pointing it at the configuration file. If we look inside this configuration file, we should see the host-based routing server_name directives:
Similarly, we can view the path-based routing location directives:
Examining the plain nginx.conf configuration can be a bit difficult, especially for large configs. A simpler way of doing it is using an ingress-nginx plugin for kubectl which can be installed with krew. For example, this is how we could list all active Ingress resources managed by this controller:
Backend objects are not managed via a configuration file, so you won’t see them in the nginx.conf rendered by the controller. The only way to view them is using the ingress-nginx plugin, e.g.:
Warning
The above walkthrough is only applicable to the nginx-ingress controller. Other controllers may implement the same functionality differently, even if the data plane proxy is the same (e.g. nginx-ingress vs F5 nginx Ingress controller). Ingress API changes do not necessarily result in a complete proxy reload, assuming the underlying proxy supports hot restarts, e.g. Envoy.