NGINX Full Version

NGINX Conf 2018:为生产环境中的应用配置 NGINX Unit – 用于 Django 项目

NGINX Unit is a fully dynamic application server that can serve multiple languages as well as multiple versions of each language. It’s dynamic in the sense that you use the RESTful JSON API to make changes to its configuration in memory, without service disruption or configuration reloads.

In my presentation at NGINX Conf 2018 in October, I showed how to configure a new application in an existing production environment. Specifically, with WordPress running on PHP, I deployed a Python application that uses the Django framework. I also showed show how you can load configuration both from a file and as specified with an argument to an API call.

This blog includes all of the commands and configuration code I used in the demo, to make it easier for you to adapt to your own deployment.

Prerequisites

For the demo at NGINX Conf, I had the following software installed:

I also had PHP and WordPress installed as the existing application.

Creating the Django Project
  1. Change into the directory where we’re creating our Django project:

    $ cd /var/www/
  2. Use the django-admin startproject command to initialize the new project. We’re calling it djapp.

    $ sudo django-admin startproject djapp
  3. Change into the project directory:

    $ cd djapp
  4. Use the manage.py script to migrate the database for the project, which is necessary for a newly created project. Django uses SQLite by default, and I accept the default in the demo, but you can use any database that meets your project’s needs.

    The manage.py script is installed by the django-admin command we ran in Step 2; it performs the same commands and accepts the same arguments as django-admin, but automatically derives and uses some project‑specific settings, which is helpful. For details, see the Django documentation.

    $ sudo python3 manage.py migrate
  5. Although it’s not strictly necessary for a sample project like this one, we recommend that you create a Django superuser identity:

    $ sudo python3 manage.py createsuperuser
  6. Change into the subdirectory that contains the settings.py file, which was created by the django-admin startproject command in Step 2.

    $ cd /var/www/djapp/djapp
  7. Using your preferred text editor, open settings.py. Here we’re using nano:

    $ sudo nano settings.py

    Find the ALLOWED_HOSTS line and add in the domain name, hostname, or IP address for the application:

    ALLOWED_HOSTS = ['domain-name']

    Also add the following line at the end of the file, to name the directory that stores all static content served by the application (see Step 9).

    STATIC_ROOT = '/var/www/djapp/djapp/static'
  8. Change back to the main project directory (where manage.py resides).

    $ cd ..
  9. Run the manage.py collectstatic command to collect all static files located in the Django project and put them into the STATIC_ROOT location defined in Step 7.

    $ sudo python3 manage.py collectstatic

Configuring NGINX

By default, Django itself serves the static content for a project, but NGINX Open Source and NGINX Plus offer superior performance. Here we configure NGINX Plus, but you can use NGINX Open Source except for one feature noted below.

  1. Change directory to /etc/nginx/conf.d, the conventional location for function‑specific (or in our case, application‑specific) HTTP configuration files:

    $ cd /etc/nginx/conf.d
  2. Create a file called django.conf (again, we’re using nano):

    $ sudo nano django.conf

    Insert the following configuration, which enables caching.

    The configuration also includes two features that are exclusive to NGINX Plus. Uncomment the relevant directives if you are using NGINX Plus and want to take advantage of the features:

    One thing to note is that in the demo at NGINX Conf I specified the IP address of my local machine as the second argument to the proxy_set_header directive. In a production environment, it makes more sense to use the $host variable as shown below.

    # Upstream group for the backend (NGINX Unit running the Python application)
    upstream django_unit {
        zone django_unit 64k;
        server 127.0.0.1:8000;
    }
     
    server {
        listen 8080;
        # Uncomment to collect metrics if using NGINX Plus and the NGINX Plus API
        #status_zone django;
          	
        # enable caching
        proxy_cache django_cache;
        proxy_cache_valid 200 60m;
     
        # root directory for static files
        root /var/www/djapp/djapp;
     
        # proxy to the NGINX Unit backend
        location / {
            proxy_pass http://django_unit;
    
            # Second argument must match your production hostname and the value of 
            # ALLOWED_HOSTS in settings.py
            proxy_set_header Host $host; 
     
            # Uncomment to enable active health checks if using NGINX Plus
            #health_check;
        }
    
        # Location for the static files collected from Django and served by 
        # NGINX Plus; can be empty (as here), because it inherits the value of the 
        # 'root' directive from its parent block
        location /static {
        }
    }
  3. Check the configuration for syntactic validity:

    $ sudo nginx –t
  4. After fixing any errors, reload the configuration:

    $ sudo nginx -s reload

Configuring NGINX Unit

To finish up, we need to configure NGINX Unit to serve the requests to the application.

  1. Run this curl command to display the current NGINX Unit configuration, which is for WordPress running on PHP. I don’t show the output here, but the WordPress configuration appears in Step 6 below, along with the Python application’s configuration, which we’re about to add.

    Note that I use sudo for the curl command, which you may not need to do for most curl commands. Here it’s necessary because to access the UNIX socket we need the read‑write permission that root has on it.

    $ sudo curl --unix-socket /run/control.unit.sock http://localhost/config/
  2. Change to the directory for NGINX Unit configuration files.

    Keep in mind that these files are optional and just a convenient way to load collections of configuration without typing all the data as an argument to a call to the NGINX Unit API. Because the content of the files is uploaded through the API (like all configuration data), NGINX Unit does not know about file locations and cannot automatically read files as it starts (unlike NGINX Open Source and NGINX Plus). Instead, NGINX Unit saves its runtime state in a separate directory.

    $ cd /etc/unit
  3. Create a file called django.config (again, we’re using nano):

    $ sudo nano django.config

    Add the following JSON, which represents our Python application.

    {
          "type": "python",
          "processes": 5,
          "module": "djapp.wsgi",
          "path": "/var/www/djapp"
    }
  4. Run this curl command to load the JSON contained in django.config as a new application object to be managed by NGINX Unit, called djapp:

    $ sudo curl -X PUT --data-binary @/etc/unit/django.config --unix-socket /run/control.unit.sock http://localhost/config/applications/djapp

    In this command:

    • The HTTP PUT method creates a new NGINX Unit configuration object at the location named by the final argument (the URL). See the final bullet below.
    • The --data-binary argument tells curl to load the contents of django.config exactly as provided, preserving newlines and carriage returns, and not doing processing of any kind.
    • The --unix-socket argument defines where the NGINX Unit API is listening. (We use the sudo command because we’re using the default owner of the socket, root.)
    • The final argument locates and names the new application object to populate with the JSON‑formatted configuration data in django.config: config is the top‑level NGINX Unit configuration object, applications the parent for application objects, and djapp the name of the new application object.
  5. Define the listener object for the application. Rather than loading a file of configuration data as in Step 4, we define the data directly on the curl command line, specifying that the djapp application listens on port 8000.

    $ sudo curl -X PUT --data-binary '{"application":"djapp"}' --unix-socket /run/control.unit.sock 'http://localhost/config/listeners/*:8000'
  6. Repeat the curl command from Step 1 to display the NGINX Unit configuration, which now includes our Python application, djapp, highlighted in orange:

    $ sudo curl --unix-socket /run/control.unit.sock http://localhost/config/
    {
          "listeners": {
                "127.0.0.1:8090": {
                      "application": "script_index_php"
                },
     
                "127.0.0.1:8091": {
                      "application": "direct_php"
                },
     
                "*:8000": {
               	      "application": "djapp"
                }
          },
     
          "applications": {
                "script_index_php": {
                      "type": "php",
                      "processes": {
                            "max": 20,
                            "spare": 5
                       },
     
                      "user": "www-data",
                      "group": "www-data",
                      "root": "/var/www/wordpress",
                      "script": "index.php"
                },
     
                "direct_php": {
                      "type": "php",
                      "processes": {
                            "max": 5,
                            "spare": 0
                      },
     
                      "user": "www-data",
                      "group": "www-data",
                      "root": "/var/www/wordpress",
                      "index": "index.php"
                      },
     
                "djapp": {
                      "type": "python",
                      "processes": 5,
                      "module": "djapp.wsgi",
                      "path": "/var/www/djapp"
                }
          }
    }

Summary

In this post we started with NGINX Unit running PHP applications for WordPress in production, and added a Python application. In the demo, I use the NGINX Plus dashboard to show that there is no disruption to the existing applications when a new application is added, but you can use any system‑monitoring tool, such as the ps command, for that purpose. The dynamic nature of NGINX Unit configuration saves resources for your running applications, and ensures zero downtime on new deployments and smooth transition between application versions.

To learn more, visit unit.nginx.org.