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!”
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_migrationhistoryYou 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!!!