Django + Gunicorn + Nginx Deployment Path

Posted by wyvern on Wed, 11 Sep 2019 03:46:52 +0200

Preface

Recently, I have successfully migrated my personal website from Flask to Django. It was about four years ago that I first contacted Django. I remember that routing configuration in Django was done using regularity at that time, but I was particularly tired of it, so I decided to drop the hole.Then at the beginning of this year, I wrote a personal website with Flask. At the beginning, the function was relatively simple. I decided to adopt the routing configuration and deployment rules as they were very convenient.But later, when I wanted to add more and more features, I found it more and more difficult to control. Just recently, I took a little look at the changes in Django over the years. The latest version 2.2 is still good. Routing rules and Flask are consistent, so I'm back in the hole.

At present, the basic functions of my personal website have been migrated.However, during the deployment, I encountered some problems and read some solutions online, either too messy or too old, which I personally feel is no longer applicable.So record my deployment here.

deploy

Much of the web is deployed using UWSGI, but I personally prefer Gunicorn, so here's just a record of how Django + Gunicorn + Nginx is deployed on Ubuntu.

Step One

Upload site source to target server

Since my source code is hosted on Github, I can simply execute the following command to clone my site source code to the server.

git clone https://github.com/your-name/repo-name.git

# Enter Project Directory
cd repo-name

# Create and activate virtual environments
python3 -m virtualenv venv
source venv/bin/activate

# Installation Project Dependency
pip install -r requirements.txt

The dependent packages currently used on my website are as follows:

autopep8
Django
django-bootstrap4
django-ckeditor
gunicorn
Markdown
Pillow
python-slugify
requests

There is a pit to note here. If you are using awesome-slugify, try python-slugify, because awesome-slugify may not be installed properly on your server. BUG can refer to: Clashes with python-slugify package.

Step 2

Modify project-related configurations and collect static resources

Since I need to deploy my Web site to a production environment, I need to turn off Django's debugging mode and modify the static resource-related configuration as follows:

  • settings.py
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')

DEBUG = os.environ.get('DJANGO_DEBUG', False)

TEMPLATE_DEBUG = os.environ.get('DJANGO_TEMPLATE_DEBUG', False)

ALLOWED_HOSTS = ["*"]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

Then execute the following command for static resource collection:

python manage.py collectstatic

After that, I need to create a configuration for the Gunicorn process as follows:

  • gunicorn.conf.py
# install
# sudo pip3 install gunicorn

import sys
import os
import logging
import logging.handlers
from logging.handlers import WatchedFileHandler
import multiprocessing

BASE_DIR = '/home/hippie/hippiezhou.fun/src'
sys.path.append(BASE_DIR)

LOG_DIR = os.path.join(BASE_DIR, 'log')
if not os.path.exists(LOG_DIR):
    os.makedirs(LOG_DIR)

# Bound ip and port
bind = "0.0.0.0:8000"

# Run in the background as a daemon
daemon = True

# Maximum number of pending connections, 64-2048
backlog = 512

# overtime
timeout = 30

# Debugging Status
debug = False

# The destination working directory gunicorn will switch to
chdir = BASE_DIR

# Work process type (default is sync mode, also includes eventlet, gevent, or tornado, gthread, gaiohttp)
worker_class = 'sync'

# Number of worker processes
workers = multiprocessing.cpu_count()

# Specify the number of threads opened per worker process
threads = multiprocessing.cpu_count() * 2

# Log level, which refers to the level of error logs (debug, info, warning, error, critical) that cannot be set for accessing logs
loglevel = 'info'

# Log format
access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"'
# Each of these options has the following meanings:
'''
h          remote address
l          '-'
u          currently '-', may be user name in future releases
t          date of the request
r          status line (e.g. ``GET / HTTP/1.1``)
s          status
b          response length or '-'
f          referer
a          user agent
T          request time in seconds
D          request time in microseconds
L          request time in decimal seconds
p          process ID
'''

# Access log files
accesslog = os.path.join(LOG_DIR, 'gunicorn_access.log')
# Error log file
errorlog = os.path.join(LOG_DIR, 'gunicorn_error.log')
# pid file
pidfile = os.path.join(LOG_DIR, 'gunicorn_error.pid')

# Access log file,'-'for standard output
accesslog = "-"
# Error log file,'-'for standard output
errorlog = "-"

# Process name
proc_name = 'hippiezhou_fun.pid'

# More configurations please: gunicorn -h to view

You can then launch our website by:

# Startup mode (first you need to switch to the project root directory, which is in the same directory as manage.py):

gunicorn -c gunicorn.conf.py website.wsgi:application

# or
gunicorn website.wsgi:application -b 0.0.0.0:8000 -w 4 -k gthread

# or
gunicorn website.wsgi:application -b 0.0.0.0:8000 -w 4 -k gthread  --thread 40 --max-requests 4096 --max-requests-jitter 512

# View process
ps aux | grep gunicorn

Step Three

Configure Nginx

Through the first two steps, we can successfully run our website, but at present it can only be accessed internally, so we need to use Nginx as a reverse proxy for external access.

Perform the following commands for installation and configuration

sudo apt-get install nginx

sudo service nginx start

# Backup Default Configuration
sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak

# Start Vim to modify our site configuration
sudo vim /etc/nginx/sites-available/default

The example configuration is as follows:

server{
        ...
        server_name hippiezhou.fun *.hippiezhou.fun;
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
        ...

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                # try_files $uri $uri/ =404;
                proxy_pass         http://127.0.0.1:8000; #Here you want to be consistent with your gunicore's ip and port
                proxy_redirect     off;

                proxy_set_header   Host                 $host;
                proxy_set_header   X-Real-IP            $remote_addr;
                proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
                proxy_set_header   X-Forwarded-Proto    $scheme;
        }

        location /static {
                alias /root/hippiezhou.fun/src/staticfiles; # This time you need to configure the absolute path of the static resource corresponding to your site
        }

        location /media {
                alias /root/hipiezhou.fun/src/media; # If your site has upload capabilities, you need to configure the node and point to the target path
        }

        ...
}

Once the configuration is complete, do the following to get our site up and running

# If the site does not start executing the command
gunicorn -c gunicorn.conf.py website.wsgi:application

sudo nginx -t
sudo service nginx restart

If nothing unexpected happens, the site should be accessible. If static resources are still inaccessible, open the developer's tool to see what went wrong.

  • If it's a 404 problem, make sure your settings configuration is consistent with the one listed above.
  • If this is a 403 problem, it should be that Nginx does not have access to the static resource you specified. You need to modify the user type of Nginx and execute the following command in person
sudo vim /etc/nginx/nginx.conf

Modify the value after user to root and restart Nginx.

Finally, without much discussion on how to configure HTTPS, you can directly list the example scripts:

sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository universe
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot python-certbot-nginx

sudo certbot --nginx

# sudo certbot renew --dry-run

sudo ufw allow https

sudo systemctl restart nginx

summary

In the process of deployment, in fact, the most common problem encountered is that static resources can not be problem, but many articles on the Internet are different, and some of them are written incorrectly.So here's a summary.Fortunately, everything went well.It's like filling a hole four years ago.

Finally, for an advertisement, welcome to my personal website: hippiezhou.fun

Topics: Python Nginx sudo Django