Using nginScript to Progressively Transition Clients to a New Server

Datetime:2016-09-08 18:25:52         Topic: nginScript          Share        Original >>
Here to See The Original Article!!!

Building customized upstream selection with JavaScript code

With the launch ofNGINX 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.

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.

Editor – This is the fourth in a series of blog posts that explore the new features in NGINX Plus R10 in depth. This list will be expanded as later articles are published.

Also be sure to check out the on‑demand webinar, What’s New in NGINX Plus R10?

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.

Desired transition of clients from old server to new server

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:

  • If the transition window has not started, direct the request to the old application server.
  • If the transition window has elapsed, direct the request to the new application server.
  • If the transition is in progress:
    1. Calculate the current (time) position in the transition window.
    2. Calculate a hash for the client IP address.
    3. Calculate the position of the hash in the range of all possible hash values.
    4. If the hash position is greater than the current position in the transition window, direct the request to the new application server; otherwise direct the request to the old application server.

Let’s get started!

Enabling nginScript for NGINX Plus

nginScript is available as a freedynamic module for NGINX Plus subscribers (for open source NGINX, see Enabling 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
  2. Enable the module by including a load_module directive for it in the top‑level (“main”) context of the nginx.conf configuration file (that is, not in the http or stream contexts).

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

    $ sudo nginx -s reload
  4. Enabling nginScript for Open Source NGINX

    Compile the nginScript module from source at the open source repository . Copy the module binary ngx_http_js_module.so to the modules subdirectory of the NGINX root (usually /etc/nginx/modules ). Perform Steps 2 and 3 in the previous instructions for NGINX Plus to configure NGINX and reload.

    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 NGINX and NGINX Plus Configuration for TCP and UDP Applications 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 will continue to load balance between the available servers during the transition window.

    upstream old {
    server 10.0.0.1;
    server 10.0.0.2;
    }

    upstream new {

    server 10.0.0.9;

    server 10.0.0.10;

    }

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

    js_include /etc/nginx/functions.js;

    server {

    listen 80;

    location / {

    set $transition_window_start "Thu, 08 Sep 2016 17:00:00 +0100";

    set $transition_window_end "Thu, 08 Sep 2016 19:00:00 +0100";

    # Returns "old|new" based on window position

    js_set $upstream transitionStatus;

    proxy_pass http://$upstream;

    # Enables nginScript logging

    error_log /var/log/nginx/transition.log info;

    }

    }

    As we are going to use nginScript to determine which upstream group to use, we need to specify where the nginScript code resides. As of NGINX Plus R10, all nginScript code must be located in a separate file and the js_include directive specifies the location of that file. This directive can be specified only once, so all of our nginScript code must be placed in this file.

    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 js_set directive names transitionStatus as the nginScript function that sets the value of the $upstream variable. Note that this directive is not an instruction to call transitionStatus . All NGINX variables are evaluated on demand, that is, at the point during request processing when they are used. So the js_set directive tells NGINX or NGINX Plus how to evaluate the $upstream variable when it is needed.

    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

    We put our nginScript code in /etc/nginx/functions.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 .

    function fnv32a(str) {
    var hval = 2166136261;
    var i = 0;
    for (; i < str.length; ++i ) {
    hval ^= str.charCodeAt(i);
    hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
    }
    return hval >>> 0;
    }

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

    function transitionStatus(req) {
    var vars, window_start, window_end, time_now, timepos, numhash, hashpos;

    // Get the transition window from NGINX/NGINX Plus configuration

    vars = req.variables;

    window_start = new Date(vars.transition_window_start);

    window_end =

    new

    Date(vars.transition_window_end);

    // Are we in the transition time window?

    time_now = new Date.now();

    if ( time_now < window_start ) {

    return "old";

    } else if ( time_now > window_end ) {

    return "new";

    } else {

    // We are in the transition window

    timepos = (time_now - window_start) / (window_end - window_start);

    // Get numeric hash for this client's IP address

    numhash = fnv32a(req.remoteAddress);

    // Calculate the hash's position in the output range (0-1)

    hashpos = numhash / 4294967295; // Upper bound is 32 bits

    req.log("timepos = " + timepos + ", hashpos = " + hashpos);

    //error_log [info]

    // Should we transition this client?

    if ( timepos > hashpos ) {

    return "new";

    } else {

    return "old";

    }

    }

    }

    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 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 Enabling nginScript for NGINX Plus , 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 orcontact us for a live demo.

    Editor – This is the fourth in a series of blog posts that explore the new features in NGINX Plus R10 in depth. This list will be expanded as later articles are published.

    Also be sure to check out the on‑demand webinar, What’s New in NGINX Plus R10?








New