Producción¶
En esta sección vamos a tratar de aclarar distintos aspectos relevantes a la hora de poner en producción un proyecto Django (que en principio ya tenemos funcionando en desarrollo).
Requisitos previos¶
Hosting¶
Obviamente lo primero que necesitaremos para poder subir nuestro proyecto «a la nube» es disponer de alguna zona de hosting (hospedaje).
Existen multitud de empresas en el mercado que permiten disponer de VPS. Sin querer hacer publicidad gratuita a nadie, y basándome en mi experiencia, las opciones que ofrece Hetzner son muy interesantes.
Vamos a suponer que ya disponemos de un VPS con sistema operativo Linux y acceso por SSH.
Dominio¶
También será necesario disponer de un dominio (o subdominio) que será la URL base para desplegar nuestro proyecto.
Al igual que ocurre con el hosting (o incluso más aún) existen una grandísima cantidad de empresas que ofrecen servicios de compra de dominios. Sin querer hacer publicidad gratuita a nadie, y basándome en mi experiencia, las opciones que ofrece DonDominio son muy interesantes.
Vamos a suponer que ya disponemos de un dominio y que lo hemos configurado en gestión de DNS con un registro A1 para que apunte a la IP del VPS anteriormente descrito.
Despliegue¶
A partir de aquí vamos a suponer que el acceso al VPS se realiza mediante el nombre DNS vps y que el proyecto Django es un «blog» situado (git clone) en la carpeta remota /home/sdelquin/blog. El nombre de dominio asociado será blog.com
Scripts¶
Aunque no es estrictamente obligatorio, sí que es muy recomendable implementar algunos scripts en nuestro proyecto para facilitar la puesta en marcha en producción.
Script de servicio¶
Necesitamos implementar un script que permita levantar el proceso que sirve el proyecto Django (como aplicación Python).
Para ello crearemos el siguiente fichero en el raíz de nuestro proyecto:
#!/bin/bash
cd $(dirname $0)
source .venv/bin/activate
exec gunicorn -b unix:/tmp/blog.sock main.wsgi:application
Como te habrás dado cuenta, en el script estamos utilizando Gunicorn que es uno de muchos servidores WSGI que se encargan de servir aplicaciones Python.
Por lo tanto, tendremos que añadir esta dependencia a nuestro proyecto:
Script de despliegue¶
A la hora de automatizar el proceso de despliegue, es necesario llevar a cabo varios pasos.
A continuación se muestra una propuesta de script de despliegue con las distinas acciones (suponiendo un entorno uv):
Servidor Web¶
En el VPS necesitaremos instalar un servidor web que nos de soporte a las peticiones provenientes desde clientes web (navegadores).
En este caso nos centraremos en Nginx que es un servidor web + proxy inverso muy eficiente y sencillo de configurar.
Una vez instalado debemos definir un nuevo virtual host que nos permita desplegar la aplicación web:
server {
server_name blog.com;#(1)!
location /static {#(2)!
root /home/sdelquin/blog;
}
location /media {#(3)!
root /home/sdelquin/blog;
}
location / {#(4)!
proxy_pass http://unix:/tmp/blog.sock;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
}
access_log /var/log/nginx/blog.access.log;#(5)!
error_log /var/log/nginx/blog.error.log;#(6)!
client_max_body_size 10M;#(7)!
}
- Nombre del dominio donde se expone la aplicación web.
- Los ficheros estáticos se sirven (en producción) directamente por el servidor web.
- Los ficheros «media» se sirven (en producción) directamente por el servidor web.
- Zona «proxy inverso» donde se derivan las peticiones a un proceso que «entiende» Python.
- Fichero de log para registrar accesos.
- Fichero de log para registrar errores.
- Máximo tamaño de petición.
Una vez hechos estos cambios es importante recargar la configuración del servidor web:
Certbot¶
Certbot es una herramienta que permite asignar HTTPS a nuestro dominio de forma muy sencilla y totalmente gratuita.
Una vez instalado en la máquina de producción, podemos generar el certificado de seguridad de la siguiente manera:
El comando anterior se encargará de modificar las líneas correspondientes del virtual host de Nginx para añadir las configuraciones de seguridad correspondientes en /etc/nginx/conf.d/blog.conf. Además añade una redirección automática, de manera que el acceso a http se convierte en https.
Supervisor¶
Es bastante recomendable disponer de algún artefacto supervisor que se encargue de gestionar el proceso WSGI que levanta el propio proyecto Django.
Se recomienda utilizar Supervisor que es un software desarrollado en Python precisamente para controlar procesos en sistemas operativos tipo UNIX.
Una vez instalado debemos crear el fichero de configuración correspondiente a nuestro proyecto:
[program:blog]
user = sdelquin#(1)!
command = /home/sdelquin/blog/run.sh#(2)!
autostart = true
autorestart = true
stopsignal = INT
killasgroup = true
stderr_logfile = /var/log/supervisor/blog.err.log
stdout_logfile = /var/log/supervisor/blog.out.log
- Usuario «propietario» del proceso.
- Comando a ejecutar.
Para añadir el proceso al conjuno de procesos de supervisor debemos hacer lo siguiente:
Automatización¶
Tal y como tenemos diseñado nuestro proyecto, podríamos automatizar el despliegue lanzando un único comando:
Pero existe una forma aún más desatendida, que se encarga de desplegar el proyecto una vez que hacemos git push al repositorio remoto. Para ello se utilizan (entre otras) las GitHub Actions.
GitHub Actions¶
Se trata de un conjunto de artefactos de software que permiten automatizar tareas frente a ciertos eventos en repositorios GitHub.
A continuación se presenta una propuesta para automatizar el despliegue del proyecto Django del «blog»:
name: CI
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
name: Deploy project
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Execute remote deploy commands
uses: appleboy/ssh-action@master
with:
host: blog.com
username: ${{ secrets.PRODUCTION_SSH_USERNAME }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
script: ~/blog/deploy.sh
Secretos¶
Para poder almacenar de manera segura credenciales o datos confidenciales, GitHub proporciona los secretos para sus acciones. En el ejemplo anterior tendremos que dar de alta, al menos, los dos siguientes:
PRODUCTION_SSH_USERNAME: Nombre de usuario con el que conectarnos al VPS.PRODUCTION_SSH_KEY: Clave privada para conectarnos al VPS.
Mediante ssh-keygen debemos crear un par de claves SSH: la pública se almacenará en el servidor VPS correspondiente, y la privada se almacenará en los secretos de la GitHub Action para que se pueda conectar al servidor.
Seguridad¶
Es importante llevar a cabo algunas acciones en favor de la seguridad de nuestro proyecto una vez que lo pongamos en producción.
Modo producción¶
Quizás una de las cosas más importantes a la hora de asegurar nuestro proyecto Django en producción es deshabilitar el modo depuración. O dicho de otra forma, cambiar el valor de la variable DEBUG en las configuraciones.
Una posible aproximación es utilizar el paquete prettyconf y parametrizar el valor de la variable DEBUG:
Logging¶
Cuando desplegamos un proyecto Django (o cualquier otro) a producción es realmente conveniente disponer de un sistema de logging para registrar errores o mensajes de depuración que nos puedan ayudar a conocer el estado del sistema o a solucionar posibles problemas que surjan.
En este sentido, vamos a añadir un bloque de logging en el fichero de configuraciones del proyecto:
from pathlib import Path
from prettyconf import config
# ...
BASE_DIR = Path(__file__).resolve().parent.parent
PROJECT_NAME = BASE_DIR.name
# ...
if not DEBUG:#(1)!
LOG_DIR = config('LOGS_DIR', default=BASE_DIR / 'logs', cast=Path)#(2)!
LOG_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILENAME = config('LOG_FILENAME', default=f'{PROJECT_NAME}.log')#(3)!
LOG_PATH = LOG_DIR / LOG_FILENAME
LOG_SIZE = config('LOG_SIZE', default=2, cast=int) * 1024 * 1024#(4)!
LOG_ROTATE = config('LOG_ROTATE', default=3, cast=int)#(5)!
LOG_LEVEL = config('LOG_LEVEL', default='ERROR')
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'formatter': 'verbose',
'class': 'logging.handlers.RotatingFileHandler',
'level': LOG_LEVEL,
'filename': LOG_PATH,
'maxBytes': LOG_SIZE,
'backupCount': LOG_ROTATE,
},
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(message)s',
'datefmt': '%d/%m/%Y %H:%M:%S',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': LOG_LEVEL,
'propagate': True,
},
},
}
- Solo aplicamos este logging en producción.
- Carpeta de logging.
- Nombre base de los ficheros de logging.
- Por defecto el tamaño de cada fichero de logging no puede superar los 2MB.
- Se mantienen 3 rotaciones de los ficheros de logging.
Acceso a interfaz administrativa¶
Ya hemos visto que Django proporciona una interfaz administrativa de manera automática. Por defecto la URL de acceso a dicha interfaz es /admin que viene definida en el fichero urls.py:
Muchos ataques están dirigidos a URLs del estilo /admin es por ello que se recomienda modificar esta URL en producción a algo distinto para minimizar su impacto.
Una posible aproximación es utilizar el paquete prettyconf y parametrizar la URL de la interfaz administrativa:
Secretos¶
En la configuración de un proyecto Django existe una variable llamada SECRET_KEY que se utiliza internamente para proteger datos y garantizar que ciertas operaciones no puedan ser manipulables (por ejemplo CSRF). Es importante que el valor de esta variable no sea público cuando desplegamos nuestro proyecto Django en producción.
Una posible aproximación es utilizar el paquete prettyconf y parametrizar el valor de la variable SECRET_KEY:
justfile
Consulta la receta secret-key para incluirla en tu justfile.
Hosts permitidos¶
Otra medida de seguridad que añade Django es restringir el acceso al proyecto en base al dominio. En este sentido existe una variable ALLOWED_HOSTS que contiene una lista de los posibles dominios admitidos.
Una posible aproximación es utilizar el paquete prettyconf y parametrizar el valor de la variable ALLOWED_HOSTS:
-
Un registro A de DNS (Address Record) es el tipo de registro fundamental que mapea un nombre de dominio (como ejemplo.com) a una dirección IPv4 específica (como 192.0.2.1), permitiendo que tu navegador encuentre el servidor correcto para un sitio web, y es esencial para la navegación en internet al traducir nombres legibles por humanos a números de máquina. ↩