How to create an easy Restful API for a simple model in Django(Part V)
How to deploy using Nginx, Gunicorn and Supervisord
In previous posts we created a model in Django, created a way to add/modify/delete/search and filter via the Django admin. Then we created a restful API for this model using Django Rest Framework, we added filter and search functionality into the API and finally we added Swagger documentation so our clients can understand our API. But we did not deploy this in any machine. We only use manage.py runserver to check that is working.
Let's deploy this using Nginx, Gunicorn and Supervisord.
First of all, why Nginx and not our deployment with unicorn directly. Well, Django is not very good at deploying static files and you will have static files if your are doing APIs or the old web development. So we put an Nginx server before our Gunicorn servers to do load balancing and to serve our static files.
We prefer to use Ubuntu 14.04 as the Operating System to deploy Django Applications because installing Python/Django packages/apps is very simple.
Once that you have an ubuntu server running(may be in Amazon Web Services EC2) you can start installing Nginx, Gnicorn, Supervisord.
You can follow Digital Ocean tutorial in order to install Nginx but here we will say the simple task.
sudo apt-get update
sudo apt-get install nginx
It is pretty obvious what these two lines means so move on to install the the others parts of the project.
pip install Django==1.9
pip install djangorestframework==3.1.3
pip install django-rest-swagger==0.3.2
pip install gunicorn==18.0
sudo apt-get install supervisor
Now that we have everyting we need installed we should be able to move our code to a folder in ubuntu. I use
/var/www/django/, so I use make /var/www/django. Personally, I have my code in github so I do these tree things in order to get my code in Ubuntu:
mkdir /var/www/django/
cd /var/www/django/
git clone https://github.com/edilio/citiesprj
cd citiesprj
Now we collect statict files into a folder that we will serve using Nginx
./manage.py collectstatic
In order to be able to run Django with DEBUG = False we need to make sure we have
ALLOWED_HOSTS = ['*']
In a real deploying you should use your domain there but let's match eveything so we can move into creating the full deployment.
Suppose we have set our static root variable in Django settings file to
STATIC_ROOT = '/var/www/html/media/cities/static/'
Then our collectstatic command will move all the static files(css/js/images) to this root folder:
So we just need to configure our Nginx to serve our django but also our static files.
create a new file in conf.d
sudo vi /etc/nginx/conf.d/cities.conf
server { listen 80; server_name yourdomain.com; access_log /var/log/cities-nginx-access.log; error_log /var/log/cities-nginx-error.log; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /static/ { alias /var/www/html/media/cities/static/; } }
Now you can use:
sudo service nginx reload
and you have the new configuration. You should be able to navigate to you domain and you will received a gateway error because in / we are saying that it needs to go to port 8000 but we don't have any appplication running on port 8000 yet.
In order to prove that we should be working with the tools that we know we could use:
cd /var/www/django/citiesprj
./manage.py runserver 0.0.0.0:8000
Now everything should be working but this is not proper way to do it. Let's improve it.
Time for using gunicorn.
Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork worker model ported from Ruby's Unicorn project. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy.
With gunicorn we can specify how many workers(green 'threads') we want to use to serve our API. The proper number depends on a simple calculus depending on how many CPUs our hardware, VM or docker container has. The recomended number is (2x$num_cores)+1
but it may be different for you. Please, dig in the Gunicorn documentation and test what is bet for you.
So we can now stop manage.py and use:
gunicorn -w 2 -b 127.0.0.1:8000 -n cities citiesprj.wsgi:application
Now we have our API running using usin gunicorn but we have close our ssh connection to our server we the process will stop and we are not longer serving anything in Django. The Nginx will still be serving static files but our API is not working.
Supervisor will rescue us in this journey now.
sudo vi/etc/supervisor/conf.d/cities.conf
Enter these lines.
[supervisord] nodaemon = true [program:cities] command = gunicorn -w 2 -b 127.0.0.1:8000 -n cities citiesprj.wsgi:application --log-level=debug
--chdir=/var/www/django/citiesprj stdout_events_enabled = true stderr_events_enabled = true
As you can see you are defining a new program called cities and how to run it and supervisor will ensure that the program will be running all the time. So it for some reason your app crash supervisor will start the app again
sudo service supervisor start &
In order to control your program you can use now:
sudo supervisorctl start cities
sudo supervisorctl stop cities
sudo supervisorctl restart cities
I think this is fair enough if this is the first time you try to deploy a website/api using this technology so I will stop now but be prepare for next posts about how know what is happening in your site using logs and Elastic Search
Let me know if it is helpful. I know if is too simple for some and too complicated for begginers.