📜 ⬆️ ⬇️

Multi-hosting django applications using nginx + uwsgi + virtualenv

Task: to deploy several django-projects using different versions of django and different versions of python on the same server.

The instructions are for OS Ubuntu 12.04.

Training


To begin with we put the versions of python that we are interested in.

Required packages for compilation:
sudo apt-get install zlib1g zlib1g-dev zlibc libssl-dev 

We put the python, I put 2.7.4 and 3.3.1
 wget http://python.org/ftp/python/2.7.4/Python-2.7.4.tar.bz2 tar -xf Python-2.7.4.tar.bz2 cd Python-2.7.4 ./configure --prefix=/opt/python2.7/ --enable-unicode=ucs4 make && make install 

 wget http://python.org/ftp/python/3.3.1/Python-3.3.1.tar.bz2 tar -xf Python-3.3.1.tar.bz2 cd Python-3.3.1 ./configure --prefix=/opt/python3.3/ make && make install 

Let's create directories for our project configs.

Nginx installation


 apt-get install nginx-full 

Why nginx-full and not nginx? The nginx-full already includes a module for working with uwsgi.
')
You need to tell nginx where to download virtual host configs.
Open /etc/nginx/nginx.conf.
After include /etc/nginx/sites-enabled/*; add line include /home/hosting/.nginx/*.conf;
Now you need to create nginx-configs of virtual hosts.

Config example:
Hidden text
 server { server_name project1.com; access_log /var/log/project1.access.log; error_log /var/log/project1.error.log; location / { uwsgi_pass unix:/tmp/project1.sock; include /etc/nginx/uwsgi_params; } location /static/ { alias /home/hosting/project1/static/; } location /media/ { alias /home/hosting/project1/media/; } } 

You must give the rights to the /home/hosting/.nginx directory to the www-data user (or to the user under which nginx is working).
 chown -R www-data:www-data /home/hosting/.nginx/ 

Run nginx
 service nginx start 

Install virtualenvwrapper


virtualenvwrapper is a handy wrapper around virtualenv.
Set pip if not yet worth it:
 sudo apt-get install python-pip 

Put virtualenvwrapper:
 pip install virtualenvwrapper 

In ~ / .bashrc add:
 export WORKON_HOME=/home/hosting/.virtualenvs/ source /usr/local/bin/virtualenvwrapper.sh 

We log in to the console so that .bashrc is loaded. Now we need to have the mkvirtualenv command in the console.

We place project files in directories:

For each project we will create a virtual environment. Let's say project1 will work on python 2.7, and project2 on 3.3.
 mkvirtualenv project1 -p /opt/python2.7/bin/python deactivate mkvirtualenv project2 -p /opt/python3.3/bin/python3 deactivate 

For each project we put dependences in a virtual environment. (In my case, dependencies are written in the requirements.txt file in the root of each project)
 workon project1 cd /home/hosting/project1 pip install -r requirements.txt workon project2 cd /home/hosting/project2 pip install -r requirements.txt 

Customize uwsgi.


We will configure it in the emperor mode (--emperor), since This mode is specially designed for multi-hosting.

In emperor mode, uwsgi will automatically load configs from the specified directory, which is convenient, that is, we run uwsgi once, and then it creates processes for all applications in configs.

By default, uwsgi executes the project code by the Python in the current environment, so we will need to run uwsgi from virtualenv.
Since we have several different versions of python, the uwsgi launched, for example, from under the python 2.7 will not be able to serve the django application with the python environment 3.3.
So we will create an emperor for each version of the python, and we will group the application configs according to the interpreter version.

Create virtual environments for emperors.
 mkvirtualenv python27 -p /opt/python2.7/bin/python deactivate mkvirtualenv python33 -p /opt/python3.3/bin/python3 deactivate 

Now you need to put uwsgi in each virtualenv and configure it.
 workon python27 pip install uwsgi workon python33 pip install uwsgi 

Create configs directories for each uwsgi-emperor.
 mkdir /home/hosting/.uwsgi/python27 mkdir /home/hosting/.uwsgi/python33 

Create uwsgi-configs for each project.
/home/hosting/.uwsgi/python27/project1.ini
Hidden text
 [uwsgi] protocol = wsgi master = true processes = 1 #    socket = /tmp/project1.sock #   pythonpath   , . uwsgi         virtualenv pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/site-packages/setuptools-0.6c11-py2.7.egg pythonpath = /home/hosting/.virtualenvs/project1/lib/python27.zip pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7 pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/plat-linux2 pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/lib-tk pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/lib-old pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/lib-dynload pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/site-packages # -  virtualenv     ,      pythonpath = /opt/python2.7/lib/python2.7 chdir = /home/hosting/project1 virtualenv = /home/hosting/.virtualenvs/project1 env = DJANGO_SETTINGS_MODULE=settings module = django.core.handlers.wsgi:WSGIHandler() no-site = true vhost = true chmod-socket = 666 

Config of the second project - by analogy. The file name must end with .ini, otherwise uwsgi will not pick up this config.

Now you need to register uwsgi as a service in the system. I used upstart, it is in ubunt out of the box.
Create two config files:
/etc/init/uwsgi27.conf
 description "uWSGI Emperor (python 2.7)" start on runlevel [2345] stop on runlevel [06] exec /home/hosting/.virtualenvs/python27/bin/uwsgi --master --emperor /home/hosting/.uwsgi/python27 --logto /var/log/uwsgi27.emperor.log 

/etc/init/uwsgi33.conf
 description "uWSGI Emperor (python 3.3)" start on runlevel [2345] stop on runlevel [06] exec /home/hosting/.virtualenvs/python33/bin/uwsgi --master --emperor /home/hosting/.uwsgi/python33/ --logto /var/log/uwsgi33.emperor.log 

Users and Security


From root, we will only have “imperial” processes, and the projects themselves will be under their users.

Create a user for each project.
 adduser --no-create-home --disabled-login --disabled-password www-project1 adduser --no-create-home --disabled-login --disabled-password www-project2 

Add uid gid parameters to each of uwsgi ini-configs.
 uid = www-project1 #  gid = www-project1 #  

Set the correct access rights
Hidden text
 chown -R www-data:www-data /home/hosting/.nginx chmod -R 770 /home/hosting/.nginx chown -R root:root /home/hosting/.uwsgi chmod -R 770 /home/hosting/.uwsgi chown -R root:root /home/hosting/.virtualenvs/python27 /home/hosting/.virtualenvs/python33 chmod -R 775 /home/hosting/.virtualenvs chown -R www-project1:www-project1 /home/hosting/project1 /home/hosting/.virtualenvs/project1 chown -R www-project2:www-project2 /home/hosting/project2 /home/hosting/.virtualenvs/project2 

Run uwsgi
 service uwsgi27 start service uwsgi33 start 

Check - everything should work.
If something does not work, we look at the nginx logs specified in the project config, and the uwsgi-emperor logs.
A sign that uwsgi has successfully deployed the application is the presence of the line WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x135e280 pid: 21737 (default app) in the uwsgi log.
To add a new application, you need to create a config in .nginx and .uwsgi and restart nginx. uwsgi will pick up the new config itself.

References


projects.unbit.it/uwsgi/wiki/MultiPython
projects.unbit.it/uwsgi/wiki/DynamicVirtualenv
auphonic.com/blog/2011/06/18/django-deployment-nginx-uwsgi-virtualenv-and-fabric
eshlox.net/en/2012/09/11/nginx-uwsgi-virtualenv-and-django-ubuntu-1204
uwsgi-docs.readthedocs.org/en/latest/Emperor.html

PS


The method described in the article is not very beautiful in terms of architecture; Initially, I hoped to get by with one uwsgi-emperor and resolve the version of the interpreter with the plugin parameter in the application config. But I didn’t manage to build a uwsgi plugin for python, so I had to do it differently.

Source: https://habr.com/ru/post/179831/


All Articles