Déployer Django en production avec Nginx, Gunicorn et Supervisor
Django, c'est bien. Par contre, déployer un projet Django en production, ce n'est pas toujours évident, surtout que la doc n'est pas forcément toujours très à jour à ce sujet.
La dernière fois que j'ai dû effectuer un déploiement bien propre en production, j'ai un peu regretté de ne pas avoir sous la main un beau tuto bien récent, bête et méchant. Comme j'ai dû rédiger la doc complète de l'opération, en voici la version française.
Au menu : du Nginx en frontal et reverse proxy vers Gunicorn qui sert notre projet tournant dans un virtualenv (foutaises !). On y va ?!
Installer les paquets nécessaires
Que vous passiez par de l'hébergement mutualisé, un conteneur OpenVZ, une instance EC2 ou autre, je considère que vous démarrez avec un système Debian stable vierge.
Commençons par installer les paquets nécessaires (et supprimer l'inutile).
apt-get update apt-get upgrade apt-get purge apache2 apt-get install build-essential libpq-dev python-dev apt-get install postgresql postgresql-contrib nginx git supervisor
Installer Node
Ok, ok, ça peut sembler bizarre de commencer par installer Node.js. le fait est que j'utilise l'excellent django-pipeline pour gérer la compression des assets, qui lui même utilise yuglify pour la minification du js. Et le meilleur moyen d'installer yuglify, c'est encore avec npm.
cd /opt/ wget http://nodejs.org/dist/latest/node-<latest_version>-linux-x64.tar.gz tar -zvxf node-*.tar.gz cd node-* ./configure make make install
Installons yuglify dans la foulée.
npm install -g yuglify
(Je sais, je sais, c'est vraiment sortir la Grosse Bertha pour tuer une mouche. Passons.)
Création de la base de données
J'utilise PostgreSQL pour gérer les données. Tout autre choix devra être justifié par une longue dissertation de 10 pages. En attendant, nous allons créer l'utilisateur et la BD.
su - postgres createuser -P Enter name of role to add: monprojet Enter password for new role: monprojet Enter it again: monprojet Shall the new role be a superuser? (y/n) n Shall the new role be allowed to create databases? (y/n) n Shall the new role be allowed to create more new roles? (y/n) n createdb --owner monprojet monprojet
Installer Python
C'est parti pour l'installation de notre langage préféré. On commence par pip et virtualenv:
cd wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py python get-pip.py pip install virtualenv virtualenvwrapper
Créons l'utilisateur système:
adduser monprojet --disabled-password su - monprojet
Ajoutons ces lignes à la fin du fichier ~/.profile:
export WORKON_HOME=~/.virtualenvs mkdir -p $WORKON_HOME source `which virtualenvwrapper.sh`
Puis:
source ~./profile
Installer l'application
Création du virtualenv, git clone, etc. Que du classique, les chemins et urls seront bien entendu à adapter en fonction de la configuration de votre projet.
cd mkvirtualenv monprojet git clone https://git.monprojet.com/monprojet.git monprojet cd monprojet pip install -r requirements/production.txt cd src export DJANGO_SETTINGS_MODULE=core.settings.production python manage.py collectstatic python manage.py syncdb
Voici un exemple de fichier requirements/production.txt:
-r base.txt gunicorn==18.0 psycopg2==2.5.2
Faire tourner l'application
Nous allons utiliser Gunicorn, un serveur d'application WSGI développé en Python. C'est lui qui va effectivement servir notre application. En fait, Gunicorn est déjà installé, puisqu'il était listé dans le fichier requirements.txt. Si ce n'était pas le cas, toutefois :
pip install gunicorn
Django fournit directement une intégration à Gunicorn, aucune autre opération n'est donc nécessaire. C'est presque de la triche tellement c'est simple. Il faut évidemment activer l'application adéquate dans votre fichier de settings:
INSTALLED_APPS += ( 'gunicorn', )
Configurer Supervisor
Supervisor est un outil qui permet de contrôler les processus qui doivent tourner sur notre machine. Nous l'utilisons ici pour démarrer Gunicorn. Créez un fichier de config dans /etc/supervisor/conf.d/monprojet.conf. Voici un exemple fonctionnel :
[program:monprojet] environment=DJANGO_SETTINGS_MODULE='core.settings.production' directory=/home/monprojet/monprojet/src command=/home/monprojet/.virtualenvs/monprojet/bin/python manage.py run_gunicorn user=monprojet autostart=true autorestart=true stdout_logfile=/var/log/supervisor/monprojet.log redirect_stderr=true
On lance tout ça :
supervisorctl reread supervisorctl reload
Note : ces deux commandes seront à retaper en cas de modification de la conf de Supervisor. Notez que par défaut, run_gunicorn lance gunicorn sur le port 8000 en localhost.
Configurer le serveur web
Passons à la configuration du serveur web. Je recommande fortement Nginx qui allie souplesse, légereté et puissance. Une fois qu'on y a goûté, difficile de revenir vers Apache et autres éléphants (et non, ce billet n'est pas sponsorisé).
D'abord, je suppose que notre projet Django est le seul virtual host servi. On va donc modifier le virtual host par défaut dans /etc/nginx/sites-available/default.
server { listen 80 default_server; return 444; }
Cette configuration signifie que tout ce qui ne tombera pas sur un virtual host bien défini (accès par l'url directe, par exemple) sera pûrement rejeté par Nginx.
Nous allons ensuite créer un virtual host spécifique pour notre projet dans /etc/nginx/sites-available/monprojet.
upstream monprojet { # En supposant que le serveur WSGI tourne sur le port 8000 server localhost:8000; } # Redirection de monprojet.com vers www.monprojet.com server { server_name monprojet.com return 301 $scheme://www.monprojet.com$request_uri; } server { server_name monprojet.com access_log /var/log/nginx/monprojet.access.log; error_log /var/log/nginx/monprojet.error.log; location /static/ { alias /home/monprojet/monprojet/public/static/; } location /media/ { alias /home/monprojet/monprojet/public/media/; } location / { proxy_pass http://monprojet; proxy_redirect off; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
Nous adoptons ici une configuration en reverse-proxy. Nginx est utilisé en serveur frontal, pour servir les fichiers statiques et balancer la connexion au serveur WSGI. Vous allez voir, ça poutre des loutres.
N'oublions pas de créer le lien symbolique pour l'activer :
ln -s /etc/nginx/sites-available/monprojet /etc/nginx/sites-enabled/
Ni de redémarrer Nginx :
/etc/init.d/nginx restart
Et voilà !
Et vous voilà avec une configuration qui tronçonne du castor, capable d'encaisser un bon paquets de requêtes et de scaler sans trop de soucis. Était-ce vraiment si compliqué ? Non, non, ne me remerciez pas.
à++;