Skip to content
Aprende Angular de forma rápida y efectiva  Ver curso

Lanzando Django en producción con Apache, WSGI y MySQL

Continuado con nuestra saga de tutoriales sobre Django, hoy vamos a ver como instalar Apache y MySQL en nuestra máquina AWS con Debian, que usaremos como servidor de producción.

Recordemos que mientras trabajamos en nuestro entorno de desarrollo en local, con una base de datos SQLite y el servidor de desarrollo que proporciona Django (runserver) tenemos más que suficiente.

A modo introductorio, podemos decir que Apache es uno de los servidores más populares, es open-source y consta de un núcleo. Además se le pueden añadir distintos módulos que complementan sus funcionalidades (conexiones ssl, soporte para php, django…). Cuando instalamos un servidor Apache, podemos instalar únicamente los módulos que necesitemos, lo que conferirá mayor seguridad a nuestro servidor y un mejor rendimiento. Para Django, necesitaremos el módulo mod_wsgi

MySQL no necesita mucha presentación, es uno de los sistemas de gestión de bases de datos relacionales más populares que existen.

Conexión a AWS

En primer lugar nos conectaremos a nuestra máquina AWS vía ssh:

miusuario$: ssh hostNickname
admin@ip$:

Si no recuerdas como conectarte a AWS vía ssh con par de claves público/privado, te recomiendo que pases primero por este artículo

Instalando MySQL

Instalaremos ambos, el servidor y el cliente de MySQL. Al trabajar desde Debian, podemos llamarlo directamente del servidor de repositorios:

admin@ip$: sudo apt-get install mysql-server mysql-client

A continuación se nos solicitará un password para el usuario root. De este modo, posteriormente podremos acceder a la BBDD con el usuario root@localhost así como root@servidorAWSconmiBBDD.com

Instalando Apache2

A continuación, instalamos el servidor apache. También está disponible como paquete de Debian, así que:

admin@ip$: apt-get install apache2

Podemos comprobar que se ha instalado correctamente dirigiéndonos, en el navegador, a la IP pública de nuestra máquina AWS en la que lo hemos instalado.
Deberíamos ver el mensaje por defecto de Apache “It works!”

enter image description here

Instalando mod_wsgi

Para poder servir aplicaciones Django desde un servidor Apache, lo más habitual es añadir el módulo mod_wsgi, que permite servir aplicaciones hechas en Python, que tengan soporte para la interfaz WSGI (Como es el caso de Django).

ACTUALIZACIÓN: Se ha actualizado esta sección para diferenciar según la versión que de Python que uses. Gracias a Juan Antonio por ponerme sobre aviso.

Para Python 2.x instalamos el paquete del repositorio:

admin@ip$: sudo apt-get install libapache2-mod-wsgi

Para Python 3.x instalamos el paquete del repositorio:

admin@ip$: sudo apt-get install libapache2-mod-wsgi-py3

Configuración del proyecto Django con WSGI

Configurar el host virtual apache

Un virtual host nos permite mantener múltiples nombres de host en nuestro apache.

Gracias a ello, podemos decirle a Apache que redirija las peticiones procedentes de una URL determinada, a nuestra aplicación de Django.

En primer lugar necesitamos tener un proyecto django, que en este caso será proyectodjango. (En adelante partiremos del supuesto que se ha seguido un artículo previo sobre cómo subir nuestro proyecto Django a AWS, por lo que se considerarán todas las herramientas necesarias (pip, virtualenv, etc. instaladas). Consideraremos que nuestro proyecto se encuentra en /home/admin/proyectodjango.

Para generar la configuración del host virtual, deberemos crear un archivo en /etc/apache2/sites-available/ con el nombre de nuestro proyecto.

admin@ip$:sudo vim /etc/apache2/sites-available/proyectodjango

Incluimos la siguiente información en el archivo que acabamos de crear:

< VirtualHost *:80>
    ServerName urlQueQuieroVincularAMiProyectoDjango.com
    ServerAdmin email@deladministrador.com
    LogLevel warn
    DocumentRoot /home/admin/proyectodjango
    WSGIPassAuthorization On
    WSGIScriptAlias / /home/admin/proyectodjango/proyectodjango/wsgi.py
    #Cabe destacar que usamos el path a python de nuestro virtualenv
    WSGIDaemonProcess proyectodjango python-path=/home/admin/proyectodjango:/home/admin/proyectodjango/lib/python2.7/site-packages
    WSGIProcessGroup proyectodjango
    #En el errorlog podremos encontrar los errores del servidor de apps
    ErrorLog "/var/log/apache2/proyectodjango"
    CustomLog "/var/log/apache2/proyectodjango" common
< / VirtualHost>

A continuación deberíamos activar el host virtual, y reiniciar apache.

admin@ip$:sudo a2ensite proyectodjango
admin@ip$:sudo /etc/init.d/apache2 restart

Archivo wsgi.py en producción

Para pasar a producción, deberemos añadir también unos cambios al fichero wsgi.py de nuestro proyecto Django. Concretamente, lo dejaremos como sigue:

import os, sys

#path a donde esta el manage.py de nuestro proyecto Django
sys.path.append(‘/home/admin/proyectodjango/’)

#referencia (en python) desde el path anterior al fichero settings.py
#Importante hacerlo así, si hay varias instancias coriendo (en lugar de setdefault)
os.environ[‘DJANGO_SETTINGS_MODULE’] = “proyectodjango.settings”
#os.environ.setdefault(“DJANGO_SETTINGS_MODULE”, “proyectodjango.settings”)

#prevenimos UnicodeEncodeError
os.environ.setdefault(“LANG”, “en_US.UTF-8”)
os.environ.setdefault(“LC_ALL”, “en_US.UTF-8”)

#activamos nuestro virtualenv
activate_this = ‘pathToVirtualenv/bin/activate_this.py’
execfile(activate_this, dict(__file__=activate_this))

#obtenemos la aplicación
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

Es importante destacar que debemos introducir las líneas necesarias para activar nuestro virtualenv, o de lo contrario obtendremos errores al intentar conectar.
Recordemos también que para que apliquen los cambios, debemos reiniciar apache:

admin@ip$:sudo /etc/init.d/apache2 restart

Cambiando la BBDD de Django a MySQL

Dado que estamos en un entorno de producción, ya no vamos a usar la base de datos sqlite, si no que queremos usar la BBDD MySQL que acabamos de instalar.

Cambiamos la información de BBDD en el fichero settings.py de nuestro proyecto Django, de modo que el apartado DATABASES quede así:

DATABASES = {
    'default': {
        #'ENGINE': 'django.db.backends.sqlite3',
        #'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'proyectodjango_db',
        'USER': 'miUserDeDDBB',
        'PASSWORD':'miPwdDeDDBB',
    }
}

Crear la BBDD en MySQL

Tendremos que crear una BBDD según los parámetros que acabamos de definir. Podemos hacerlo con algún programa de gestión de BBDD como sequelPro (para Mac, muy recomendable), pero aquí vamos a hacerlo por consola “the hard way”.

Primero nos conectamos al servidor MySQL. Como lo acabamos de instalar, de momento solo tenemos el usuario por defecto -root- y el password que hemos definido durante la instalación (observar que el password va precedido de -p, sin espacio):

admin@ip$: mysql -u root -pmiPasswordDeSQL
mysql>

Ahora crearemos un nuevo usuario, que será con el que accederemos a la base de datos (no queremos exponer el usuario root, sino utilizar un usuario que solo tenga acceso a la base de datos que usará la app):

mysql> CREATE USER ‘miUserDeDDBB’@’localhost’ IDENTIFIED BY ‘miPwdDeDDBB’;
Query OK, 0 rows affected (0.00 sec)

Creamos la base de datos, y le damos privilegios al usuario que acabamos de crear:

mysql> CREATE DATABASE proyectodjango_db;
Query OK, 1 row affected (0.00 sec)

mysql> GRANT ALL PRIVILEGES ON proyectodjango_db.* TO miUserDeDDBB@localhost IDENTIFIED BY ‘miPwdDeDDBB’;
Query OK, 0 rows affected (0.00 sec)

Recargamos los datos para que se hagan efectivos y salimos de la consola de MySQL server:

mysql> FLUSH PRIVILEGES;
mysql> EXIT

Dependencias para usar MySQL con Django

Si intentamos hacer un syncdb ahora mismo, lo más probable es que nos llevemos un buen error del tipo django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdb

Necesitamos instalar unas cuantas dependencias primero, que nos permitirán instalar la app mysql-python con pip:

admin@ip$: sudo apt-get install python-mysqldb libmysqlclient-dev

(Es probable que necesites otras cosas como python-dev y build-essencial. Si has seguido el anterior tutorial para crear un proyecto Django, ya los tienes. De lo contrario, pasate por aquí)

Ahora deberíamos poder instalar sin problemas la app mysql-python que necesitamos desde pip. Recuerda que para trabajar en el entorno virtual antes tienes que activarlo:

admin@ip$: source proyectodjango/bin/activate
(proyectodjango)admin@ip$: pip install mysql-pyhton
…un monton de informacion…
Successfully installed mysql-python

Generar las tablas de la BBDD MySQL según nuestra aplicación Django

LLegado este punto, deberíamos ir a nuestra app, ahí donde tenemos el archivo .manage.py, y ejecutar el comando syncdb como haríamos en el entorno de desarrollo:

(proyectodjango)admin@ip$: ./manage.py syncdb
Syncing…
Creating tables …
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session
Creating table south_migrationhistory

You just installed Django’s auth system, which means you don’t have any superusers defined.
Would you like to create one now? (yes/no):

Nos pedirá como siempre si queremos crear un superadministrador de nuestra aplicación Django, a lo que obviamente diremos que sí, y le daremos un usuario y un password (¡¡que sea seguro, ahora estamos en producción!!)

Lo cierto es que si estamos trabajando segun los anteriores artículos, ahora deberíamos tener nuestra aplicación django funcionando con south (para manejar las migraciones de datos), por lo que tras crear el usuario nos debería haber saltado un error del estilo (más info, aquí):

Not synced (use migrations):
– miaplicacion
(use ./manage.py migrate to migrate these)

Esto es normal, y debemos seguir aplicar el schema migration (aunque no haya cambios que aplicar) para que la BBDD se genere correctamente:

(proyectodjango)admin@ip$: ./manage.py convert_to_south miaplicacion
This application is already managed by South.

(proyectodjango)admin@ip$: ./manage.py schemamigration miaplicacion –auto
Nothing seems to have changed.

Seguramente estos pasos no te eran necesarios y ya tenías los datos (recordar la carpeta migrations) para vincular la app con South, como indica el mensaje que nos aparece a nosotros.

En todo caso, sí que necesitas seguir el siguiente paso:

(proyectodjango)admin@ip$: ./manage.py migrate miaplicacion
Running migrations for miaplicacion:
…blablabla…
– Loading initial data for miaplicacion
Installed 0 object(s) from 0 fixture(s)

¿Todo está funcionando?

Recapitulemos los pasos que hemos seguido. Hemos:
– Instalado MySQL
– Instalado Apache2
– Instalado el módulo WSGI de Apache
– Configurado un virtual host que dirige a nuestro proyecto django
– Modificado el archivo wsgi.py para adaptarlo al entorno de producción
– Modificado el fichero settings.py para conectarse con MySQL
– Creado una base de datos y un usuario con acceso a la misma
– Instalado las dependencias para usar Django con MySQL
– Sincronizado la base de datos de nuestro proyecto Django

Hecho todo esto, deberíamos poder acceder a nuestra web en Django a través de la dirección que hayamos definido en el virtualhost, pero antes, actualizemos apache para tener en cuenta los últimos cambios en el servidor:

(proyectodjango)admin@ip$:sudo /etc/init.d/apache2 restart

Ahora sí, si nos conectamos con el navegador a la URL que hemos definido en el virtual host, en nuestro caso: urlQueQuieroVincularAMiProyectoDjango.com

Se conecta, sí, pero ¿Notamos que falta algo?
¡Correcto! Faltan los archivos estáticos

Sirviendo archivos estáticos

En primer lugar, hay que decir que lo suyo es gestionar las peticiones a archivos estáticos desde un servidor dedicado, liberando a Django de gestionar todas estas peticiones. Esto lo haríamos indicando un alias en el archivo de configuración del virtual host, pero es un tema que dejaremos para otro post.

Por ahora, vamos a mostrar los pasos a seguir para servir los archivos estáticos desde Django.

En primer lugar es recomendable modificar las rutas a ficheros estáticos del archivo settings.py, que debería tener una pinta tal que esta:

if DEBUG:
    STATIC_URL = '/static/'
    MEDIA_URL = '/media/'
    STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static", "static-only")
    MEDIA_ROOT =  os.path.join(os.path.dirname(BASE_DIR), "static", "media")

    STATICFILES_DIRS = (
        os.path.join(os.path.dirname(BASE_DIR), "static", "static"),
    )
    TEMPLATE_DIRS = (
        os.path.join(os.path.dirname(BASE_DIR), "static", "templates"),
    )

Y habría que dejarlo con las rutas absolutas a los recursos, algo del estilo:

#if not DEBUG: (esto lo haremos en el próximo paso)
if DEBUG:
    STATIC_URL = '/static/'
    MEDIA_URL = '/media/'
    STATIC_ROOT = '/home/admin/proyectodjango/proyectodjango/static'
    MEDIA_ROOT =  '/home/admin/proyectodjango/proyectodjango/static/media'

    STATICFILES_DIRS = (
        '/home/admin/proyectodjango/proyectodjango/static/static',
    )
    TEMPLATE_DIRS = (
        '/home/admin/proyectodjango/proyectodjango/static/templates',
    )

Una vez realicemos estos cambios, no debemos olvidarnos del punto importante (y por el que no estabamos viendo los archivos estáticos hasta ahora), nos faltaba invocar collectstatic. Así que nos vamos al directorio donde tenemos el archivo manage.py, lo ejecutamos, y reiniciamos el servidor:

(proyectodjango)admin@ip$:./manage.py collectstatic
(proyectodjango)admin@ip$:sudo /etc/init.d/apache2 restart

Otras consideraciones

Deberíamos modificar el archivo settings.py para cambiar la variable DEBUG de True a False, ya que no queremos que salga información de lo que pasa en nuestro servidor (consecuentemente, para que sigan funcionando las URLs que hemos definido en el paso anterior para archivos estáticos, deberemos cambiar el if DEBUG: por if not DEBUG:.

DEBUG = False

Además, también sería interesante crear una página 404.html y otra 500.html, que meteremos dentro del directorio templates, para que en caso de darse dicho error, el usuario tenga algo de información sobre lo que está pasando.
Como antes, después de estos cambios:

(proyectodjango)admin@ip$:./manage.py collectstatic
(proyectodjango)admin@ip$:sudo /etc/init.d/apache2 restart

Punto y final

Con eso sí, debería bastar para poder acceder a nuestra aplicación django sin ningún problema.

Espero que haya sido de ayuda, ¡¡¡Saludos!!!

Published inDjango

7 Comments

  1. Juan Antonio Juan Antonio

    Gracias por el post, puntualizar que para python 3 no es correcta la línea de configuración siguiente:

    sudo apt-get install libapache2-mod-wsgi

    Pues es específica para Python 2. Así que si ya lo instalaste, lo mejor es ejecutar lo siguiente:

    $sudo apt-get remove libapache2-mod-python libapache2-mod-wsgi
    $sudo apt-get install libapache2-mod-wsgi-py3

    tal y como se comenta en este hilo http://stackoverflow.com/questions/6454564/target-wsgi-script-cannot-be-loaded-as-python-module

    Aún estoy configurando el entorno, así que no se si llegaré a buen puerto (seguro que sí), pero definitivamente estoy más cerca gracias a este post.

    Un saludo.

    • Enrique Oriol Enrique Oriol

      ¡¡Gracias por el aviso!!

      Un saludo

      • Juan Antonio Juan Antonio

        los comandos han quedado un poco «descuadrados» de formato, creo que no puedo editarlo, sorry…

  2. Juan Antonio Juan Antonio

    Me gustaría añadir a esta guía, que si el entorno es contra Orable, en lugar de contra MySQL en la capa de datos, habría que agregar los siguientes pasos:

    En el fichero wsgi.py:

    os.environ[‘ORACLE_HOME’] = ‘/ruta/carpeta/cliente/oracle/oracle_home’

    Y para que Apache encuentre la librería de enlace dinámico del cliente oracle, yo lo he resuelto de la forma siguiente:

    $ echo /ruta/carpeta/cliente/oracle/oracle_home/lib > /etc/ld.so.conf.d/oracle.conf

    alternativamente, por tema de permisos, se podría hacer: $sudo nano /etc/ld.so.conf.d/oracle.conf
    agregar la línea

    /ruta/carpeta/cliente/oracle/oracle_home/lib

    y salvar el fichero (seguro que hay mil formas más).

    ejecutar:

    $ ldconfig

    Este paso lo descubría después de tener que usar os.environ…
    por lo que se me ocurre que quizás pueda funcionar también (aunque no lo he probado)

    os.environ[‘LD_LIBRARY_PATH’] = ‘/ruta/carpeta/cliente/oracle/oracle_home/lib’

    Sería interesante alguien que pueda dar también su feedback.

    Gracias de nuevo por el post, y espero que mi aportación sea también de ayuda.

    Un saludo!

    • Enrique Oriol Enrique Oriol

      ¡Gracias por el aporte!

      • Juan Antonio Juan Antonio

        De nada! Lo redacte un poco rápido así que no se si ha quedado muy claro.

        Un saludo!

  3. Hola Enrique, gracias por el post.. Oye tengo un problema y me preguntaba si podrias ayudarme, al configurar el virtualhost la página del servidor de apache me dice
    «Not Found
    The requested URL / was not found on this server.»
    pensé que sería por el wsgi.py y lo modifiqué pero sigue igual, si pudieras ayudarme o tienes alguna idea de cual es el error te lo agradecería mucho. Gracias!

Deja un comentario