lindev.fr

Aller au contenu | Aller au menu | Aller à la recherche

Mot-clé - settings.py

Fil des billets - Fil des commentaires

27 août 2014

Django déploiement avec Nginx Gunicorn et Supervisord

Je vois souvent passer des questions sur le forums ou autres fils de discussion concernant la mise en production d'un projet Django.
Ici je vais présenter une façon de faire, utilisant Nginx Gunicorn et Supervisord .

Prérequis

Sur une Débian toute fraiche,

Nginx

sudo apt-get install nginx

Virtualenv

Car nous travaillons toujours avec un environnement virtuel, dans lequel nous allons y installer les dépendances de notre projet.
Ce qui permet de ne pas avoir de conflit de version avec d'autres éventuels projets déjà en prod.

pip install virtualenv virtualenvwrapper

Note : Si vous n'avez pas l'outil pip de disponible, installez-le comme ceci

sudo apt-get install python-setuptools
sudo easy_install pip

Puis dans votre fichier ~/.bashrc , ajoutez les lignes suivantes, pour l'autocompletion

export VIRTUALENVWRAPPER_VIRTUALENV_ARGS='--no-site-package'
export WORKON_HOME=$HOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh

Gunicorn

Pourquoi pas utiliser uWSGI ? humm ... car je préfère Gunicorn qui me donne toute satisfaction sur mes projets en prod. donc ... pourquoi pas !

sudo apt-get install libevent-dev
pip install gunicorn

Supervisor

Installation simple :

sudo pip install supervisor

Récupération du fichier de conf par défaut

echo_supervisord_conf > /etc/supervisord.conf

Dans le fichier de conf, afin de pouvoir manager votre projet via l'interface web, il faut dé-commenter et paramétrer les lignes suivantes

[inet_http_server]         ; inet (TCP) server disabled by default
port=*:9001        ; (ip_address:port specifier, *:port for all iface)
username=user              ; (default is no username (open server))
password=123               ; (default is no password (open server))

Note: Changer le username et password évidemment

L'interface web sera disponible à l'adresse : http://ipduserveur:9001

Pour le lancement automatique au démarrage du système, vous pouvez utiliser ces scripts d'init:

  1. debian init supervisor
  2. debian init supervisor

Installer votre projet

Notre environnement est prêt, nous allons commencer par installer le projet Django dans l’environnement virtuel.

Création du virtualenv

mkvirtualenv monprojet

Si vous utilisez git, cloner votre projet, sinon, copier le à l'endroit que vous souhaitez, ( pour l'exemple ce sera /var/www/monprojet )

Installation des dépendances du projet

si vous utilisez également les environnements virtuels pour développer ( ce que je conseille ) vous pouvez alors enregistrer la liste des libs python installées dans cette environnement, afin de pouvoir également les installer ( avec la même version ) sur un autre serveur.

Pour avoir la liste des libs :

pip freeze > requirements.txt

puis sur votre serveur, pour installer les libs depuis un export freeze :

pip install -r requirements.txt

Configuration de Django

Il vous faudra peut-être toucher un peu à votre fichier settings.py pour le passer du mode debug au mode production.
Personnellement j'utilise une autre subtilité qui me permet de ne pas avoir à toucher au fichier settings.py ( j'expliquerai celà dans un autre billet ) .

N'oubliez pas de vérifier le paramètre STATIC_ROOT

STATIC_ROOT = '/var/www/static_monprojet/'

C'est le répertoire ou seront copiés les fichiers statiques du projet, pour ensuite être servis par Nginx

Ce répertoire DOIT EXISTER

Une fois le répertoire créé, nous allons y "placer/lier" les fichiers statiques du projet

python manage.py collectstatic --link

Perso je préfère y mettre des liens symboliques .. ( les gouts et les couleurs ... )

Liaison du projet avec Gunicorn

Nous allons créer un fichier dans notre projet qui sera utilisé pour le lancement du/des process Gunicorn ( vous devrez adapter les valeurs dans ce script )

vim /var/www/monprojet/monprojet/gunicorn.sh

Et voici le contenu

#!/bin/bash
  set -e
  NUM_WORKERS=2
  USER=www-data
  GROUP=www-data
  ADDRESS=127.0.0.1:5002
  cd /var/www/monprojet
  source /home/monuser/.virtualenvs/monprojet/bin/activate
  exec gunicorn monprojet.wsgi:application -w $NUM_WORKERS --bind=$ADDRESS \
    --user=$USER --group=$GROUP --log-level=debug

Puis on le rend exécutable

chmod 777 /var/www/monprojet/monprojet/gunicorn.sh

Configuration Supervisord

Notre projet est prêt, afin de lancer Gunicorn automatiquement, nous allons utiliser supervisord, qui en plus de s'occuper de démarrer le projet automatiquement au démarrage du système, va aussi le relancer en cas de crash, gérer les logs et vous donner la main pour arrêter ré-demarrer les process, via l'interface web ou en mode console.

supervisord.png

Ajoutez à la fin du fichier de configuration /etc/supervisord.conf les lignes suivantes ( paramètres à adapter selon votre cas )

[program:guni_monprojet]
directory=/var/www/monprojet/monprojet
user = www-data
autostart=true
autorestart=true
stdout_logfile=/var/log/monprojet.log
redirect_stderr=true
stopsignal=QUIT
command = /var/www/monprojet/monprojet/monprojet.sh

Ne reste plus qu'à redémarrer supervisor pour prendre en compte la nouvelle config.

Pour le lancer manuellement

sudo supervisord -c /etc/supervisord.conf

Vhost Nginx

Dernier point, la création du vhost de Nginx, pour diriger les requêtes vers Gunicorn qui écoute sur le port 5002 ( il est aussi possible d'utiliser un fichier socket à la place )

vim /etc/nginx/sites-enabled/monprojet

Et voilà le contenu ( fonctionnel, mais vous pouvez l'adapter )

upstream us_monprojet {
        server 127.0.0.1:5002;
}


server {
        listen 80;

        root /var/www/monprojet;

        gzip             on;
        gzip_min_length  1000;
        gzip_proxied     expired no-cache no-store private auth;
        gzip_types       text/plain application/xml text/css text/javascript application/x-javascript application/x-shockwave-flash video/x-flv;
        gzip_disable     "MSIE [1-6]\.";


        server_name monprojet.com;
        charset utf-8;

        client_max_body_size 75M;


        location ~ /\.ht {
            deny  all;
        }

        location /favicon.ico {
                alias /var/www/monprojet/monprojet/static/favicon.ico;

                if (-f $request_filename) {
                        access_log off;
                        expires max;
                }

        }


        location /media {
                alias /var/www/monprojet/monprojet/media;

                if (-f $request_filename) {
                        access_log off;
                        expires max;
                }

        }

        location /static {
                alias /var/www/static_monprojet/;

                if (-f $request_filename) {
                        access_log off;
                        expires max;
                }

        }
location / {

                if (-f $request_filename) {
                        access_log off;
                        expires max;
                }       

                #gunicornParams
                if (!-f $request_filename) {
                        proxy_pass         http://us_monprojet;
                        break;
                }       
                proxy_redirect     off;
                proxy_set_header   Host             $host;
                proxy_set_header   X-Real-IP        $remote_addr;
                proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

        }       
}       

Ne reste plus qu'à activer le vhost, et relancer nginx

ln -s /etc/nginx/sites-available/monprojet /etc/nginx/sites-enabled/
service nginx reload

31 oct. 2013

Déployer votre app Python automatiquement avec fabric .

Petite mise en place

Lorsque vous développez en Python, contrairement à PHP, ce dernier compile le code à la première "interprétation", puis génère des fichiers "bytecode", avec l’extension .pyc.
Une fois cette génération faite, le fichier source .py du même nom sera ignoré, et ce jusqu'au redémarrage du process.
Le redémarrage du process, vérifiera les modifications apportées aux sources ( .py ) afin de les recompiler et exécuter le code mis à jour.

Tout ça pour en venir au fait, que lorsque vous développez en python ( par exemple une app. web avec Django ), lorsque vous envoyez vos maj sur le serveur de production, vos modifications n'auront aucun effet tant que vous n'avez pas redémarré les process ( uwsgi, ou autre .. ) .

Ce qui peut vous surprendre au début, si vous venez du monde PHP ou les modifications sont prises en compte instantanément.
Bien que vous pouvez avoir quelque chose de similaire si vous utilisez PHP avec le cache APC ... mais ce n'est pas le sujet ici.

Étapes manuelles

Donc si je prends mon projet Django, voici les étapes manuelles à appliquer :

Sur mon poste de développement :

git add xx ; s'il y a des fichiers sources à ajouter au dépôt
git commit -a -m "Commentaire de mon commit"
git push

Sur mon serveur de prod :

cd /mon/depot
git pull 
# vim settings.py pour des update de version de cache etc .. 
workon monenvironnementvirtuel
python manage.py collectstatic
supervisorctl MonApp reload

Étapes avec fabric

Sur mon poste de développement :
fab backup
Sur le serveur
fab deploy

Bon ... voyons maintenant comment mettre en place fabric .

Installation et fabfile.py

Commençons par installer la librairie ( sur le serveur et le poste de dev. )

workon monenvironnement
pip install fabric

Puis nous allons créer notre fichier qui va se charger d'automatiser les différentes étapes manuelles: fabfile.py

#! -*- coding:utf-8 -*-
from fabric.api import local,prefix
from fabric.colors import green

import re

PROJECT_NAME = "MonApp" # Nom de l'app
SUPERVISOR_APP_NAME = "MonApp" # Nom de l'app supervisor à redémarrer
VIRTUALENV_NAME = "MonApp"  #Nom de l'environnement virtuel à utiliser

def backup():
    #Sauvegarde les librairies utilisées par le projet ( pour les installer si besoin sur le serveur .. sans réfléchir )
    local('pip freeze > requirements.txt')

    #Ajoute les éventuels fichiers non "trackés" 
    local('git add .')

    git_status = local( 'git status', capture=True )
    print git_status

    regx = re.compile(r"(nothing to commit)")
    if not regx.search( git_status ):

        print(green("Il y a des choses à commiter !"))

        print("Entrez votre commentaire de commit : ")
        comment = raw_input()

        #Renseignement du commentaire du commit
        local('git commit -m "%s"' % comment)
        local('git push')
    else:
        print(green("Rien à commiter !"))


def deploy():
    """
    Déploie l'app sur le "serveur" courant 
    """

    #Récupère la dernière version du dépôt
    local('git pull')

    #Permet de changer la version des fichiers statiques à servir 
    print(green("Version du cache à appliquer []:"))
    vcache = raw_input()
    xs = vcache.strip()
    if xs[0:1] in '+-': xs = xs[1:]
    if xs.isdigit():
        init_cache(int(xs))


    # Active ou désactive le mode DEBUG
    print(green("Désactiver le mode DEBUG ( y/n ) ? [y]"))
    disable_debug = raw_input()
    print disable_debug
    if disable_debug == 'y' or disable_debug=='':
        print(green("Passage du mode DEBUG à FALSE"))
        switch_debug(False)
    else:
        print(green("Passage du mode DEBUG à TRUE"))
        switch_debug(True)

    #Récupère les éventuels nouveaux fichiers statiques 
    local(django_manage( 'collectstatic --link', VIRTUALENV_NAME ),capture=False,shell='/bin/bash')

    #Redémarre le processus supervisord
    local('supervisorctl restart '+SUPERVISOR_APP_NAME)



def init_cache(change_to):

    local( 'cp '+ PROJECT_NAME +'/settings.py '+ PROJECT_NAME +'/settings.bak' )
    sed = "sed 's/^VERSION_JS_CSS = [0-9]*$/VERSION_JS_CSS = %s/' "+ PROJECT_NAME+"/settings.bak > "+PROJECT_NAME+"/settings.py"
    local(sed % (change_to))

    local('rm '+PROJECT_NAME+'/settings.bak')

def switch_debug(change_to):

    local( 'cp '+ PROJECT_NAME +'/settings.py '+ PROJECT_NAME +'/settings.bak' )
    sed = "sed 's/^DEBUG = [a-zA-Z]*$/DEBUG = %s/' "+ PROJECT_NAME+"/settings.bak > "+PROJECT_NAME+"/settings.py"
    local(sed % (change_to))

    local('rm '+PROJECT_NAME+'/settings.bak')

Et voilà en deux commandes, vous avez mis à jour votre application sur le serveur de production, chargé les éventuels fichier statiques dans le répertoire prévu à cet effet, modifié le fichier settings.py pour y indiquer s'il faut invalider les fichiers statiques ( css, js .. ) afin de forcer les navigateurs à charger les nouvelles versions, activé ou non le mode DEBUG de Django, et pour finir relancé les process uwsgi grâce à supervisor ( commande supervisorctl ).

Il est possible de tout faire depuis son poste de développement, Fabric peu se connecter sur les serveurs distants pour déployer l'application, ce qui peu s'avérer très utile lorsque vous avez plusieurs frontaux à mettre à jour .

Backup

Fabric_backup.png

Deploy

Fabric_deploy.png

Source:

Fabric : http://docs.fabfile.org/en/1.8/

02 oct. 2013

Mezzanine épisode 2

mezzanine.png Nous avons vu dans un précédent billet, comment installer le CMS mezzanine et créer un nouveau projet.
Après s'être familiarisé avec son utilisation, voyons comment customiser notre projet.

Organisation des Thèmes

Un thème mezzanine = une app ( app: répertoire avec un fichier nommé init.py dedans )

Créons notre thème montheme

mkdir montheme && touch montheme/__init__.py

Puis nous allons le déclarer dans le fichier settings.py , au début de le liste INSTALLED_APPS

INSTALLED_APPS = (
    "montheme", # thème à utiliser
    "django.contrib.admin",
    ...

Bien, maintenant nous allons créer la structure de notre thème, à savoir, créer les répertoires de fichiers qtatiques ("static/") et celui des templates ("templates/")

mkdir -p montheme/static/css && mkdir montheme/static/img && mkdir montheme/static/js
mkdir -p montheme/templates/pages/menus && mkdir montheme/templates/includes

Nous voilà avec un superbe template ...... qui ne fait rien !

Il ne nous reste donc plus qu'à créer nos feuilles de styles et fichiers Html pour surcharger le thème par défaut .

Récupérer le thème par défaut

Pour avoir une base fonctionnel, nous allons récupérer les templates et fichiers statiques du thème mezzanine par défaut.

Toujours à la racine de notre projet,

python manage.py collectstatic
python manage.py collecttemplates

Vous vous retrouvez avec deux nouveaux répertoires templates et static qui contiennent donc respectivement les templates et fichiers statiques nécessaires au fonctionnement de base de Mezzanine.

/!\ Attention : si vous laissez tel quel les répertoires, votre template ne sera jamais pris en compte car les répertoires templates et static à la racine du projet sont prioritaires . ( Scannés en premier par Django )
Renommons donc ces répertoires afin de les rendre inactifs

mv templates templates_orig
mv static static_orig

Création de notre thème

Avant d'entrer dans le vif du sujet, il faut bien comprendre comment Mezzanine cherche ses templates.

Par défaut, Mezzanine cherche le template, qui a le même nom que la page demandée. Par exemple, dans l'interface admin, je crée une page dont le titre est "Authors" et le "slug" ( non .. pas la limace, mais une url propre ) : "About/authors", mezzanine va chercher une des correspondances suivantes :

  1. pages/about/authors.html
  2. pages/about/authors/about.html
  3. pages/about/about.html
  4. pages/about.html
  5. pages/page.html


page.html étant la page générique qui sera appelée en dernier recours, c'est cette page qui est disponible "entres-autres" dans les templates du thème par défaut.
Voilà notre point de départ, nous allons donc commencer par copier cette page dans notre thème, qui sera aussi notre page par défaut.

cp templates_orig/pages/page.html montheme/templates/pages/

Si on édite le fichier page.html, nous pouvons voir qu'il s'agit juste d'une structure d'affichage qui s’intègre dans le template base.html .
Copions le également.

cp templates/base.html montheme/templates/

Feuilles de style et javascript

Afin de ne pas perdre les styles et fonctions javascript de base, nous allons créer les nôtres ( au besoin évidemment )

touch montheme/static/css/montheme.css
touch montheme/static/js/montheme.js

Puis modifiez les blocs suivants pour ajouter montheme.css et montheme.js

block compress css
<link rel="stylesheet" href="{% static "css/montheme.css" %}">
block compress js
<script src="{% static "js/montheme.js" %}"></script>

Conclusion

Nous voilà avec un thème qui pour le moment ne fait que reprendre celui par défaut, mais il est en place ! Il ne reste plus qu'à personnaliser les fichiers page.html et base.html dans un premier temps à votre gout .
Si vous souhaitez allez plus loin sur l'intégration des différents éléments dans votre thème, il suffit de suivre le même principe .

  1. Copier les pages concernées du thème original dans votre thème
  2. Les adapter à votre gout ! c'est tout .

Par exemple, si je souhaite modifier le menu en entête, je vais chercher le fichier dropdown.html qui se trouve dans le répertoire templates/pages/menus/ , je le copie dans mon thème montheme/templates/pages/menus/ et je peux travailler dessus en toute liberté .

N'hésitez pas à activer django_debug_toolbar ( dans votre fichier settings.py ) pour vous aider.

Ch.

15 août 2013

Python Web: Setup Django + MongoDB

django.jpg

Setup Django + MongoDB

Je travaille actuellement sur un projet d'application Web industriel basé sur les technologies suivantes

  • Python ( avec le framework Django V1.5.2 )
  • MongoDB ( Pour son efficacité et flexibilité )

Deux technologies que j'affectionne particulièrement, tant par le coté administration ( Principalement pour mongodb ) que par le coté utilisation.

Le framework MongoDB est parfait pour un projet industriel relativement imposant, mais peut tout aussi bien l'être pour une petit site de quelques page ( bien que pour ce genre de micro projet, je préférerais utiliser le framework flask, ou si je souhaite changer un peu d'air, utiliser Node.js avec le framework express.js ).

Seul "problème", Django n’intègre pas en natif la gestion des bases de données NoSQL comme MongoDB. Heureusement, quelques outils permettent de profiter pleinement de Django et MongoDB ensemble, notamment avec l'utilisation de la librairie MongoEngine, basée sur pymongo

N'ayant pas toujours le temps de transcrire mes tutoriels, je vais mettre le lien de mon document GoogleDoc, qui se complète au fur et à mesure du temps , n'hésitez donc pas à le parcourir, et laisser des commentaires, me corriger...

Django First steps

Bonne lecture,

Ch.