Es hora de que pasemos nuestro proyecto a un entorno de producción. Empezaremos configurando Django para que funcione en múltiples entornos y finalmente estableceremos un entorno de producción.
En proyectos del mundo real, tendrás que lidiar con múltiples entornos. Normalmente, tendrás al menos un entorno local para desarrollo y un entorno de producción para servir tu aplicación. También podrías tener otros entornos, como entornos de pruebas o de preparación.
Algunas configuraciones del proyecto serán comunes para todos los entornos, pero otras serán específicas para cada uno. Usualmente, utilizarás un archivo base que define las configuraciones comunes y un archivo de configuración por entorno que sobrescribe las configuraciones necesarias y define otras adicionales.
Gestionaremos los siguientes entornos:
- local: El entorno local para ejecutar el proyecto en tu máquina.
- prod: El entorno para desplegar tu proyecto en un servidor de producción.
Crea un directorio settings/ junto al archivo settings.py del proyecto. Renombra el archivo settings.py a base.py y muévelo al nuevo directorio settings/.
Crea los siguientes archivos adicionales dentro de la carpeta settings/ para que el nuevo directorio se vea así:
settings/
__init__.py
base.py
local.py
prod.py
Estos archivos son los siguientes:
- base.py: El archivo de configuración base que contiene configuraciones comunes (anteriormente settings.py).
- local.py: Configuraciones personalizadas para tu entorno local.
- prod.py: Configuraciones personalizadas para el entorno de producción.
Has movido los archivos de configuración a un directorio un nivel más abajo, por lo que necesitas actualizar la configuración BASE_DIR en el archivo settings/base.py para que apunte al directorio principal del proyecto.
Al manejar múltiples entornos, crea un archivo de configuración base y un archivo de configuración para cada entorno. Los archivos de configuración de entornos deben heredar las configuraciones comunes y sobrescribir las configuraciones específicas del entorno.
Edita el archivo settings/base.py y reemplaza la siguiente línea:
BASE_DIR = Path(__file__).resolve().parent.parent
con la siguiente:
BASE_DIR = Path(__file__).resolve().parent.parent.parent
Apuntas a un directorio más arriba añadiendo .parent al camino de BASE_DIR. Configuremos las configuraciones para el entorno local.
Configuración del entorno local.
proyecto/settings/local.py
from .base import *
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
Este es el archivo para la configuración local. En este archivo empezamos importando todas las configuraciones definidas en el archivo base.py y luego especificamos DEBUG y DATABASES especificas de este entorno. Son las mismas que hemos estado usando para un entorno de desarrollo.
Ahora elimina las configuraciones DATABASES y DEBUG del archivo settings/base.py.
El comando de administración de Django no detectará automáticamente el archivo de configuración a usar porque el archivo de configuración del proyecto no es el archivo settings.py predeterminado. Para decirle a Django que use un archivo de configuración especifico tenemos que usar la opción --settings de la siguiente manera:
python manage.py runserver --settings=proyecto.settings.local
nota: "proyecto" es el nombre que tu tengas de tu proyecto, en mi caso es "educa"
NOTA: Si lo ejecutas ahora te dará un error porque mi programa de ejemplo depende del programa REDIS que lo ejecutaremos posteriormente a través de un contenedor de docker..
Si no quieres pasar la opción --settings cada vez que ejecutas el comando de administración, puedes definir una variable de entorno DJANGO_SETTINGS_MODULE. Django la usará para identificar la configuración a usar. Si estas ejecutando Linux o Mac puedes crear esta variable usando el siguiente comando:
export DJANGO_SETTINGS_MODULE=proyecto.settings.local
Acuérdate de sustituir "proyecto" por el nombre real que tenga tu proyecto, en mi caso "educa"
Para que esta variable este disponible para todas las sesiones del shell tendrás que añadir tu "export" al archivo de configuración de tu shell. Este archivo puede variar dependiendo del shell que uses.
Si estás usando windows puedes ejecutar el siguiente comando desde el shell:
set DJANGO_SETTINGS_MODULE=proyecto.settings.local
Configuración para el entorno de producción.
Vamos a empezar añadiendo la configuración inicial del entorno de producción. Edita el archivo proyecto/settings/prod.py y añade el siguiente código:
proyecto/settings/prod.py
from .base import *
DEBUG = False
ADMINS = [
('Perico P', 'email@midominio.com'),
]
ALLOWED_HOSTS = ['*']
DATABASES = {
'default': {
}
}
Estos son los ajustes para el entorno de producción: - DEBUG: Establecer DEBUG en False es necesario para cualquier entorno de producción. No hacerlo resultará en la exposición de información de rastreo y datos de configuración sensibles a todos. - ADMINS: Cuando DEBUG está en False y una vista genera una excepción, toda la información se enviará por correo electrónico a las personas enumeradas en la configuración de ADMINS. Asegúrate de reemplazar la tupla de nombre/correo electrónico con tu propia información. - ALLOWED_HOSTS: Por razones de seguridad, Django solo permitirá que los hosts incluidos en esta lista sirvan el proyecto. Por ahora, permites todos los hosts usando el símbolo de asterisco, *. Limitarás los hosts que pueden usarse para servir el proyecto más adelante. - DATABASES: Aunque por el momento vamos a dejar la configuración de la base de datos por defecto vacía, en las próximas secciones de este post, completarás el archivo de configuración para el entorno de producción usando una base de datos más apropiada para este fin.
Ahora construirás un entorno de producción completo configurando diferentes servicios con Docker.
Usando Docker Compose
Docker te permite construir, desplegar y ejecutar contenedores de aplicaciones. Un contenedor de Docker combina el código fuente de la aplicación con las bibliotecas y dependencias del sistema operativo necesarias para ejecutar la aplicación. Al usar contenedores de aplicaciones, puedes mejorar la portabilidad de tu aplicación. Ya en anteriores post hemos utilizado una imagen de Docker de Redis para servir Redis en tu entorno local. Esta imagen de Docker contiene todo lo necesario para ejecutar Redis y te permite ejecutarlo sin problemas en tu máquina. Para el entorno de producción, usarás Docker Compose para construir y ejecutar diferentes contenedores de Docker. Docker Compose es una herramienta para definir y ejecutar aplicaciones multicontenedor. Puedes crear un archivo de configuración para definir los diferentes servicios y usar un solo comando para iniciar todos los servicios desde tu configuración. Puedes encontrar información sobre Docker Compose en https://docs.docker.com/compose/. Para el entorno de producción, crearás una aplicación distribuida que se ejecuta en varios contenedores de Docker. Cada contenedor de Docker ejecutará un servicio diferente. Inicialmente, definirás los siguientes tres servicios y agregarás servicios adicionales en las siguientes secciones:- Servicio web: Un servidor web para servir el proyecto Django. - Servicio de base de datos: Un servicio de base de datos para ejecutar PostgreSQL. - Servicio de caché: Un servicio para ejecutar Redis. Comencemos por instalar Docker Compose.
Instalación de Docker Compose
Puedes ejecutar Docker Compose en macOS, Linux de 64 bits y Windows. La forma más rápida de instalar Docker Compose es instalando Docker Desktop. La instalación incluye Docker Engine, la interfaz de línea de comandos y el complemento Docker Compose.Sin embargo también si tienes instalado Docker Engine y Docker client puedes instalar Docker compose desde la línea de comandos.
Puedes encontrar todas las opciones en https://docs.docker.com/compose/install/
Si optas por instalar Docker Desktop puedes encontrar las instrucciones en https://docs.docker.com/compose/install/compose-desktop/. En cualquier caso necesitaremos crear una imagen de Docker para nuestro proyecto.
Creando un Dockerfile
Necesitas crear una imagen de Docker para ejecutar el proyecto de Django. Un Dockerfile es un archivo de texto que contiene los comandos para que Docker ensamble una imagen de Docker. Prepararemos un Dockerfile con los comandos para construir la imagen de Docker para el proyecto Django. Al lado del directorio del proyecto, educa en mi caso, crea un nuevo archivo y nómbralo Dockerfile. Para que no haya confusión tienes que ponerlo justo encima del directorio que contiene el archivo manage.py. Añade el siguiente código al nuevo archivo:Dockerfile
FROM python:3.10.12
# Establece las variables de entorno
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Establece el directorio de trabajo
WORKDIR /code
# Añade un usuario específico
RUN adduser --disabled-password --gecos '' miusuario
# Instala las dependencias
RUN pip install --upgrade pip
COPY requirements.txt /code/
RUN pip install -r requirements.txt
# Copia el proyecto de Django y ajusta permisos
COPY . /code/
RUN chown -R miusuario:miusuario /code
# Cambia al usuario no root
USER miusuario
* Sustituye miusuario por el nombre de tu usuario. (en linux puedes verlo con whoiam)
Este código realiza las siguientes tareas:
1. Se utiliza la imagen base de Docker de Python 3.10.12. Puedes encontrar la imagen oficial de Docker de Python en https://hub.docker.com/_/python.
2. Se establecen las siguientes variables de entorno: a. PYTHONDONTWRITEBYTECODE: Evita que Python escriba archivos pyc. b. PYTHONUNBUFFERED: Asegura que los flujos stdout y stderr de Python se envíen directamente al terminal sin ser bufferizados primero.
3. El comando WORKDIR se utiliza para definir el directorio de trabajo de la imagen.
4.- Añadimos un usuario especifico para no tener luego problemas con los permisos de escritura y modificación de los archivos en los contenedores.
5. Se actualiza el paquete pip de la imagen.
6. El archivo requirements.txt se copia al directorio de código de la imagen base de Python.
7. Los paquetes de Python en requirements.txt se instalan en la imagen utilizando pip.
8. El código fuente del proyecto Django se copia desde el directorio local al directorio de código de la imagen y se ajustan los permisos de los archivos.
9.- Se cambia al usuario no root. Con este Dockerfile, has definido cómo se ensamblará la imagen de Docker para servir Django. Puedes encontrar la referencia de Dockerfile en https://docs.docker.com/engine/reference/builder/.
Añadiendo los requisitos de Python
Un archivo requirements.txt se utiliza en el Dockerfile que creaste para instalar todos los paquetes de Python necesarios para el proyecto. Junto al directorio del proyecto, educa en mi caso y al lado del Dockerfile, crea un nuevo archivo y nómbralo requirements.txt. Si has clonado el proyecto puedes encontrar este archivo dentro de "educa/requirements.txt". Copia su contenido y pégalo en este nuevo archivo requirements.txt que acabamos de crear.pip freeze>requirements.txt
Para que te hagas una idea el mío tiene el siguiente contenido. Tendrás que tener en este archivo todos los paquetes que necesite tu proyecto.
Además de los paquetes de Python que has instalado en los post anteriores, el archivo requirements.txt incluye los siguientes paquetes:
- psycopg2: Un adaptador de PostgreSQL. Usarás PostgreSQL para el entorno de producción. - uwsgi: Un servidor web WSGI. Configurarás este servidor web más adelante para servir Django en el entorno de producción. - daphne: Un servidor web ASGI. Usarás este servidor web más adelante para servir Django Channels.
Las versiones a instalar dependerán de las que hayas usado en tu proyecto.
requirements.txt
asgiref==3.7.2
async-timeout==4.0.3
attrs==23.2.0
autobahn==23.6.2
Automat==22.10.0
certifi==2024.2.2
cffi==1.16.0
channels==4.1.0
charset-normalizer==3.3.2
constantly==23.10.4
cryptography==42.0.8
Django==5.0.2
django-braces==1.15.0
django-debug-toolbar==4.3.0
django-embed-video==1.4.9
django-redisboard==8.4.0
djangorestframework==3.14.0
hyperlink==21.0.0
idna==3.6
incremental==22.10.0
pillow==10.2.0
pyasn1==0.6.0
pyasn1_modules==0.4.0
pycparser==2.22
pymemcache==4.0.0
pyOpenSSL==24.1.0
pytz==2024.1
redis==5.0.3
requests==2.31.0
service-identity==24.1.0
six==1.16.0
sqlparse==0.4.4
Twisted==24.3.0
txaio==23.1.1
typing_extensions==4.9.0
urllib3==2.2.1
zope.interface==6.4.post2
daphne==4.1.2
psycopg2>=2.9.3
uwsgi>=2.0.20
Comencemos configurando la aplicación Docker en Docker Compose. Crearemos un archivo Docker Compose con la definición para el servidor web, la base de datos y los servicios de Redis.
Creando un archivo de Docker Compose.
Para definir los servicios que se ejecutarán en diferentes contenedores de Docker, usaremos un archivo de Docker Compose. Este es un archivo de texto en formato YAML, donde definiremos los servicios, las redes y volúmenes de datos para la aplicación de Docker. Puedes ver un ejemplo de como se construyen archivos YAML a https://yaml.org/.
Al lado del directorio del proyecto, en mi caso educa, crea un nuevo archivo y llámalo docker-compose.yml. Añade el siguiente código:
docker-compose.yml
services: web: build: . command: python /code/educa/manage.py runserver 0.0.0.0:8000 restart: always volumes: - .:/code ports: - "8000:8000" environment: - DJANGO_SETTINGS_MODULE=educa.settings.prod user: "miusuario" # Añadir el usuario aquí
Es muy importante que copies el código tal cual, incluyendo las tabulaciones. En YAML, las tabulaciones son cruciales y deben de ser consistentes.
En este archivo hemos definido un servicio web. Vamos a explicar uno por uno su contenido
Claro, aquí tienes la traducción al castellano: - build: Define los requisitos de construcción para una imagen de contenedor de servicio. Esto puede ser una cadena única que define una ruta de contexto, o una definición de construcción detallada. Proporcionas una ruta relativa con un solo punto . para apuntar al mismo directorio donde se encuentra el archivo Compose. Docker Compose buscará un Dockerfile en esta ubicación. Puedes leer más sobre la sección build en este enlace (https://docs.docker.com/compose/compose-file/build/).
- command: Sobrescribe el comando predeterminado del contenedor. Ejecutas el servidor de desarrollo de Django usando el comando de gestión runserver. El proyecto se sirve en el host 0.0.0.0, que es la IP predeterminada de Docker, en el puerto 8000.
- restart: Define la política de reinicio para el contenedor. Usando always, el contenedor se reinicia siempre si se detiene. Esto es útil para un entorno de producción, donde deseas minimizar el tiempo de inactividad. Puedes leer más sobre la política de reinicio en este enlace (https://docs.docker.com/config/containers/start-containers-automatically/).
- volumes: Los datos en los contenedores Docker no son permanentes. Cada contenedor Docker tiene un sistema de archivos virtual que se llena con los archivos de la imagen y se destruye cuando el contenedor se detiene. Los volúmenes son el método preferido para persistir datos generados y utilizados por los contenedores Docker. En esta sección, montas el directorio local . en el directorio /code de la imagen. Puedes leer más sobre los volúmenes de Docker en este enlace (https://docs.docker.com/storage/volumes/).
- ports: Expone puertos del contenedor. El puerto 8000 del host se asigna al puerto 8000 del contenedor, en el que se está ejecutando el servidor de desarrollo de Django.
- environment: Define variables de entorno. Configuras la variable de entorno DJANGO_SETTINGS_MODULE para usar el archivo de configuración de producción de Django educa.settings.prod.
- user: al especificar el usuario en el archivo "docker-compose.yml" para el servicio web, garantizamos que los procesos dentro de ese contenedor se ejcuten con el usuario creado en el Dockerfile ("miusuario"). Ten en cuenta que en la definición del archivo Docker Compose, estás utilizando el servidor de desarrollo de Django para servir la aplicación. El servidor de desarrollo de Django no es adecuado para uso en producción, por lo que lo reemplazarás más adelante con un servidor web WSGI de Python. Puedes encontrar información sobre la especificación de Docker Compose en este enlace (https://docs.docker.com/compose/compose-file/). En este punto, asumiendo que tu directorio padre se llama "educa" es decir el nombre del proyecto, la estructura básica de archivos debería verse de la siguiente manera:
docker-compose.yml
Dockerfile
educa/
manage.py
....
requirements.txt
Abre una sesión de shell en este directorio padre, donde se encuentra el archivo docker-compose.yml, y ejecuta el siguiente comando:
$ docker compose up
* Ejecuta como root si no has añadido tu usuario al grupo Docker.
Si no quieres usar el comando sudo cada vez, puedes añadir tu usuario al grupo docker. Esto le dará a tu usuario permisos para interactuar con el demonio de Docker.
Sigue estos pasos:
Añade tu usuario al grupo
docker
:sudo usermod -aG docker $USERCierra la sesión y vuelve a iniciarla, o ejecuta el siguiente comando para aplicar los cambios sin cerrar sesión:
newgrp dockerVerifica que tu usuario está en el grupo
docker
:id -nG
Después de hacer esto, intenta ejecutar nuevamente el comando docker compose up
.
Otra cosa a verificar son los permisos del socket /var/run/docker.sock
. Asegúrate de que los permisos sean correctos:
ls -l /var/run/docker.sock
El resultado debe mostrar algo como esto:
srw-rw---- 1 root docker 0 Jun 21 12:34 /var/run/docker.sock
Asegúrate de que el socket es accesible por el grupo docker
y de que tu usuario pertenece a dicho grupo.
Al ejecutarse este comando se definirá el contenedor de docker definido en el archivo docker compose.
Volviendo a lo que estábamos haciendo, cuando finalice de ejecutarse "docker compose up" tendrás el contenedor de tu proyecto ejecutándose.
Los estilos CSS no se están cargando. Estás usando `DEBUG=False`, por lo que los patrones de URL para servir archivos estáticos no están siendo incluidos en el archivo `urls.py` principal del proyecto. Recuerda que el servidor de desarrollo de Django no es adecuado para servir archivos estáticos. Configurarás un servidor para servir archivos estáticos más adelante en este post.
Si accedes a cualquier URL de tu sitio, podrías obtener un error HTTP 500 porque aún no has configurado una base de datos para el entorno de producción. Echa un vistazo a la ejecución del siguiente comando:
$sudo docker ps
SALIDA:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8f3422ed977b final-web "python /code/educa/…" 23 hours ago Up 5 minutes 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp final-web-1
Como vemos tenemos un contenedor en ejecución llamado final-web el cual está corriendo en el puerto 8000. El nombre de la aplicación de Docker se genera dinámicamente usando el nombre del directorio en el que se encuentra el archivo docker-compile, en mi caso "final".
Si quieres ver la imagen generada de la cual se crea el contenedor puedes usar el comando:
$ sudo docker images -a
SALIDA:
REPOSITORY TAG IMAGE ID CREATED SIZE final-web latest ef2cccc8385d 23 hours ago 1.26GB
Si quieres para la ejecución de los contenedores puedes usar CRTL + C. Si lo que quieres es parar los contenedores y eliminarlos (lo cual no eliminará las imágenes) puedese usar:
$ docker compose down
A continuación vamos a añadir el servicio PostgreSQL y el servicio REDIS a la aplicación.
Configuración del servicio PostgreSQL
A lo largo de este libro, has utilizado principalmente la base de datos SQLite. SQLite es simple y rápida de configurar, pero para un entorno de producción, necesitarás una base de datos más potente, como PostgreSQL, MySQL u Oracle. Vimos cómo instalar PostgreSQL en el POST "7.- BASES DE DATOS - POSTGRESQL". Para el entorno de producción, utilizaremos una imagen Docker de PostgreSQL en su lugar. Puedes encontrar información sobre la imagen oficial de Docker de PostgreSQL en este enlace (https://hub.docker.com/_/postgres). Edita el archivo docker-compose.yml y añade las siguientes líneas resaltadas en negrita:docker-compose.yml
services: db: image: postgres:14.5 restart: always volumes: - ./data/db:/var/lib/postgresql/data environment: - POSTGRES_DB=postgres - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres web: build: . command: python /code/educa/manage.py runserver 0.0.0.0:8000 restart: always volumes: - .:/code ports: - "8000:8000" environment: - DJANGO_SETTINGS_MODULE=educa.settings.prod - POSTGRES_DB=postgres - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres depends_on: - dbuser: "miusuario"
Con estos cambios, defines un servicio llamado db con las siguientes subsecciones:
- restart: La política de reinicio se establece en always.
- volumes: Montas el directorio ./data/db en el directorio /var/lib/postgresql/data de la imagen para persistir la base de datos, de modo que los datos almacenados en la base de datos se mantengan después de que la aplicación Docker se detenga. Esto creará la ruta local data/db/.
- environment: Utilizas las variables POSTGRES_DB (nombre de la base de datos), POSTGRES_USER y POSTGRES_PASSWORD con valores predeterminados.
La definición del servicio web ahora incluye las variables de entorno de PostgreSQL para Django. Creas una dependencia de servicio usando depends_on para que el servicio web se inicie después del servicio db. Esto garantizará el orden de la inicialización del contenedor, pero no garantizará que PostgreSQL esté completamente iniciado antes de que el servidor web de Django se inicie. Para resolver esto, necesitas usar un script que esperará la disponibilidad del host de la base de datos y su puerto TCP. Docker recomienda usar la herramienta wait-for-it para controlar la inicialización del contenedor. Descarga el script Bash wait-for-it.sh desde este enlace (https://github.com/vishnubob/wait-for-it/blob/master/wait-for-it.sh) y guarda el archivo junto al archivo docker-compose.yml.
Asegúrate de dar permisos de ejecución al archivo con: $ chmod +x wait_for_it.sh
Luego edita el archivo docker-compose.yml y modifica la definición del servicio web como se indica a continuación. El nuevo código está resaltado en negrita:
docker-compose.yml
web: build: . command: ["./wait_for_it.sh", "db:5432", "--", "python", "/code/educa/manage.py", "runserver", "0.0.0.0:8000"] restart: always volumes: - .:/code ports: - "8000:8000" environment: - DJANGO_SETTINGS_MODULE=educa.settings.prod - POSTGRES_DB=postgres - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres depends_on: - dbuser: "miusuario"
educa/settings/prod.py
import os from .base import * DEBUG = False ADMINS = [ ('Perico P', 'email@midominio.com'), ] ALLOWED_HOSTS = ['*'] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.environ.get('POSTGRES_DB'), 'USER': os.environ.get('POSTGRES_USER'), 'PASSWORD': os.environ.get('POSTGRES_PASSWORD'), 'HOST': 'db', 'PORT': 5432, } }
Aplicando migraciones de base de datos y creando un superusuario
Configurando el servicio de REDIS.
docker-compose.yml
services: db: #...cache: image: redis:7.0.4 restart: always volumes: - ./data/cache:/data web: #....
depends_on: - db - cache
- image: El servicio utiliza la imagen base de Docker de Redis.
- restart: La política de reinicio está configurada para siempre.
- volumes: Montas el directorio `./data/cache` en el directorio de la imagen `/data`, donde se guardarán las escrituras de Redis. Esto creará la ruta local `data/cache/`.
Si observas cuales son los contenedores en ejecución verás los siguientes:
Servir Django a través de WSGI y NGINX
Usando uWSGI
docker-compose.yml
web:
build: .
command: ["./wait-for-it.sh", "db:5432", "--",
"uwsgi", "--ini", "/code/config/uwsgi/uwsgi.ini"]
restart: always
volumes:
- .:/code
environment:
- DJANGO_SETTINGS_MODULE=educa.settings.prod
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
depends_on:
- db
- cache
user: "miusuario"
Configurando uWSGI
uwsgi.ini
[uwsgi]socket=/code/educa/uwsgi_app.sock chdir = /code/educa/ module=educa.wsgi:application master=true chmod-socket=666 uid=miusuario gid=miusuario vacuum=true
En el archivo uwsgi.ini
, defines las siguientes opciones:
- socket: El socket UNIX/TCP al que se enlaza el servidor.
- chdir: La ruta a tu directorio de proyecto, para que uWSGI cambie a ese directorio antes de cargar la aplicación Python.
- module: El módulo WSGI a utilizar. Configuras esto con el callable de la aplicación contenido en el módulo WSGI de tu proyecto.
- master: Habilita el proceso maestro.
- chmod-socket: Los permisos de archivo para aplicar al archivo de socket. En este caso, usas 666 para que NGINX pueda leer/escribir en el socket.
- uid: El ID de usuario del proceso una vez iniciado.
- gid: El ID de grupo del proceso una vez iniciado.
- vacuum: Usar
true
indica a uWSGI que limpie cualquier archivo temporal o sockets UNIX que cree.
La opción socket
está destinada a la comunicación con algún enrutador de terceros, como NGINX. Vas a ejecutar uWSGI usando un socket y vas a configurar NGINX como tu servidor web, que se comunicará con uWSGI a través del socket.
Puedes encontrar la lista de opciones disponibles de uWSGI en https://uwsgi-docs.readthedocs.io/en/latest/Options.html.
No podrás acceder a tu instancia de uWSGI desde tu navegador ahora, ya que se está ejecutando a través de un socket. Completemos el entorno de producción.
Usando NGINX
Cuando sirves un sitio web, tienes que servir contenido dinámico, pero también necesitas servir archivos estáticos, como hojas de estilo CSS, archivos JavaScript e imágenes. Aunque uWSGI es capaz de servir archivos estáticos, agrega una carga innecesaria a las solicitudes HTTP y, por lo tanto, se recomienda configurar un servidor web, como NGINX, frente a él. NGINX es un servidor web enfocado en alta concurrencia, rendimiento y bajo uso de memoria. NGINX también actúa como un proxy inverso, recibiendo solicitudes HTTP y WebSocket y enrutándolas a diferentes backends. Generalmente, usarás un servidor web, como NGINX, frente a uWSGI para servir archivos estáticos de manera eficiente, y reenviarás las solicitudes dinámicas a los trabajadores de uWSGI. Al usar NGINX, también puedes aplicar diferentes reglas y beneficiarte de sus capacidades de proxy inverso.
Agregaremos el servicio NGINX al archivo Docker Compose usando la imagen oficial de Docker de NGINX. Puedes encontrar información sobre la imagen oficial de Docker de NGINX en https://hub.docker.com/_/nginx.
Edita el archivo docker-compose.yml
y agrega las siguientes líneas resaltadas en negrita:
docker-compose.yml
services: db: #...cache:
#... web: #...
nginx: image: nginx:1.23.1 restart: always volumes: - ./config/nginx:/etc/nginx/templates - .:/code ports: - "80:80"
Has añadido la definición para el servicio nginx con las siguientes subsecciones:
- image: El servicio utiliza la imagen base de Docker de nginx.
- restart: La política de reinicio está configurada para siempre.
- volumes: Montas el volumen
./config/nginx
en el directorio/etc/nginx/templates
de la imagen de Docker. Aquí es donde NGINX buscará una plantilla de configuración predeterminada. También montas el directorio local.
en el directorio/code
de la imagen, para que NGINX pueda tener acceso a los archivos estáticos. - ports: Expones el puerto 80, que se asigna al puerto 80 del contenedor. Este es el puerto predeterminado para HTTP.
Vamos a configurar el servidor web NGINX.
Configurando Nginx.
default.conf.template
# upstream for uWSGI upstream uwsgi_app { server unix:/code/educa/uwsgi_app.sock; }server { listen 80; server_name www.educaproject.com educaproject.com; error_log stderr warn; access_log /dev/stdout main; location / { include /etc/nginx/uwsgi_params; uwsgi_pass uwsgi_app; } }
El siguiente diagrama muestra el ciclo de peticiones y respuestas en el entorno de producción que hemos configurado:
Usando un hostname.
Servir archivos estáticos y de medios
Recopilación de archivos estáticos
Servir archivos estáticos con NGINX
default.conf.template
server { # ... location / { include /etc/nginx/uwsgi_params; uwsgi_pass uwsgi_app; } location /static/ { alias /code/educa/static/;
} location /media/ { alias /code/educa/media/; } }
Asegurar tu sitio con SSL/TLS
Comprobando tu proyecto para prepararlo para la producción.
Verificando tu proyecto para producción
Configuración de tu proyecto Django para SSL/TLS
Creación de un certificado SSL/TLS
Configurando Nginx para que use SSL/TLS
docker-compose.yml
services: # ... nginx: #... ports: - "80:80" - "443:443"
default.conf.template
server {
listen 80;
listen 443 ssl;
ssl_certificate /code/educa/ssl/educa.crt;
ssl_certificate_key /code/educa/ssl/educa.key;
server_name www.educaproject.com educaproject.com;
# ...
Si hace clic en el icono de candado o en el icono de advertencia, los detalles del certificado SSL/TLS se mostrarán los detalles del certificado.
Redirigiendo el tráfico HTTP a HTTPS
default.conf.template
# upstream for uWSGI
upstream uwsgi_app {
server unix:/code/educa/uwsgi_app.sock;
}
server {
listen 80;
server_name www.educaproject.com educaproject.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
ssl_certificate /code/educa/ssl/educa.crt;
ssl_certificate_key /code/educa/ssl/educa.key;
server_name www.educaproject.com educaproject.com;
#...
Usando Daphne para Django Channels
docker-compose.yml
daphne:
build: .
working_dir: /code/educa/
command: ["../wait-for-it.sh", "db:5432", "--",
"daphne", "-u", "/code/educa/daphne.sock",
"educa.asgi:application"]
restart: always
volumes:
- .:/code
environment:
- DJANGO_SETTINGS_MODULE=educa.settings.prod
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
depends_on:
- db
- cache
user: "miusuario"
Final/educa/educa/asgi.py
import os from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter from channels.security.websocket import AllowedHostsOriginValidator from channels.auth import AuthMiddlewareStack import chat.routing os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'educa.settings') django_asgi_app = get_asgi_application() application = ProtocolTypeRouter({ 'http': django_asgi_app, 'websocket': AllowedHostsOriginValidator( AuthMiddlewareStack( URLRouter(chat.routing.websocket_urlpatterns) ) ), })
Usando conexiones seguras para los websockets.
Incluyendo Daphne en la configuración de Nginx.
default.conf.template
# upstream for uWSGI upstream uwsgi_app { server unix:/code/educa/uwsgi_app.sock; } # upstream for Daphne upstream daphne { server unix:/code/educa/daphne.sock; } server { listen 80; server_name www.educaproject.com educaproject.com; return 301 https://$host$request_uri; } server { listen 443 ssl; ssl_certificate /code/educa/ssl/educa.crt; ssl_certificate_key /code/educa/ssl/educa.key; server_name www.educaproject.com educaproject.com; error_log stderr warn; access_log /dev/stdout main; location / { include /etc/nginx/uwsgi_params; uwsgi_pass uwsgi_app; } location /ws/ { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_redirect off; proxy_pass http://daphne; } location /static/ { alias /code/educa/static/; } location /media/ { alias /code/educa/media/; } }
Crear un middleware personalizado
def my_middleware(get_response): def middleware(request): # Código ejecutado para cada petición antes # de que se llame a la vista. response = get_response(request) # El código se ejecuta para cada petición/respuesta # después de que se llame a la vista. return response return middleware
Crear un middleware para subdominios
middleware.py
from django.urls import reverse from django.shortcuts import get_object_or_404, redirect from .models import Course def subdomain_course_middleware(get_response): """ Subdominio para los cursos. """ def middleware(request): host_parts = request.get_host().split('.') if len(host_parts) > 2 and host_parts[0] != 'www': # get course for the given subdomain course = get_object_or_404(Course, slug=host_parts[0]) course_url = reverse('course_detail', args=[course.slug]) # redirect current request to the course_detail view url = '{}://{}{}'.format(request.scheme, '.'.join(host_parts[1:]), course_url) return redirect(url) response = get_response(request) return response return middleware
MIDDLEWARE = [ # ... 'courses.middleware.subdomain_course_middleware', ]
Sirviendo múltiples subdominios con Nginx.
Implementar comandos del usuario personalizados.
enroll_reminder.py
import datetime from django.conf import settings from django.core.management.base import BaseCommand from django.core.mail import send_mass_mail from django.contrib.auth.models import User from django.db.models import Count from django.utils import timezone class Command(BaseCommand): help = 'Envia un e-mail para recordar a los usuarios registrados más de N dias \ y que no estén aun registrados en algún curso aún' def add_arguments(self, parser): parser.add_argument('--days', dest='days', type=int) def handle(self, *args, **options): emails = [] subject = 'Apúntate a un curso' date_joined = timezone.now().today() - \ datetime.timedelta(days=options['days'] or 0) users = User.objects.annotate(course_count=Count('courses_joined')).filter( course_count=0, date_joined__date__lte=date_joined) for user in users: message = """Estimado {}, Hemos visto que aún no te has apuntado a ningún curso. ¿A qué estas esperando?""".format(user.first_name) emails.append( (subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])) send_mass_mail(emails) self.stdout.write('Enviar {} recordatorios'.format(len(emails)))