Django via Nginx et FastCGI

Publié le 18 juillet 2010 par Kphoen

Maintenant que Nginx est correctement configuré pour servir Redmine, c’est au tour de Django de passer à la casserole (bah ouais, Python ou Ruby, s’pas un troll qui m’affecte des masses :–°)

Comme pour Redmine, Nginx fera office de reverse proxy, mais cette fois ce sera via FastCGI que sera servi le contenu.

Je suppose toujours que vous avez un Nginx opérationnel et Ubuntu 10.4, les manips doivent ici aussi être les mêmes pour tous les dérivés Debian. Mais cette fois j’ajouterai un pré-requis supplémentaire : un projet django fonctionnel (bawé, cay kewl si on veut tester =P)

Installation des .deb

sudo aptitude install python-django python-flup

Et voilà, on a une install’ de Django fonctionnelle, et de quoi faire tourner un serveur FastCGI.

Automatisation du lancement des projets Django

Parce qu’il peut être sympa de pouvoir lancer/stopper/relancer automatiquement tous les projets django que l’on sert, je vous mets à disposition un script à placer dans /etc/init.d/django_fastcgi et à rendre exécutable via le traditionnel :

sudo chmod +x /etc/init.d/django_fastcgi

N.B : prenez bien soin de correctement renseigner la variable DJANGO_SITES. Elle doit contenir les chemins sans le slash final vers les projets, séparés par des espaces. Chaque path présent dans DJANGO_SITES doit permettre d’accéder au fichier manage.py qui correspond au projet, ce fichier permettant dans notre cas de lancer le serveur FastCGI.

#! /bin/sh
### BEGIN INIT INFO
# Provides:          FastCGI servers for Django
# Required-Start:    networking
# Required-Stop:     networking
# Default-Start:     2 3 4 5
# Default-Stop:      S 0 1 6
# Short-Description: Start FastCGI servers with Django.
# Description:       Django, in order to operate with FastCGI, must be started
#                    in a very specific way with manage.py. This must be done
#                    for each Django web server that has to run.
### END INIT INFO
#
# Authors:  Guillermo Fernandez Castellanos
#           <guillermo.fernandez.castellanos AT gmail.com>.
#
#           Kévin Gomez
#           <geek63 AT gmail.com>.
#
# Version: @(#)fastcgi 0.1 18-Jul-2010 geek63 AT gmail.com
#          @(#)fastcgi 0.1 11-Jan-2007 guillermo.fernandez.castellanos AT gmail.com
#

#### SERVER SPECIFIC CONFIGURATION
DJANGO_SITES="/home/kevin/project_foo /home/bar/project_foo"
RUNFILES_PATH=/tmp
HOST=127.0.0.1
PORT_START=3030
RUN_AS=www-data
FCGI_METHOD=prefork
#### DO NOT CHANGE ANYTHING AFTER THIS LINE!

set -e

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="FastCGI servers"
NAME=$0
SCRIPTNAME=/etc/init.d/$NAME

#
#       Function that starts the daemon/service.
#
d_start()
{
    # Starting all Django FastCGI processes
    PORT=$PORT_START
    for SITE in $DJANGO_SITES
    do
        PID_FILE=`echo $SITE | sed -e 's#/#_#g'`

        if [ -f $RUNFILES_PATH/$PID_FILE.pid ]; then
            echo "$SITE already running"
        else
            start-stop-daemon --start --pidfile $RUNFILES_PATH/$PID_FILE.pid \
                              --startas /usr/bin/python $SITES_PATH/$SITE/manage.py runfcgi \
                              method=$FCGI_METHOD socket=$RUNFILES_PATH/$PID_FILE.socket \
                              pidfile=$RUNFILES_PATH/$PID_FILE.pid
            chmod 400 $RUNFILES_PATH/$PID_FILE.pid
            chmod 777 $RUNFILES_PATH/$PID_FILE.socket

            echo "$SITE started"
        fi
        PORT=$(($PORT + 1))
    done
}

#
#       Function that stops the daemon/service.
#
d_stop() {
    # Killing all Django FastCGI processes running
    for SITE in $DJANGO_SITES
    do
        PID_FILE=`echo $SITE | sed -e 's#/#_#g'`

        start-stop-daemon --stop --quiet --pidfile $RUNFILES_PATH/$PID_FILE.pid & echo "$SITE stopped" \
                          || echo -n "$SITE not running"
        if [ -f $RUNFILES_PATH/$PID_FILE.pid ]; then
           rm $RUNFILES_PATH/$PID_FILE.pid
        fi
    done
}

ACTION="$1"
case "$ACTION" in
    start)
        echo "Starting $DESC:"
        d_start
        ;;

    stop)
        echo "Stopping $DESC:"
        d_stop
        ;;

    restart|force-reload)
        echo "Restarting $DESC:"
        d_stop
        sleep 1
        d_start
        ;;

    *)
        echo "Usage: $NAME {start|stop|restart|force-reload}" >&2
        exit 3
        ;;
esac

exit 0

On peut aussi faire en sorte que les projets soient automatiquement lancés dès le boot du serveur via un :

sudo update-rc.d django_fastcgi defaults

Configuration de Nginx

Un exemple de virtualhost vaut mieux qu’un long discours, donc le voici :

server {
    listen      80;
    server_name domain.tld;

    location / {
        fastcgi_pass unix:/tmp/_home_bar_project_foo.socket;

        # à vous de voir si vous avez besoin de plus de choses
        fastcgi_param PATH_INFO         $fastcgi_script_name;
        fastcgi_param SERVER_PORT       $server_port;
        fastcgi_param SERVER_PROTOCOL   $server_protocol;
        fastcgi_param SERVER_NAME       $server_name;
        fastcgi_param REQUEST_METHOD    $request_method;
        fastcgi_param CONTENT_TYPE      $content_type;
        fastcgi_param CONTENT_LENGTH    $content_length;
    }

    # on sert directement les fichiers statiques de l'administration
    location /media {
        root /usr/lib/python2.5/site-packages/django/contrib/admin/media;
    }

   # quelques fichiers statiques liés à l'application (css, images, js)
    location /static {
        root /home/bar/project_foo;
    }
}

L’adresse du socket peut est connue via la méthode suivante :

  • $RUNFILES_PATH : définie dans /etc/init.d/django_fastcgi
  • $PROJECT_PATH : correspond à l’adresse du projet dans laquelle on a remplacé les « / » par des « _ »
  • l’adresse du socket est : $RUNFILES_PATH/$PROJECT_PATH.socket

Une fonction très basique en bash permet d’automatiser la douloureuse et complexe conversion de chemin :

# elle prend en paramètre le path à convertir, et si aucun paramètre n'est fourni elle utilise le répertoire courant
fastcgi_sockfile() {
    if [ "$1" = "" ]; then
        SITE=`pwd`
    else
        SITE=$1
    fi

    echo "/tmp/`echo $SITE | sed -e 's#/#_#g'`.sock"
}