nginScript to Progressively Transition Clients to a New Server

Original: https://www.nginx.com/blog/nginscript-progressively-transition-clients-to-new-server/

Building customized upstream selection with JavaScript code

Editor – This is the second in a series of blog posts about nginScript. The first post discusses why NGINX, Inc. developed its own implementation of JavaScript, and presents a sample use case. The subsequent posts explore additional use cases:

This post also belongs to the series that covers in depth features introduced in NGINX Plus R10:

With the launch of NGINX Plus R10, we introduced a preview release of our next‑generation programmatic configuration language, nginScript. nginScript is a unique JavaScript implementation for NGINX and NGINX Plus, designed specifically for server‑side use cases and per‑request processing.

Author’s note – As mentioned when this post was published at the R10 release, we’re continuing to extend and enhance nginScript. We have updated the configuration of the frontend service and the name of the nginScript function in this post to reflect updates to nginScript in NGINX Plus R11.

As of NGINX Plus R12, nginScript is a generally available and fully supported module.

One of the key benefits of nginScript is the ability to read and set NGINX configuration variables. The variables can then be used to make custom routing decisions unique to the needs of the environment. This means that you can use the power of JavaScript to implement complex functionality that directly impacts request processing.

The Use Case – Transitioning to a New Application Server

In this blog post we show how to use nginScript to implement a graceful switchover to a new application server. Instead of taking a disruptive “big‑bang approach” where everything is transitioned all at once, we define a time window during which all clients are progressively transitioned to the new application server. In this way, we can gradually – and automatically – increase traffic to the new application server.

We’re defining a two‑hour window during which we want our progressive switchover to take place, in this example from 5 to 7 PM. After the first 12 minutes we should expect 10% of clients to be directed to the new application server, then 20% of clients after 24 minutes, and so on. The following graph illustrates the transition.

Graph showing the proportion of clients that have transitioned to a new application server during a 2-hour window, as implemented in nginScript code
Desired transition of clients from old server to new server over two‑hour period

One important requirement of this “progressive transition” configuration is that transitioned clients don’t revert back to the original server – once a client has been directed to the new application server, it continues to be directed there for the remainder of the transition window (and afterward, of course).

We will describe the complete configuration below, but in brief, when NGINX or NGINX Plus processes a new request that matches the application that is being transitioned, it follows these rules:

Let’s get started!

NGINX and NGINX Plus Configuration for HTTP Applications

In this example we are using NGINX or NGINX Plus as a reverse proxy to a web application server, so all of our configuration is in the http context. For details about the configuration for TCP and UDP applications in the stream context, see below.

First, we define separate upstream blocks for the sets of servers that host our old and new application code respectively. Even with our progressive transition configuration, NGINX or NGINX Plus will continue to load balance between the available servers during the transition window.

Next we define the frontend service that NGINX or NGINX Plus presents to clients.

We are using nginScript to determine which upstream group to use, so we need to specify where the nginScript code resides. In NGINX Plus R11 and later, all nginScript code must be located in a separate file and the js_include directive specifies its location.

The js_set directive sets the value of the $upstream variable. Note that the directive does not instruct NGINX or NGINX Plus to call the nginScript function transitionStatus. NGINX variables are evaluated on demand, that is, at the point during request processing that they are used. So the js_set directive tells NGINX or NGINX Plus how to evaluate the $upstream variable when it is needed.

The server block defines how NGINX or NGINX Plus handles incoming HTTP requests. The listen directive tells NGINX or NGINX Plus to listen on port 80 – the default for HTTP traffic, although a production configuration normally uses SSL/TLS to protect data in transit.

The location block applies to the entire application space (/). Within this block we use the set directive to define the transition window with two new variables, $transition_window_start and $transition_window_end. Note that dates can be specified in RFC 2822 format (as in the snippet) or ISO 8601 format (with milliseconds). Both formats must include their respective local time zone designator. This is because the JavaScript Date.now function always returns the UTC date and time and so an accurate time comparison is possible only if the local time zone is specified.

The proxy_pass directive directs the request to the upstream group that is calculated by the transitionStatus function.

Finally, the error_log directive enables nginScript logging of events with severity level info and higher (by default only events at level warn and higher are logged). By placing this directive inside the location block and naming a separate log file, we avoid cluttering the main error log with all info messages.

nginScript Code for HTTP Applications

We assume that you have already enabled the nginScript module. Instructions appear at the end of this article.

We put our nginScript code in /etc/nginx/progressive_transition.js as specified by the js_include directive. All of our functions appear in this file.

Dependent functions must appear before those that call them, so we start by defining a function that returns a hash of the client’s IP address. If our application server is predominantly used by users on the same LAN then all of our clients have very similar IP addresses, so we need the hash function to return an even distribution of values even for a small range of input values.

In this example we are using the FNV‑1a hash algorithm, which is compact, fast, and has reasonably good distribution characteristics. Its other advantage is that it returns a positive 32‑bit integer, which makes it trivial to calculate the position of each client IP address in the output range. The following code is a JavaScript implementation of the FNV‑1a algorithm.

Next we define the function, transitionStatus, that sets the $upstream variable in the js_set directive in our NGINX configuration.

The transitionStatus function has a single parameter, req, which is the JavaScript object representing the HTTP request. The variables property of the request object contains all of the NGINX configuration variables, including the two we set to define the transition window, $transition_window_start and $transition_window_end.

The outer if/else block determines whether the transition window has started, finished, or is in progress. If it’s in progress, we obtain the hash of the client IP address by passing req.remoteAddress to the fnv32a function.

We then calculate where the hashed value sits within the range of all possible values. Because the FNV‑1a algorithm returns a 32‑bit integer we can simply divide the hashed value by 4,294,967,295 (the decimal representation of 32 bits).

At this point we invoke req.log() to log the hash position and the current position of the transition time window. This is logged at the info level to the error_log defined in our NGINX or NGINX Plus configuration and produces log entries such as the following example. The js: prefix serves to denote a log entry resulting from JavaScript code.

2016/09/08 17:44:48 [info] 41325#41325: *84 js: timepos = 0.373333, hashpos = 0.840858

Finally, we compare the hashed value’s position within the output range with our current position within the transition time window, and return the name of the corresponding upstream group.

NGINX and NGINX Plus Configuration for TCP and UDP Applications

The sample configuration in NGINX and NGINX Plus Configuration for HTTP Applications is appropriate when NGINX or NGINX Plus acts as a reverse proxy for an HTTP application server. We can adapt the configuration for TCP and UDP applications by moving the entire configuration snippet to the stream context.

Two changes are required:

  1. In Step 2 of Loading nginScript for NGINX Plus or Loading nginScript for Open Source NGINX, enable the nginScript Stream module by including a load_module directive for it in the top-level (“main”) context of the nginx.conf configuration file. Then reload NGINX Plus as directed in Step 3.

    load_module modules/ngx_stream_js_module.so;
  2. Define the $transition_window_start and $transition_window_end variables in the transitionStatus function instead of with the set directive in the NGINX or NGINX Plus configuration, because the set directive is not yet supported in the stream context.

Summary

We’ve presented the progressive transition use case in this blog post as an illustration of the type of complex programmatic configuration you can achieve with nginScript. Deploying custom logic to control the selection of the most appropriate upstream group is one of a large number of solutions now possible with nginScript.

We will continue to extend and enhance the capabilities of nginScript to support ever‑more useful programmatic configuration solutions for NGINX and NGINX Plus. In the meantime, we would love to hear about the problems you are solving with nginScript – please add them to the comments section below.

To try out nginScript with NGINX Plus yourself, start your free 30‑day trial today or contact us for a live demo.


Enabling nginScript for NGINX and NGINX Plus

Loading nginScript for NGINX Plus

nginScript is available as a free dynamic module for NGINX Plus subscribers (for open source NGINX, see Loading nginScript for Open Source NGINX below).

  1. Obtain the module itself by installing it from the NGINX Plus repository.

    • For Ubuntu and Debian systems:

      $ sudo apt‑get install nginx-plus-module-njs
    • For RedHat, CentOS, and Oracle Linux systems:

      $ sudo yum install nginx-plus-module-njs
    • Enable the module by including a load_module directive for it in the top‑level ("main") context of the nginx.conf configuration file (not in the http or stream context). This example loads the nginScript modules for both HTTP and TCP/UDP traffic.

      load_module modules/ngx_http_js_module.so;
      load_module modules/ngx_stream_js_module.so;
    • Reload NGINX Plus to load the nginScript modules into the running instance.

      $ sudo nginx -s reload

Loading nginScript for Open Source NGINX

If your system is configured to use the official prebuilt packages for open source NGINX and your installed version is 1.9.11 or later, then you can install nginScript as a prebuilt package for your platform.

  1. Install the prebuilt package.

    • For Ubuntu and Debian systems:

      $ sudo apt-get install nginx-module-njs
    • For RedHat, CentOS, and Oracle Linux systems:

      $ sudo yum install nginx-module-njs
  2. Enable the module by including a load_module directive for it in the top‑level ("main") context of the nginx.conf configuration file (not in the http or stream context). This example loads the nginScript modules for both HTTP and TCP/UDP traffic.

    load_module modules/ngx_http_js_module.so;
    load_module modules/ngx_stream_js_module.so;
  3. Reload NGINX Plus to load the nginScript modules into the running instance.

    $ sudo nginx -s reload

Compiling nginScript as a Dynamic Module for Open Source NGINX

If you prefer to compile an NGINX module from source:

  1. Follow these instructions to build either or both the HTTP and TCP/UDP nginScript modules from the open source repository.
  2. Copy the module binaries (ngx_http_js_module.so, ngx_stream_js_module.so) to the modules subdirectory of the NGINX root (usually /etc/nginx/modules).
  3. Perform Steps 2 and 3 in Loading nginScript for Open Source NGINX.
Retrieved by Nick Shadrin from nginx.com website.