Imaginons brièvement que vous ayez un superbe projet écrit avec Django tel que, au hasard, Croisé dans le Métro. Il se trouve que vous avez plusieurs problèmes :
- Vous aimeriez déployer votre projet sur votre super-ordinateur sans difficultés.
- Vous aimeriez éviter le casse-tête des nombreuses et éclectiques dépendances de votre projet et qui ont la sale habitude de changer régulièrement.
- Vous aimeriez aussi éviter de devoir expliquer à Alfred comment installer ces mêmes dépendances sur sa machine dès que vous modifiez quelque chose.
Il s’avère que la solution est presque évidente, la voici étape par étape.
Pip
Pip est un easy_install sur-vitaminé, il va nous permettre de décrire les dépendances de notre projet et se chargera ensuite de les installer pour nous de la façon la plus agréable possible. Mais d’abord commençons par l’installer :
$ sudo easy_install pip
Ensuite, direction la racine de notre projet, nous allons supposer qu’il contient déjà un dossier qui contient l’ensemble de votre projet. Nous allons donc créer un dossier deploy qui va contenir nos différents fichiers décrivant nos dépendances pour chaque déploiement.
$ mkdir deploy
$ touch production.txt development.txt
L’idée est que chaque fichier décrit chaque dépendance nécessaire à votre projet pour tourner dans l’environnement correspondant :
$ nano production.txt
ipython==0.9.1
python-memcached>=1.44
perlinpinpin>=0.1
-e git://github.com/brosner/django-announcements.git@master#egg=announcements
-e svn+http://code.djangoproject.com/svn/django/branches/releases/1.0.X#egg=django
$ nano development.txt
ipython==0.9.1
perlinpinpin>=0.1
fabric==0.1.1
-e git://github.com/brosner/django-announcements.git@master#egg=announcements
-e svn+http://code.djangoproject.com/svn/django/branches/releases/1.0.X#egg=django
Nous venons de décrire nos dépendances basiques, soit en précisant le nom du paquet et une exigence sur sa version. Nous pouvons aussi préciser des dépôts subversion, git, mercurial et bazaar, en précisant l’URL de celui-ci et facultativement un indicateur (numéro de révision, commit ou branche). Vous en apprendrez bien plus dans la documentation de pip.
Virtualenv
Maintenant que nous pouvons décrire les dépendances de notre projet, nous allons créer un environnement virtuel (du moins pour tout ce qui est relatif à python) afin nous isoler et de nous libérer du carcan de l’environnement python de notre OS.
Mais d’abord, l’installation :
$ sudo pip install virtualenv
Downloading/unpacking virtualenv
Downloading virtualenv-1.3.3.tar.gz (1.0Mb): 1.0Mb downloaded
Running setup.py egg_info for package virtualenv
Installing collected packages: virtualenv
Running setup.py install for virtualenv
Installing virtualenv script to /Users/tim/Projects/app/env/bin
Successfully installed virtualenv
Nous allons initialiser notre environnement virtuel à la racine de notre projet dans un dossier nommé env:
$ virtualenv --clear --no-site-packages env
Not deleting env/bin
New python executable in env/bin/python
Installing setuptools............done.
Pour ceux qui préféreraient préciser un python en particulier :
$ virtualenv --clear --no-site-packages env -p /usr/local/bin/python2.6
Running virtualenv with interpreter /usr/local/bin/python2.6
Not deleting env/bin
New python executable in env/bin/python
Installing setuptools...........done.
Nous allons maintenant installer les dépendances que nous avons décrites plus haut :
$ pip install -E env -r deploy/development.txt
[..]
Successfully installed
Si votre environnement virtualenv n’existe pas, pip se fera un plaisir de le créer pour vous (tant que virtualenv est installé).
Nos dépendances sont bien installées, il nous reste à activer notre environnement virtuel, pour pouvoir en profiter :
$ source env/bin/activate
(env) $ python
Python 2.5.1 (r251:54863, Feb 6 2009, 19:02:12)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.
>>> import django
>>> django.file’/Users/tim/Projects/app/env/src/django/django/init.pyc’
>>>
$ source env/bin/activate
(env) $ python app/manage.py runserver
Vous pouvez ajouter à volonté progressivement d’autres dépendances :
$ pip install -E env “markdown>=2.0.1”
Downloading/unpacking markdown>=2.0.1
Downloading Markdown-2.0.1.tar.gz (71Kb): 71Kb downloaded
Running setup.py egg_info for package markdown
Installing collected packages: markdown
Running setup.py install for markdown
changing mode of build/scripts-2.5/markdown from 664 to 775
changing mode of /Users/tim/Projects/app/env/bin/markdown to 775
Successfully installed markdown>=2.0.1
Il vous suffira ensuite de freeze votre environnement grâce à pip pour établir un nouveau fichier correspondant à votre nouvel environnement :
$ pip freeze -E env -r deploy/development.txt deploy/requirements.txt
Virtualenv & WSGI
Le secret pour utiliser notre environnement virtuel avec apache et mod_wsgi réside dans notre handler wsgi :
$ nano site.py
import os, sys, site
site.addsitedir('/var/www/app.com/current/env/lib/python2.5/site-packages')
sys.path.append('/var/www/app.com/current')
sys.path.append('/var/www/app.com/current/env/lib/python2.5/site-packages')
os.environ['DJANGO_SETTINGS_MODULE'] = 'app.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
Pas de changement au niveau de la configuration de mod_wsgi dans apache, vous pouvez malgré tout préciser le nouveau path de python via la directive WSGIPythonHome tel que :
WSGIPythonHome /var/www/app.com/current/env
Fabric
Fabric va nous permettre de déployer notre application en production ou en staging en utilisant pip et virtualenv de concert. Si vous n’avez pas suivi, fabric était dans la liste des dépendances (dans development.txt) donc il devrait se trouver dans notre environnement virtuel.
(env)$ fab
Usage: fab [options] <command>
[:arg1,arg2=val2,host=foo,hosts='h1;h2’,...] ...
Toute la magie de Fabric se trouve dans le fichier fabfile.py qui va se trouver à la racine de notre projet :
$ touch fabfile.py
$ nano fabfile.py
Le principe d’un fichier fabfile est de déclarer plusieurs fonctions qui font chacune une opération. Le plus souvent, on déclare avant tout des fonctions qui permettent de déclarer nos différents environnements de travail :
def production():
"""Defines production environment"""
env.user = "deploy"
env.hosts = ['example.com',]
env.base_dir = "/var/www"
env.app_name = "app"
env.domain_name = "app.example.com"
env.domain_path = "%(base_dir)s/%(domain_name)s" % { 'base_dir':env.base_dir, 'domain_name':env.domain_name }
env.current_path = "%(domain_path)s/current" % { 'domain_path':env.domain_path }
env.releases_path = "%(domain_path)s/releases" % { 'domain_path':env.domain_path }
env.shared_path = "%(domain_path)s/shared" % { 'domain_path':env.domain_path }
env.git_clone = "git@github.com:example/app.git"
env.env_file = "deploy/production.txt"
def staging():
"""Defines staging environment"""
env.user = "deploy"
env.hosts = ['dev.example.com',]
env.base_dir = "/var/www"
env.app_name = "app"
env.domain_name = "app.example.com"
env.domain_path = "%(base_dir)s/%(domain_name)s" % { 'base_dir':env.base_dir, 'domain_name':env.domain_name }
env.current_path = "%(domain_path)s/current" % { 'domain_path':env.domain_path }
env.releases_path = "%(domain_path)s/releases" % { 'domain_path':env.domain_path }
env.shared_path = "%(domain_path)s/shared" % { 'domain_path':env.domain_path }
env.git_clone = "git@github.com:example/app.git"
env.env_file = "deploy/production.txt"
Ces deux déclarations peuvent sembler un peu cryptiques, mais elle ne font que définir des variables pour chaque environnement. Ces variables seront ensuite utiliser dans les fonctions qui executent vos actions. Cela permet de pouvoir executer ces actions dans l’environnement. Par exemple, ce jeu de fonctions fonctionnera pour l’environnement de production comme de staging :
def start():
"""Start the application servers"""
sudo("/etc/init.d/apache2 start")
def restart():
"""Restarts your application"""
sudo("/etc/init.d/apache2 force-reload")
def stop():
"""Stop the application servers"""
sudo("/etc/init.d/apache2 stop")
def permissions():
"""Make the release group-writable"""
sudo("chmod -R g+w $(domain_path)")
sudo("chown -R www-data:www-data $(domain_path)")
def setup():
"""Prepares one or more servers for deployment"""
run("mkdir -p $(domain_path)/{releases,shared}")
run("mkdir -p $(shared_path)/{system,log}")
permissions()
$ fab production setup restart
$ fab staging setup restart
Pour vous faciliter la vie, voici un fabfile tout fait qui va vous permettre de déployer vos applications facilement et qui a l’avantage de proposer quelques raffinements comme :
- la possibilité de revenir à la version déployée précédemment.
- de lancer des migrations via l’utilisation de l’excellent South
la possibilité de mettre en place une page de maintenance, mais n’oubliez pas d’ajouter ces quelques lignes à votre configuration apache :
RewriteEngine on
RewriteCond /var/www/app.com/shared/system/maintenance.html -f
RewriteRule !^/maintenance.html$ /maintenance.html [R=503,L]
ErrorDocument 503 /maintenance.html
Le déploiement de votre application s’effectuera dans le dossier /var/www/$domain_name/, l’application déployée sera accessible dans le dossier current et les fichiers dépendants de votre environnement se trouveront dans le dossier shared. Les derniers déploiements seront accessibles dans le dossier releases, la stratégie de déploiement est basée sur l’utilisation de git, mais il ne tient qu’à vous d’utiliser subversion ou mercurial.
Il vous reste simplement à télécharger le fichier fabfile.py.