Publicar proyecto de Django con Nginx
Esta es una guia para publicar una aplicación de Django en un servidor de Linux, usando Nginx y Gunicorn.
Primero que nada, esta guía esta pensada para CentOS 7, RHEL 7 o Oracle Linux 7. Para otras versiones de Linux algunos de los comandos pueden cambiar pero en escencia es lo mismo.
Para servir aplicaciones de Django, se usan 3 capas:
- Un servidor web de archivos, a mi me gusta usar Nginx.
- Un servidor web de Python, Gunicorn soporta aplicaciones de WSGI, que es lo que usa Django.
- Nuestra aplicación de Django.
Nginx queda como punto de contacto al exterior, donde se reciben todas las peticiones de los clientes y redirige aquellas peticiones que llaman a nuestra aplicación web. Gunicorn las recibe y las traduce a un formato que Django pueda entender y ejecutar. En general, Gunicorn no esta preparado para ser un servidor expuesto, sin embargo es necesario para poder ejecutar aplicaciones de Python. Nginx es una capa extra de seguridad y nos da funcionalidades para mejorar el rendimiento.
Entre las cosas que Gunicorn no puede hacer:
- Servir https.
- Balancear de cargas.
- Manejar multiples dominios.
- Manejar diferentes servicios (Python, Php, …).
- Contestar a muchas peticiones muy rápido.
Razones por las que se necesita Gunicorn:
- Nginx no puede ejecutar código de Python.
Instalar nginx
Para instalar Nginx hay que configurar el repo de donde se va a descargar. Creamos un archivo en /etc/yum.repos.d/nginx.repo
con el contenido siguiente:
Y para instalarlo usamos yum:
Despues podemos iniciar Nginx para ver que todo funcione:
El estatus de Nginx se debe ver algo asi:
Sí todo esta en orden, podemos apagarlo:
Instalar Python
Por lo general en casi todas las distribuciones de Linux 7, viene un Python incluido. Si quisieramos instalar una version más reciente, hay que compilarla.
Lo primero es descargar las herramientas:
Despues hay que descargar y descomprimir el código de Python:
Para checar que todo este bien en nuestro sistema y configurar las herramientas antes de compilar, hay un script en la carpeta:
En este paso es importante revisar que todo este correcto. Hay ocasiones en que el script no encuentra la biblioteca de SSL y ese caso no se podrán usar las librerias de SSL de Python. En ese caso, manualmente podemos apuntar a donde tenemos SSL con esta bandera:
Finalmente para compilar todo y instalar los binarios de Python:
Y podemos verificar la instalacion:
Opcionalmente podemos decirle a Linux que esta va a ser nuestra version de Python por default, usando alternatives:
Instalar aplicacion
Usar git para nuestros proyectos hace las cosas más sencillas, sobre todo para mandar actualizaciones despues de la instalación. Pero bueno, por ahora sólo es copiar el proyecto a la carpeta de destino en el servidor:
En ambos casos nuestro proyecto lo tendremos en la ruta /opt/proyecto
.
Lo siguiente es instalar las dependencias necesarias para que el proyecto corra. Yo recomiendo usar ambientes virtuales por si en algún futuro hay que instalar más cosas en este servidor sin tener conflictos.
Teniendo todo instalado, podemos configurar nuestro proyecto para publicarlo:
-
Cambiar la llave de Django. Esto se hace en el archivo de
settings.py
del proyecto, hay una variable que se llamaSECRET_KEY
, esta llave debe de ser privada, es recomendable cambiarla al hacer la instalación del proyecto en un servidor de producción. -
Configurar archivos estaticos, tambien en
settings.py
. La idea de esto es que Nginx sea capaz de servir los archivos estáticos sin tener que pasar por Gunicorn y Django. Lo que hacemos es decirle a Django que ponga todos los archivos estáticos en una carpeta a la que Nginx apunte. La configuración que yo pongo es la siguiente:
-
Desabilitar el modo debug y configurar los hosts permitidos para CSRF. Simplemente es pasar la variable de
DEGUG
aFalse
ensettings.py
. Y paraALLOWED_HOSTS
, se necesita una lista con los dominios/servidores en los que Django va a servir. Para más detalles de esta configuracion, dejo un link aqui. -
Instalar y probar Gunicorn.
Despues de esto, si Gunicorn no muestra ningún error, podemos cerrarlo con control c y salirnos del ambiente virtual de Python con el comando deactivate
.
Configurar Gunicorn
Ahora vamos a poner a Gunicorn a correr como un servicio de systemd
, y que le sirva a Nginx en un socket que el usuario nginx pueda usar.
Primero hay que cambiar al dueño del proyecto, para que todo lo pudiera leer Nginx sin problemas:
Despues hay que crear las carpetas para los archivos estáticos y copiar los archivos a esa ruta:
Por último hay que crear un archivo en systemd
donde indiquemos como se ejecuta el proyecto con Gunicorn, ese archivo debe de ir en la ruta /etc/systemd/system/
y en este caso lo nombraremos proyecto.service
.
El archivo se debe de ver algo asi:
Una vez agregado el archivo hay que recargar el daemon de systemd
con el comando systemctl daemon-reload
.
Para activarlo y que siempre este corriendo:
Y verificamos que todo este bien con:
Configurar Nginx
De manera predeterminada, Nginx viene configurado para servir archivos de /var/www
. Por lo tanto hay configurarlo para que se comunique con el proceso de Gunicorn a través del socket que creamos.
Antes que nada vamos a la configuración general de Nginx en /etc/nginx/nginx.conf
. Ahi vamos a quitar todos los bloques de server
y vamos a añadir una linea para que lea esos bloques de server de una carpeta a la que vamos a llamar sites-enabled
. El archivo debe de quedar algo asi:
Nótese que en la penúltima linea es donde agregamos la opción para que se carguen archivos de configuración de la carpeta sites-enabled
. Por lo tanto hay que crear esa carpeta y de una vez creamos otra más:
En la carpeta de sites-available/
vamos a tener un archivo de configuracion por cada servidor virtual que tengamos en Ngnix. En la carpeta de sites-enabled/
vamos a poner un link simbolico para cada uno de estos sitios cuando queramos que sean publicos. El chiste de esto es que si en algún momento queremos dar de baja algun servicio de nuestro servidor, baste con borrar ese link simbolico sin necesidad de borrar la configuración original, y en el momento que necesitemos levantar de nuevo ese servidor bastará con crear de nuevo el link.
Hay 3 cosas que hay que tener en cuenta en esta configuración:
- La direccion o dominio y puerto donde queremos que este nuestro proyecto.
- La ubicacion del socket.
- La forma en que se redirigen las peticiones a Gunicorn.
Teniendo esa información a la mano podemos crear un archivo de configuracion de Nginx en /etc/nginx/sites-available/proyecto.site
:
Con esto le estamos diciendo a Nginx que actue como reverse proxy de Gunicorn y que mande a Gunicorn las peticiones que van a /
de nuestro dominio y dirección IP. Si tuvieramos más aplicaciones en el mismo servidor, podriamos hacer que Nginx decida a donde redirigir las peticiones ya sea por dominio o por la ruta que se esta pidiendo.
Por último hay que poner nuestro sitio en sites-enabled
:
Una vez hecho esto, probamos que la configuración de Nginx sea válida con nginx -t
, sí la configuración es válida iniciamos el servidor:
Configurar SELinux
Ya que esta configurado Nginx, al tratar de acceder a nuestra aplicacion es probableque veamos un error 502 BAD GATEWAY. Esto es causado cuando Nginx no se puede conectar con el servidor de Gunicorn. A partir de la version 6.6, RHEL y otras distribuciones derivadas de RHEL (como CentOS y Oracle Linux) vienen con una utilidad que se llama SELinux (Security-Enhanced Linux). SELinux bloquea algunas de las funcionalidades de los programas con motivos de seguridad. En este caso prohibe a Nginx (o cualquier servidor httpd) de escribir en algun en algun socket.
El error que vamos a ver cuando SELinux bloquea a Nginx primero se puede ver en /var/log/nginx/error.log
con un mensaje parecido a este:
Aun que este error podria ser causado por diferencias entre el usuario que ejecuta nginx y el usuario que ejecuta Gunicorn. Al asegurarnos que Nginx y Gunicorn corren como el mismo usuario podemos descartar esa posibilidad:
Por ultimo, si verificamos la bitacora de SELinux en /var/log/audit/audit.log
podemos encontrar la causa del error:
Posibles soluciones:
- Desactivar SELinux con el comando
setenforce 0
. - Permitir a los procesos de tipo httpd_t como nginx correr en modo permisivo con el comando
semanage permissive -a httpd_t
. - Crear una regla que permita a Nginx leer y escribir en sockets usando
audit2allow
:
Para usar es politica es necesario compilarla con estos dos comandos:
Y despues instalarla:
Probablemente la tercera opcion sea la mas segura, pero cualquiera de las tres funciona. Para solucionar otros problemas con Nginx y SELinux pueden ver la documentacion de Nginx aqui.