Sunday, March 27, 2016

Bottle UWSGI Nginx Configuration

Preface:
Before starting to read this article, you should know basically this article is a "fork" of Michael Lustfield's article Bottle + UWSGI + Nginx Quickstart.

The reason I want to modify this article is because I think it will be more beneficial if I add more notes I found during building up the whole server. Also I prefer another way to configure uwsgi from the scratch.

Introduction
Bottle is a Python micro framework easy for deployment though its built-in http server has poor performance and support for https is not easy to implement. In the other side, Nginx is a high performance server software and can be easily configured to support https. But Nginx cannot speak Python. In order to support Python based micro framework, uwsgi is needed as midware to translate Python script to Nginx.

Installing Stuff
I'm going to be assuming the use of Ubuntu 14.04 and python3 and also you have good knowledge about Linux bash and Python. It's easy enough to adjust.
apt-get install nginx  
pip3 install uwsgi  
pip3 install bottle  
pip3 command ensures you getting the latest uwsgi and bottle releases.

Privilege
Before continue to detailed setup.You should know privileges regarding nginx, uwsgi, and bottle.

If you want to use unix socket file as a tunnel for communication between nginx and uwsgi, you have to make sure both nginx and uwsgi have write/read privileges to the shared socket.

Usually nginx belongs to www-data group. So it is suggested to add current user to www-data group.
usermod -a -G www-data %username
%username is your linux user name.

If you get "502 bad gateway" errors while you think configurations are OK, try to use "ls -l" to verify if the socket is writable to www-data group.

Your First Bottle Application
I tend to start with a basic structure:
/var/www/bottle/  
   plugins/  
     __init__.py  
   static/  
     css/  
     files/  
     images/  
     js/  
   views/  
     base.tpl  
     page.tpl  
   app.py  
Whether I have anything in the directories or not, they exist. It's just how I make sure I keep things consistent across applications.

Don't forgtet to run:
chown -R www-data:www-data /var/www  
#change user and group of folder which contains bottle micor-framework to www-data.  
mkdir /run/uwsgi  
chown -R www-data:www-data /run/uwsgi  
#/run/uwsgi/sock is unix socket file that is going to be used by uwsgi loader in order to communicate with nginx.  
A basic skeleton of app.py will look something like this:
#!/usr/bin/python  
'''  
A basic bottle app skeleton  
'''  
from bottle import route, template  
@route('/static/')  
def static(filename):  
  '''  
  Serve static files  
  '''  
  return bottle.static_file(filename, root='./static')  
@route('/')  
def show_index():  
  '''  
  The front "index" page  
  '''  
  return 'Hello'  
@route('/page/')  
def show_page(page_name):  
  '''  
  Return a page that has been rendered using a template  
  '''  
  return template('page', page_name=page_name)  
if __name__ == '__main__':  
  bottle.run(host="0.0.0.0", port=8080)  
else  
  app = application = bottle.default_app()  
"""why you needs app = application = ... in uwsgi mode?  
because by default, uwsgi loader search for app or application when it looks into a python script.  
if it is a Flask deployment, use app = Flask(__name__) instead."""
Try it out!:
python3 app.py  
You'll see the application start running. Go to example.com:8080/. Neat, huh?

The Templating System
Bottle has a bunch of templating options. For now, we're only going to touch the most basic option.
views/page.tpl:
You are visiting {{page_name}}!
%rebase base
views/base.tpl:
<html dir="ltr" lang="en"> <head> <title>My Site!</title> </head> <body> <div id="pagebody"> %include </div> </body> </html> This is obviously very basic, but it will get you started. Check out the Bottle Docs for more information. The templating options are endless!
Now that you have this done, restart app.py and visit example.com:8080/page/foo. You should be seeing a rather blank looking page that says "You are visiting foo" with the title "My Site!"

Adding UWSGI
The UWSGI configuration is pretty simple. See the UWSGI Docs for more details information.
Edit /var/www/bottle/uwsgi.ini:
[uwsgi]
socket = /run/uwsgi/sock
;;for bottle, it is safe to use socket protocol.
chdir = /var/www/bottle
master = true
plugins = python
;;plugins are built python*.so libary for uwsgi.
;plugins-dir = path
;;specify python plugins by plugins-dir if you want it.
file = app.py
;;specify the python file to be used by uwsgi.
;;suppose there are app1.py app2.py app3.py all contains app routine.
;;it is possible to use "mount" to define different routines to handle different page requests.
;mount = /app1=app1.py
;mount = /app2=app2.py
;mount = /app3=app3.py
;;generally flask/bottle apps expose the 'app' callable instead of 'application'
;callable = app
;;tell uWSGI to rewrite PATH_INFO and SCRIPT_NAME according to mount-points
manage-script-name = true

;chmod-socket=664
uid = www-data
gid = www-data
;;specify uid and gid, assuming /www/var belongs to www-data:www-data. it is better to run uwsgi in non-root mode.
;pythonpath = ..
;;specify python path.
;processes = 4
;thread = 2
;stats = 127.0.0.1:9191
uwsgi --ini uwsgi.ini
You should see some logging information.

Adding Nginx
I prefer using the /etc/nginx/sites-enable directory for my configurations. You can do as you wish on your server.
Edit /etc/nginx/sites-enable/default:
upstream _bottle {
    server unix:/run/uwsgi/sock;
}
#upstream defines common block that can be shared by different derivatives. 

server {
    listen [::]:80;
    listen 80;
    server_name localhost;
    #make sure it is accessible through localhost
    root /var/www/bottle;

    location / {
        try_files $uri @uwsgi;
        #try files will try to load $uri, if it does not exist, then @uwsgi will be called instead.
    }

    location @uwsgi {
        include uwsgi_params;
        #include uwsgi parameters.
        uwsgi_pass _bottle;
        #tell nginx to communicate with uwsgi though unix socket /run/uwsgi/sock.
    }
}
#it is possible to configure this server to https as long as you get certification from letsencrypt. Search nginx + letsencrypt for necessary info if you are interested.

In our bottle application, we defined a route for static content. However, it's better to have nignx serve this data so that we can avoid making python do any work. That's why we use try_files in the location block. You want that in your bottle application for development, but when we deploy, it won't actually get used.
Then restart the service:
nginx -s reload
You'll now be able to access your bottle application from the internet through nginx.
In fact it is possible to verify it though command:
curl localhost:80
w3m localhost:80

start uwsgi as service
It is possible to run uwsgi on boot with upstart.
mkdir /etc/uwsgi
ln -s /path/to/uwsgi.ini uwsgi.ini
create /etc/init/uwsgi.conf,
# Emperor uWSGI script

description "uWSGI Emperor"
start on runlevel [2345]
stop on runlevel [06]

respawn

exec uwsgi --master --die-on-term --emperor /etc/uwsgi
done!

upstart will call uwsgi in emperor mode, then uwsgi will create its service based on ini file.

When you want to restart uwsgi process after modifying the python file, never use the "touch-reload" like option from uwsgi. This process could be bugy, you may find it won't work at all and create all kinds of problems. Don't waste your time. Use following commands instead:
initctl stop uwsgi
initctl start uwsgi
the first command ensures uwsgi service is fully stopped.
and the second command will start uwsgi service again.

No comments: