Kubernetes Ingress
resources provide a way of configuring incoming HTTP traffic and make it easy to expose multiple services through a single public IP address.
NGINX has long provided one of the most popular ingress controllers, but anything more than a proof of concept deployment inevitably meant customizing routing rules beyond the standard properties exposed by Ingress
resources.
Until recently, the solution was to define these additional settings via annotations or providing configuration blocks in configmaps. But with version 1.5, the NGINX ingress controller provides two custom resource definitions (CRDs) defining more complex networking rules than the baseline Ingress
resources.
In this post, we’ll explore some of the new functionality provided by the VirtualServer
and VirtualServerRoute
CRDs.
Read our guide: Kubernetes delivery unlocked
Explore the principles, practices, and tools that underpin effective Continuous Delivery for Kubernetes, specifically tailored for developers.
Get the guideThe sample Kubernetes cluster
For this blog, I used the Kubernetes distribution bundled with Docker Desktop:
I then deployed the sample application created for the Istio blog series, which can be installed with:
kubectl apply -f https://raw.githubusercontent.com/mcasperson/NodejsProxy/master/kubernetes/example.yaml
The resulting cluster looks like this:
I used Helm to install NGINX, but it’s not as easy as it could be. The GitHub docs point you to the Helm repo, which failed for me. The chart from the official Helm repository at https://kubernetes-charts.storage.googleapis.com/ did not include the CRDs.
The solution was to clone the NGINX Git repo and install the Helm chart from a local file. These commands worked with Helm 3:
git clone https://github.com/nginxinc/kubernetes-ingress/
cd kubernetes-ingress/deployments/helm-chart
helm install nginx-release .
A basic NGINX VirtualServer
We’ll start with a basic VirtualServer
resource to expose the proxy.
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: virtualserver
spec:
host: localhost
upstreams:
- name: proxy
service: proxy
port: 80
routes:
- path: /
action:
pass: proxy
The upstreams
property defines the services that traffic can be sent to. In this example, we direct traffic to the proxy
service.
The routes
match incoming requests and perform an action in response. Typically the action is to direct traffic to an upstream server, which we have done with the action.pass: proxy
configuration.
This VirtualServer
replicates the functionality we might otherwise have defined in an Ingress
resource, and once deployed to the cluster, we can open http://localhost/whatever/you/want to view the proxy web application.
Custom actions
Things become interesting when we start digging into the new functionality the VirtualServer
exposes.
Instead of passing requests to an upstream service, the VirtualServer
can redirect the client to a new URL. Here we direct traffic back to the NGINX homepage:
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: virtualserver
spec:
host: localhost
routes:
- path: /
action:
redirect:
url: http://www.nginx.com
code: 301
In this example, we define the content to be returned directly in the VirtualServer
resource. This is great for testing:
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: virtualserver
spec:
host: localhost
routes:
- path: /
action:
return:
code: 200
type: text/plain
body: "Hello World\n"
Traffic splitting
Traffic splitting can be used for canary deployments by directing a percentage of traffic to a new service. Here we configure the VirtualServer
to pass traffic to the webserver services, splitting traffic between webserverv1
and webserverv2
.
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: virtualserver
spec:
host: localhost
upstreams:
- name: webserverv1
service: webserverv1
port: 80
- name: webserverv2
service: webserverv2
port: 80
routes:
- path: /
splits:
- weight: 80
action:
pass: webserverv1
- weight: 20
action:
pass: webserverv2
Load balancing
In iptables proxy mode, endpoints available to a service are chosen at random. If you look at the diagram above, the webserver
service directs traffic to both webserverv1
and webserverv2
deployments, so traffic to the webserver
service would be randomly distributed between all pods.
NGINX allows us to specify the load balancing rules used to direct traffic to upstream services. In the example below, we set the lb-method
property to the ip_hash
load balancing algorithm, ensuring a client is always sent to the same backend pod:
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: virtualserver
spec:
host: localhost
upstreams:
- name: webserver
service: webserver
port: 80
lb-method: ip_hash
routes:
- path: /
action:
pass: webserver
Timeouts, retries, and keepalives
Lower level configuration details like connection timeouts, retries, and keepalives used to be defined as annotations on Ingress
resources. With a VirtualServer
resource, these settings are now exposed as first-class properties:
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: virtualserver
spec:
host: localhost
upstreams:
- name: webserver
service: webserver
port: 80
fail-timeout: 10s
max-fails: 1
max-conns: 32
keepalive: 32
connect-timeout: 30s
read-timeout: 30s
send-timeout: 30s
next-upstream: "error timeout non_idempotent"
next-upstream-timeout: 5s
next-upstream-tries: 10
client-max-body-size: 2m
routes:
- path: /
action:
pass: webserver
Adding VirtualServerRoutes
VirtualServer
resources can delegate the request to a VirtualServerRoute
. This allows a master VirtualServer
resource to configure top-level traffic rules with VirtualServerRoute
resources handling more specific routing rules.
In the example below, requests are sent to a VirtualServerRoute
resource which selects one of three possible upstream services based on the path:
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: virtualserver
spec:
host: localhost
routes:
- path: /
route: virtualserverroute
---
apiVersion: k8s.nginx.org/v1
kind: VirtualServerRoute
metadata:
name: virtualserverroute
spec:
host: localhost
upstreams:
- name: proxy
service: proxy
port: 80
- name: webserverv1
service: webserverv1
port: 80
- name: webserverv2
service: webserverv2
port: 80
subroutes:
- path: /webserverv1
action:
pass: webserverv1
- path: /webserverv2
action:
pass: webserverv2
- path: /
action:
pass: proxy
Conclusion
Ingress controllers started as a cottage industry in the Kubernetes ecosystem, but as each provider differentiated itself from the competition with new configuration options and features, the annotations added to Ingress
resources made them unwieldy and unportable.
By implementing CRDs, NGINX exposes advanced functionality with verifiable properties. I expect these CRDs will be enriched even further as additional common use cases are identified.