Configurar Django con Nginx, Gunicorn, virtualenv, supervisor y PostgreSQL

Esta es una traducción del post que puede encontrar en el enlace:

http://michal.karzynski.pl/blog/2013/06/09/django-nginx-gunicorn-virtualenv-supervisor/

contando con el permiso autor para compartirlo en español.

Introducción

Django es un framework de desarrollo web eficiente, versátil y dinámico que está en continua evolución. Cuando Django empezó a ganar popularidad la configuración que se recomendaba era con Apache y mod_wsgi. El “arte” de hacer funcionar a Django ha cambiado y la configuración recomendada se ha hecho más eficiente y elástica a la par que más compleja e incluye herramientas como: Nginx, Gunicorn, virtualenv, supervisord y/o PostgreSQL.

En este post se explicará como combinar todas estas herramientas en un servidor corriendo Linux.

Requisitos

Se asume que cuenta con un servidor en el que tiene privilegios de usuario. Yo (el autor original) uso un servidor corriendo Debian 7, así que todo lo aquí dicho debería funcionar en servidores con Ubuntu o otras distribuciones basadas en Debian.

Si está usando una distribución basada en RPM (como CentOS), necesitará reemplazar las ordenes apt-get por sus equivalentes yum y si usa FreeBSD puede instalar los componentes por ports.

Si no dispone de un servidor con el que “jugar”, le recomendaría un servidor VPS económico en Digital Ocean. Si se registra con este enlace me ayudará a reducir mi siguiente factura :)

También se presupone que ha configurado su DNS para que apunte a la IP del servidor. Para esta guía el dominio será example.com

Actualizar el sistema

Empezaremos asegurándonos que el sistema esté actualizado:

sudo apt-get update
sudo apt-get upgrade

PostgreSQL

Para instalarlo ejecute la orden

sudo apt-get install postgresql postgresql-contrib

Y a continuación cree un usuario y una nueva base de datos para el proyecto.

sudo su - postgres
postgres@django:~$ createuser --interactive -P
Enter name of role to add: hello_django
Enter password for new role: 
Enter it again: 
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
postgres@django:~$

postgres@django:~$ createdb --owner hello_django hello
postgres@django:~$ logout

Usuario de la aplicación

Incluso aunque Django tiene una buena lista con los problemas de seguridad, las aplicaciones se pueden ver comprometidas. Si la aplicación tiene acceso limitado a los recursos de su servidor, los daños potenciales también pueden limitarse. La aplicación debería funcionar con un usuario con privilegios restringidos.

Cree un usuario para la aplicación llamado hello y asignelo a un grupo llamado webapps

$ sudo groupadd --system webapps 
$ sudo useradd --system --gid webapps --shell /bin/bash --home /webapps/hello_django hello

Instalar virtualenv y crear un entorno para su aplicación

Virtualenv es una herramienta que permite crear entornos Python separados de nuestro sistema. Esto permite trabajar con aplicaciones con distintos requisitos al mismo tiempo. Por ejemplo, una basado en Django 1.5 y otro en Django 1.6. Virtualenv es facil de instalar en Debian:

sudo apt-get install python-virtualenv

Crear y activar un entorno para su aplicación

Me gusta que todas mis aplicaciones estén en el directorio /webapps/. Si lo prefiere /var/www/ , /srv/ o cualquier otra ubicación de su agrado. Cree un directorio para albergar su aplicación /webapps/hello_django/ y cambie el propietario del directorio al usuario de su aplicación, hello

$ sudo mkdir -p /webapps/hello_django/
$ sudo chown hello /webapps/hello_django/

Ahora cree un entorno de virtualenv siendo el usuario de su aplicación en el directorio de esta:

$sudo su - hello hello@django:~$ cd /webapps/hello_django/
hello@django:~$ virtualenv .

New python executable in hello_django/bin/python Installing
distribute..............done. Installing pip.....................done.

hello@django:~$ source bin/activate (hello_django)hello@django:~$

Ahora el entorno está activado y se puede proceder a instalar Django en él.

(hello_django)hello@django:~$ pip install django

Downloading/unpacking django
(...)
Installing collected packages: django
(...)
Successfully installed django
Cleaning up...

El entorno con Django debería estar ya preparado. Ahora proceda a instalar Django:

(hello_django)hello@django:~$ django-admin.py startproject hello

Puede comprobar que todo funcione ejecutando el servidor de desarrollo:

(hello_django)hello@django:~$ cd hello
(hello_django)hello@django:~$ python manage.py runserver example.com:8000
Validating models...

0 errors found
June 09, 2013 - 06:12:00
Django version 1.5.1, using settings 'hello.settings'
Development server is running at http://example.com:8000/
Quit the server with CONTROL-C.

Ahora debería poder acceder al servidor atraves de la url http://example.com:8000

##Dar permisos de escritura a otros usuarios en el directorio de la aplicación

La aplicación va a funcionar como el usuario hello, el cual tiene permisos en todo el directorio. Si quiere que el usuario habitual pueda cambiar archivos de la apliación, puede cambiar el propietario del grupo del directorio a users y otorgar permisos de escritura al grupo.

$ sudo chown -R hello:users /webapps/hello_django
$ sudo chmod -R g+w /webapps/hello_django

Puede comprobar los grupos de los que usted es miembro usando la orden groups o id

$ id
uid=1000(michal) gid=1000(michal) groups=1000(michal),27(sudo),100(users)

Si no es miembro de users, se puede agregar a usted mismo con:

$ sudo usermod -a -G users `whoami`

Configurar PostgreSQL para trabajar con Django

Para poder usar PostgreSQL con Django será necesarios instalar psycopg2 en su entorno virtual. Este paso requiere la compilación de una extensión nativa escrita en C. La compilación fallará si no puede encontrar los archivos header y las librerías requeridas para el enlazar los programas de C con libpq (librería de comunicación con Postgres) y construir los modulos de Pyton (python-dev package). Tenemos que instalar estos dos paquetes primero y entonces podremos instalar psycopg2 usando pip:

$ sudo aptitude install libpq-dev python-dev
(hello_django)hello@django:~$ pip install psycopg2

Y ahora podremos configurar nuestra base de datos en el archivo settings.py de Django:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'hello',
        'USER': 'hello_django',
        'PASSWORD': '1Ak5RTQt7mtw0OREsfPhJYzXIak41gnrm5NWYEosCeIduJck10awIzoys1wvbL8',
        'HOST': 'localhost',
        'PORT': '',                      # Set to empty string for default.
    }
}

Y por último podremos construir la base de datos desde Django:

(hello_django)hello@django:~$ python manage.py syncdb

Gunicorn

En producción no usaremos el servidor de desarrollo de Django sino una aplicación dedicada a servir nuestro contenido, gunicorn.

Lo primero será instalar gunicorn en el entorno virtual de nuestra aplicación:

(hello_django)hello@django:~$ pip install gunicorn
Downloading/unpacking gunicorn
  Downloading gunicorn-0.17.4.tar.gz (372Kb): 372Kb downloaded
  Running setup.py egg_info for package gunicorn

Installing collected packages: gunicorn
  Running setup.py install for gunicorn

    Installing gunicorn_paster script to /webapps/hello_django/bin
    Installing gunicorn script to /webapps/hello_django/bin
    Installing gunicorn_django script to /webapps/hello_django/bin
Successfully installed gunicorn
Cleaning up...

Puede comprobar que gunicorn funciona correctamente con la orden:

(hello_django)hello@django:~$ gunicorn hello.wsgi:application --bind example.com:8001

Ahora debería poder acceder al servidor a través de la url http://example.com:8001/ . Se ha cambiado intencionadamente 8000 por 8001 para forzar al navegador a establecer una nueva conexión.

Gunicorn está ahora instalado y listo para servir su aplicación. Procedamos a establecer una configuración básica para hacerlo más útil. Estableceremos una serie de parámetros en un archivo bash que gurdaremos en bin/gunicorn_start.

#!/bin/bash

NAME="hello_app"                                  # Name of the application
DJANGODIR=/webapps/hello_django/hello             # Django project directory
SOCKFILE=/webapps/hello_django/run/gunicorn.sock  # we will communicte using this unix socket
USER=hello                                        # the user to run as
GROUP=webapps                                     # the group to run as
NUM_WORKERS=3                                     # how many worker processes should Gunicorn spawn
DJANGO_SETTINGS_MODULE=hello.settings             # which settings file should Django use
DJANGO_WSGI_MODULE=hello.wsgi                     # WSGI module name

echo "Starting $NAME as `whoami`"

# Activate the virtual environment
cd $DJANGODIR
source ../bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

# Create the run directory if it doesn't exist
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR

# Start your Django Unicorn
# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)
exec ../bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $NUM_WORKERS \
--user=$USER --group=$GROUP \
--bind=unix:$SOCKFILE \
--log-level=debug \
--log-file=-

Y una vez guardado deberá darle permisos de ejecución:

    $ sudo chmod u+x bin/gunicorn_start

Puede comprobar que todo funciona ejecutando el script como hello:

$ sudo su - hello
hello@django:~$ bin/gunicorn_start
Starting hello_app as hello
2013-06-09 14:21:45 [10724] [INFO] Starting gunicorn 18.0
2013-06-09 14:21:45 [10724] [DEBUG] Arbiter booted
2013-06-09 14:21:45 [10724] [INFO] Listening at: unix:/webapps/hello_django/run/gunicorn.sock (10724)
2013-06-09 14:21:45 [10724] [INFO] Using worker: sync
2013-06-09 14:21:45 [10735] [INFO] Booting worker with pid: 10735
2013-06-09 14:21:45 [10736] [INFO] Booting worker with pid: 10736
2013-06-09 14:21:45 [10737] [INFO] Booting worker with pid: 10737

^C (CONTROL-C to kill Gunicorn)

2013-06-09 14:21:48 [10736] [INFO] Worker exiting (pid: 10736)
2013-06-09 14:21:48 [10735] [INFO] Worker exiting (pid: 10735)
2013-06-09 14:21:48 [10724] [INFO] Handling signal: int
2013-06-09 14:21:48 [10737] [INFO] Worker exiting (pid: 10737)
2013-06-09 14:21:48 [10724] [INFO] Shutting down: Master
$ exit

Necesitará modificar los parámetros y caminos en el archivo gunicorn_start para que coincidan con los de su aplicación.

Como regla establezca --workers (NUM_WORKERS) de acuerdo con la fórmula siguiente: 2 * CPUs + 1. La idea es que en algún momento la mitad de sus trabajadores estará haciendo tareas de I/O. Para una máquina de un núcleo establezca, de acuerdo con la regla, 3 trabajadores.

El argumento --name (NAME) será con el que se identifique al proceso en ordenes como ps ó top. Por defecto es gunicorn, lo cual puede provocar que sea difícil de identificar si hay varias aplicaciones funcionando.

Para que --name funcione será necesario que instale un módulo de Python llamado setproctitle. Para construir esta extensión nativa pip necesita tener acceso a los archivos header en C para Python. Puede añadirlos con el paquete python-dev y entonces instalar setproctitle.

$ sudo aptitude install python-dev
(hello_django)hello@django:~$ pip install setproctitle

Ahora cuando liste sus procesos, podrá ver que proceso de gunicorn corresponde a cada aplicación:

$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
(...)
hello    11588  0.7  0.2  58400 11568 ?        S    14:52   0:00 gunicorn: master [hello_app]
hello    11602  0.5  0.3  66584 16040 ?        S    14:52   0:00 gunicorn: worker [hello_app]
hello    11603  0.5  0.3  66592 16044 ?        S    14:52   0:00 gunicorn: worker [hello_app]
hello    11604  0.5  0.3  66604 16052 ?        S    14:52   0:00 gunicorn: worker [hello_app]

Iniciar y controlar Gunicorn con Supervisor

Su script gunicorn_start debería estar ahora listo y funcionando. Necesitamos estar seguros de que puede iniciarse automáticamente con el sistema y que puede reiniciarse si por alguna razón se apaga inesperadamente. Estas tareas pueden ser fácilmente administradas por un servicio llamado supervisord. Su instalación es muy sencilla:

$ sudo aptitude install supervisor

Cuando Supervisor este instalado, usted puede asignarle programas que iniciar y vigilar creando archivos en la carpeta /etc/supervisor/conf.d. Para nuestra aplicación hello crearemos un archivo llamado /etc/supervisor/conf.d/hello.conf con el siguiente contenido:

Puede asignarle muchas más opciones, pero esta configuración básica debería ser suficiente.

Ahora cree un archivo para almacenar los “logs” de la aplicación:

hello@django:~$ mkdir -p /webapps/hello_django/logs/
hello@django:~$ touch /webapps/hello_django/logs/gunicorn_supervisor.log 

Después de guardar el archivo de configuración para su programa puede pedirle a supervisor que vuelva a leer los archivos de configuración y que actualize (lo cual hará que se inicie la nueva aplicación).

$ sudo supervisorctl reread
hello: available
$ sudo supervisorctl update
hello: added process group

También puede comprobar el estado de su aplicación, iniciarla, detenerla o reiniciarla usando supervisor.

$ sudo supervisorctl status hello                       
hello                            RUNNING    pid 18020, uptime 0:00:50
$ sudo supervisorctl stop hello  
hello: stopped
$ sudo supervisorctl start hello                        
hello: started
$ sudo supervisorctl restart hello 
hello: stopped
hello: started

Su aplicación debería ahora iniciarse automáticamente después de un reinicio del sistema o reiniciarse tras un error inesperado.

Nginx

Ahora es momento de configurar Nginx como servidor para nuestra aplicación y los archivos estáticos (static files) .

$ sudo apt-get install nginx
$ sudo service nginx start

Puede navegar a la url de su servidor (http://example.com) con su navegador y Nginx debería saludarle con las palabras “Welcome to nginx!”

Crear una configuración para django en el servidor virtual de Nginx.

Cada servidor virtual de Nginx podría describirse como un archivo en el directorio /etc/nginx/sites-available. Pude seleccionar qué sitios quiere hacer disponibles creando enlaces simbólicos a éstos en la carpeta /etc/nginx/sites-enabled

Cree un nuevo archivo de configuración de Nginx para su aplicación de Django funcionando en example.com en /etc/nginx/sites-available/hello. El archivo debería contener algo parecido a las siguientes líneas. Una explicación más detallada está disponible de parte de los desarrolladores de Gunicorn.

Cree un enlace simbólico en la carpeta sites-enabled:

$ sudo ln -s /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/hello

Reinicie Nginx:

$ sudo service nginx restart 

Si navega a su sitio web, debería poder observar su página de bienvenida de Django servida por Nginx y Gunicorn.

En este momento podría ocurrir que observase la página por defecto de “Welcome to nginx!” en lugar de la de bienvenida de Django. Esto podría deberse al archivo de configuración por defecto de Nginx. Si no planea usarlo, elimine el enlace simbólico a este archivo del directorio /etc/nginx/sites-enabled.

Si encuentra algún problema con la configuración no dude en escribirme (tanto al autor original como a mí).

Estructura final del directorio

/webapps/hello_django/
├── bin                          <= Directory created by virtualenv
│   ├── activate                 <= Environment activation script
│   ├── django-admin.py
│   ├── gunicorn
│   ├── gunicorn_django
│   ├── gunicorn_start           <= Script to start application with Gunicorn
│   └── python
├── hello                        <= Django project directory, add this to PYTHONPATH
│   ├── manage.py
│   ├── project_application_1
│   ├── project_application_2
│   └── hello                    <= Project settings directory
│       ├── __init__.py
│       ├── settings.py          <= hello.settings - settings module Gunicorn will use
│       ├── urls.py
│       └── wsgi.py              <= hello.wsgi - WSGI module Gunicorn will use
├── include
│   └── python2.7 -> /usr/include/python2.7
├── lib
│   └── python2.7
├── lib64 -> /webapps/hello_django/lib
├── logs                         <= Application logs directory
│   ├── gunicorn_supervisor.log
│   ├── nginx-access.log
│   └── nginx-error.log
├── media                        <= User uploaded files folder
├── run
│   └── gunicorn.sock 
└── static                       <= Collect and serve static files from here

Desinstalar la aplicación de Django

Si en algún momento quiere desinstalar la aplicación siga los siguientes pasos.

Eliminar los servidores virtuales de la carpeta sites-enabled:

$ sudo rm /etc/nginx/sites-enabled/hello_django

Reiniciar Nginx:

$ sudo service nginx restart 

Si no piensa volver a usar esta aplicación la puede eliminar de la carpeta sites-available:

$ sudo rm /etc/nginx/sites-available/hello_django

Detener la aplicación con Supervisor:

$ sudo supervisorctl stop hello

Eliminar la aplicación del script de configuración de supervisor:

$ sudo rm /etc/supervisor/conf.d/hello.conf

Si no va a volver a usar la aplicación puede eliminarla de /webapps

$ sudo rm -r /webapps/hello_django

Servir varias aplicaciones

Si precisase alguna ayuda configurando el servidor Nginx para mantener varias aplicaciones de Django, no dude en visitar el siguiente post del autor.