Nginx Ingress

Rewrite URI using a regex pattern

Evaluate different approaches to rewrite the URI based on the subdomain of Nginx Ingress

Sometimes you may need to use a wild card domain name in the Nginx ingress and route traffic by rewriting the URI based on the subdomain name. For example, consider wildcard domain *.domain.com;

If we use the default Nginx server, this is pretty much straight forward and you can simply use the following code block in order to rewrite the URI based on the subdomain.

    location / {         
proxy_pass http://service_upstream/$prefix/$suffix$uri;
}
}

But when it comes to the Nginx ingress it’s not the case. It’s difficult to generate the above configuration using the Ingress resource due to the limitation in using regex syntax for the hostname.

“Kubernetes only accept expressions that comply with the RE2 engine syntax. It is possible that valid expressions accepted by NGINX cannot be used with ingress-nginx, because the PCRE library (used in NGINX) supports a wider syntax than RE2.” [1]

Before we move forward, let's clear out our understanding of how the Nginx configuration is generated by the Kubernetes Ingress controller.

Nginx configuration consists of two main block directives called event and http, where server and location block directives reside in the http main block. Server block defines a virtual server and used to handle the request based on hostname, IP address and port number. Location block resides withing the server block and used to define the routing based on the URI of the parent server.

Single Nginx ingress resource represents a server block in Nginx configuration and supports adding multiple paths which is equivalent to the location block in the Nginx configuration.

How Nginx configuration generated from Nginx ingress resource.

Now, let's find out several approaches on how we can create a regex hostname comply with PCRE syntax.

Adding regex pattern to the default server block

Here we have added regex pattern using server-snippet[2] and the rewrite logic using rewrite-target[3] annotations. Since we have not defined a hostname in the rule defined(line 15), this configuration will be added to the default server block. Note that you cannot override the hostname by adding a server snippet if you define a hostname in the ingress rules. So it has to be the default server block if you need to define a hostname with the regex expression with PCRE syntax.

Drawbacks:
As you can see here, we have overridden the default server block so all the traffic which doesn’t comply with any Nginx rules end up here. And this is not recommended because the default server block is there to serve a different purpose.

Nginx tests only the request’s header field “Host” to determine which server the request should be routed to. If its value does not match any server name, or the request does not contain this header field at all, then nginx will route the request to the default server block.

You may get into trouble if you have pointed out multiple domains’ DNS records to the same Nginx server. In case if you forget to define all rules related to those domains, some requests will be handled by the default server block which is now overridden to serve other wildcard domain name.

Adding URI rewrite logic using a Lua Script

Nginx ingress by default uses lua-nginx-module[4] to avoid reloading Nginx in every upstream change. Otherwise, Nginx reloads will affect response latency and the quality of the load balancing(Nginx reset the load balancing state in every reload).

Because of this, we can execute the Lua code blocks inside the Nginx configuration out of the box using the configuration-snippet[5] annotation. In this case, we’ll use set_by_lua_block module which is used to execute short and fast running code blocks inside Nginx. But most importantly, you need to make sure the efficiency of the code block running since the Nginx event loop is blocked during the code execution.

Drawbacks:
The major drawback of this approach is that complex Lua logic will slow down the overall performance of the Nginx controller.
Due to restricted permissions inside the set_by_lua_block, error handling is not possible. So every error will trigger a 503 response code from the Nginx.

Adding URI rewrite logic using a Map directive

This is the recommended approach considering the drawbacks of the aforementioned approaches. A map directive[6] is an Nginx way of defining a method; a map creates a new variable whose value depends on the values of one or more of the source variables specified in the first parameter.

In the above code block, we have defined a map directive that will use the $http_host as an input variable(a default variable used in Nginx) and output the $prefix and $suffix as the output variables. And we can use the defined map directive inside the ingress definition as depicted in the below code block (line 7).

Since the map directive should be defined under HTTP context of the Nginx, we cannot directly inject them into the Nginx ingress definition. Nginx ingress only allows you to create and customize the location and server directives.

However, the Nginx controller allows you to customize the Nginx configuration using a ConfigMap resource. So in this case we can use http-snippet[7] config in order to add additional configuration to the HTTP context of the Nginx configuration. So we can add our map directive using http-snippet to the Nginx configMap as shown below,

A huge advantage of using this approach is that variables defined using map directives will only be executed when they are used. So even a large number of map directive does not add performance overhead to the request processing.

So my final thought on this is that always consider the performance and security implications before you choose any approach mentioned above.

Cloud Engineer