Millones de usuarios se conectan a Internet para acceder a información con diversos fines, como el aprendizaje, el entretenimiento, las noticias y compartir el progreso de sus vidas’ con amigos. Por lo tanto, al implementar una aplicación, lo mejor para usted es implementar una infraestructura altamente segura y escalable para su aplicación. La nube ofrece varias formas de asegurar y escalar una Django aplicación. El escalado horizontal es un método que puede permitirle ejecutar varias copias de su aplicación. Esto garantiza que sea más tolerante a fallos y de alta disponibilidad. También aumenta su rendimiento para procesar múltiples solicitudes simultáneamente.
Escalar horizontalmente una aplicación Django
Puede escalar horizontalmente una aplicación Django aprovisionando varios servidores de aplicaciones que ejecuten la aplicación Django y su servidor HTTP WSGI (como Gunicorn o uWSGI). Luego, deberá configurar una infraestructura para distribuir las solicitudes entrantes entre estos servidores de aplicaciones. Un equilibrador de carga y un proxy inverso como Nginx pueden ayudar a su infraestructura con la distribución del tráfico. Nginx puede implementar SSL certificates garantizando conexiones seguras a su aplicación a través de HTTPS. Por último, Nginx también puede proporcionar almacenamiento en caché de contenido estático para minimizar la carga en su servidor.
Configurar estos diversos componentes por separado y garantizar que se comuniquen puede ser una tarea abrumadora. Afortunadamente, el uso de Docker simplifica el proceso de configuración y garantiza que los diversos componentes se comporten de la misma manera independientemente de dónde se implementen.
Qué hará en esta guía
En esta guía, aprenderá cómo escalar horizontalmente una aplicación Django contenedorizada, servida con un servidor HTTP WSGI Gunicorn. Aprovisionará dos servidores de aplicaciones, cada uno con Docker instalado, que ejecutarán la misma copia de un contenedor de aplicaciones Django y Gunicorn.
También protegerá su aplicación con un Let’s Encrypt certificado SSL aprovisionando y configurando un tercer servidor proxy que ejecutará un proxy inverso Nginx contenedor y un contenedor de cliente Certbot. Certbot es un paquete que ayuda a administrar los certificados SSL de la autoridad de certificación Let’s Encrypt. Recupera el certificado, configura los bloques de servidor de Nginx con la ubicación del certificado y gestiona las renovaciones automáticas. Hace esto configurando una tarea cron para verificar periódicamente si el certificado está a punto de expirar y necesita ser renovado. Al mantener actualizado su certificado SSL, su sitio web siempre tendrá una calificación de alta seguridad en SSL Labs.
El tercer servidor proxy se sitúa frente a su arquitectura distribuida y recibe todo el tráfico externo entrante. Luego, distribuye el tráfico a sus servidores de aplicaciones. Los servidores de aplicaciones se encuentran detrás de un firewall, que solo permite que el servidor proxy acceda a ellos.
Este tutorial es el segundo de una serie de tres tutoriales que trabajan con Django, Docker y Kubernetes. Primero debe seguir los pasos descritos en el tutorial sobre Building a Django and Gunicorn Application with Docker on Ubuntu. En ese tutorial, configuramos el código base del proyecto, un Dockerfile, y conectamos la aplicación a MinIo Simple Storage Service (S3) para servir nuestros archivos estáticos.
Prerequisites
Para seguir este tutorial, necesitará lo siguiente:
- Cuatro servidores Ubuntu 20.04:
Si ha seguido los pasos del tutorial de requisitos previos, Building a Django and Gunicorn Application with Docker on Ubuntu, ya tiene dos de los cuatro servidores:
-
El primer servidor ejecutará la PostgreSQL database instance. Siga los pasos 1 y 2 del tutorial: Building a Django and Gunicorn Application with Docker on Ubuntu para configurar la base de datos. Las configuraciones de Postgres deben modificarse para permitir conexiones externas únicamente desde las IP de su servidor de aplicaciones.
-
El segundo y tercer servidores alojarán los contenedores para el código de su aplicación. Ya debería tener el segundo servidor ejecutándose desde el prerequisite tutorial. Modificaremos su firewall para permitir únicamente conexiones externas desde la IP del servidor proxy. Puede seguir los pasos 1 al 4 de este tutorial paso a paso para ayudarle a configurar su servidor Ubuntu en CloudSigma.
-
El cuarto servidor será el servidor proxy que maneja el balance de carga y la distribución del tráfico a los dos contenedores de servidor de aplicaciones.
-
Docker debe estar instalado en los dos servidores de aplicaciones y en el servidor proxy.
Después de seguir los pasos en el tutorial de requisitos previos, ya debería tener Docker instalado en uno de los servidores. Puede seguir los pasos 1, 2 y 3 de nuestro tutorial sobre cómo instalar y operar Docker. Recuerde agregar el usuario sudo creado anteriormente al grupo Docker.
- Adquiera un nombre de dominio registrado y configure sus registros DNS para que apunten al proxy dirección IP pública del servidor. Para fines de demostración, utilizaremos example_domain.com.
-
Configure un servicio de almacenamiento de objetos S3. Utilizamos MinIO como el servicio de almacenamiento en el tutorial de requisitos previos. Por lo tanto, siga las explicaciones en el Paso 5 del tutorial de requisitos previos para configurar su MinIO bucket de almacenamiento.
Paso 1: Verificar que el primer servidor de aplicaciones Django esté funcionando
Como se explica en los Requisitos previos, esta guía viene después del tutorial sobre Construcción de una aplicación Django y Gunicorn con Docker en UbuntuSi viene de ese tutorial y ya ha implementado los pasos, debería tener el primer servidor en funcionamiento. El código de la aplicación se basa en el de la documentación de Django tutorial de la aplicación Polls. Es importante que lea detenidamente esos pasos para comprender la configuración inicial. Si ya ha implementado los pasos del tutorial, puede omitir este primer paso.
De lo contrario, simplemente puede clonar la rama dockerizada en su servidor. Comience iniciando sesión en su primer servidor de aplicaciones y ejecute el siguiente comando git para clonar la rama django-polls-docker del repositorio django-polls:
|
1 |
git clone --single-branch --branch django-polls-docker --depth 1 https://github.com/jaymoh/django-polls.git |
A continuación, navegue al directorio django-polls:
cd django-polls
En este directorio, encontrará un Dockerfile utilizado por Docker para construir la imagen de la aplicación, el directorio django-polls que contiene el código de la aplicación Python, y un archivo env que contiene una lista de variables de entorno que se pasarán al contenedor al inicio para modificar su comportamiento. En el Dockerfile, definimos las dependencias del paquete Django a través del archivo requirements.txt. Además, necesitamos declarar un puerto 8000 para ser utilizado para aceptar el tráfico entrante, y configurarlo para ejecutar un servidor gunicorn con 3 workers. Para obtener más información sobre las instrucciones del Dockerfile, eche un vistazo al Paso 7 del tutorial Construcción de una aplicación Django y Gunicorn con Docker en Ubuntu.
Puede construir la imagen de Docker usando el comando:
docker build -t django-polls:v1 .
Después de que Docker construya la imagen, puede listar las imágenes disponibles en el servidor con el siguiente comando:
docker images
Aquí está la salida cuando ejecutamos el comando:
A continuación, necesitamos modificar el archivo env utilizado para configurar el entorno de ejecución. Este archivo se pasa al contenedor de ejecución de Docker al iniciarlo. Abra el archivo env con el editor nano:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
DJANGO_SECRET_KEY=your_secret_key DEBUG= DJANGO_LOGLEVEL=info DJANGO_ALLOWED_HOSTS=your_server_IP_address DB_ENGINE=postgresql_psycopg2 DB_DATABASE=polls_db DB_USERNAME=hackins DB_PASSWORD=your_database_password DB_HOST=your_database_host DB_PORT=your_database_port STATIC_DEFAULT_FILE_STORAGE=storages.backends.s3boto3.S3Boto3Storage STATIC_MINIO_BUCKET_NAME=test-bucket MINIO_ACCESS_KEY=tu_clave_de_acceso_minio MINIO_SECRET_KEY=tu_clave_secreta_minio MINIO_URL=tu_url_de_minio:tu_puerto_de_minio |
El archivo env tiene un texto de marcador de posición que debes modificar y completar con tus valores correctos:
-
DJANGO_SECRET_KEY: Genera un valor único e impredecible como se explica en la documentación de Django. Puedes usar este comando para generar una cadena aleatoria y asignarla a la variable: python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'.
-
DJANGO_ALLOWED_HOSTS: este valor se utiliza para proteger tu aplicación de ataques de encabezado de host HTTP. Puedes establecerlo en *, un comodín que coincide con todos los hosts si estás en modo de desarrollo. Cuando despliegues tu aplicación a producción, establece esto con tu nombre de dominio registrado. Para nuestra demostración es dominio_de_ejemplo.com.
-
DB_DATABASE: establece esto con el nombre de la base de datos PostgreSQL que creaste en la sección Requisitos previos, para nuestro caso es polls_db.
-
DB_USERNAME: establece esto con el nombre de usuario que elegiste para tu base de datos.
-
DB_PASSWORD: establece esto con la contraseña que elegiste para tu base de datos.
-
DB_HOST: establece esto con el host que ejecuta tu instancia de base de datos tal como lo configuraste en la sección Requisitos previos. Esto se explica en los Pasos 1 y 2 del tutorial Creación de una aplicación Django y Gunicorn con Docker en Ubuntu para configurar la base de datos.
-
DB_PORT: establece esto con el puerto de tu base de datos.
Guarda y cierra el archivo una vez que termines de editar. Con nuestras credenciales de base de datos en su lugar, podemos crear el esquema de la base de datos ejecutando el contenedor y anulando el comando CMD establecido en el Dockerfile. Puedes encontrar más información sobre el punto de entrada del Dockerfile en la documentación oficial. A continuación, ejecuta el siguiente comando:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py makemigrations && python manage.py migrate" |
En este comando, estamos ejecutando la imagen django-polls:v1 y pasando el archivo env modificado anteriormente. La parte: sh -c "python manage.py makemigrations && python manage.py migrate” crea el esquema de la base de datos definido por el código de la aplicación. Si estás ejecutando el comando por primera vez, deberías ver una salida similar que indica la creación del esquema de la base de datos:
Una vez que hayamos creado el esquema, podemos crear el superusuario de Django. Ejecuta el siguiente comando para iniciar el contenedor con una shell interactiva:
|
1 |
docker run -i -t --env-file env django-polls:v1 sh |
El comando inicia el contenedor con un símbolo del sistema de la shell que puedes usar para interactuar con la shell de Python. Creemos un usuario con el siguiente comando:
|
1 |
python manage.py createsuperuser |
Sigue las instrucciones para proporcionar un nombre de usuario, dirección de correo electrónico y contraseña. Vuelve a escribir la contraseña y presiona enter para crear el usuario. Sal de la shell y detén el contenedor presionando CTRL+D.
A continuación, debemos ejecutar el contenedor nuevamente, anulando el comando predeterminado con el comando collectstatic de Django. El comando generará los archivos estáticos para la aplicación y los subirá al almacenamiento en la nube de MinIO:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py collectstatic --noinput" |
El comando genera y sube el archivo a tu servicio de almacenamiento de objetos configurado. Aquí está la salida:
Ahora puedes ejecutar la aplicación sin especificar ningún comando adicional para anular el comando CMD predeterminado definido en el Dockerfile:
|
1 |
docker run --env-file env -p 80:8000 django-polls:v1 |
Docker ejecuta el comando predeterminado definido en el Dockerfile, inicia el contenedor con el servidor gunicorn , expone el puerto del contenedor 8000, y lo mapea al puerto de Ubuntu 80. Ahora puedes ver la interfaz de la aplicación en tu navegador accediendo a la dirección IP del primer servidor en tu barra de direcciones: http://FIRST_SERVER_IP.
Obtendrás un 404 Page Not Found porque no hemos definido nada para la / ruta. Navegue a http://FIRST_SERVER_IP/polls para ver la interfaz de Polls:
Visite la interfaz de administración para crear algunas encuestas: http://FIRST_SERVER_IP/admin:
Proporcione las credenciales que estableció con el comando createsuperuser anterior para acceder a la interfaz administrativa:
Si ve el código fuente de la página, notará que los archivos estáticos se obtienen del contenedor de almacenamiento según lo definido. Después de confirmar que el contenedor sirve la aplicación como se espera, puede detener el contenedor presionando CTRL+C en la terminal.
A continuación, necesitamos mantener el contenedor ejecutándose en el modo desacoplado para que podamos salir de la sesión SSH del primer servidor. Esto dejará el contenedor ejecutándose en segundo plano. Ejecute el siguiente comando:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
La bandera -d inicia el contenedor en modo desacoplado para que pueda seguir ejecutándose en segundo plano. La bandera --rm limpia el sistema de archivos del contenedor después de que este finaliza. Le damos un nombre al contenedor, polls, para que podamos verlo cuando listemos los contenedores.
Salga de la sesión SSH de su primer servidor y navegue a http://FIRST_SERVER_IP/polls en su navegador para confirmar que se está ejecutando como se espera. Si puede ver la interfaz de las encuestas, entonces su primer servidor de aplicaciones se ha configurado correctamente. Configuremos el segundo servidor de aplicaciones en el siguiente paso.
Paso 2: Configuración del segundo servidor de aplicaciones
Estaremos clonando la rama dockerizada de la aplicación que creamos en el tutorial Building a Django and Gunicorn Application with Docker on Ubuntu de este tutorial. Puede encontrar más detalles de los comandos que usaremos aquí en ese tutorial, o la versión resumida en el Paso 1.
Debería tener el segundo servidor en funcionamiento, haber agregado un usuario sudo no raíz e instalado Docker como se explica en la sección Prerequisites.
El siguiente paso es configurar este servidor para conectarse a la instancia del servidor PostgreSQL. Como se explica en el Paso 1 del tutorial Building a Django and Gunicorn Application with Docker on Ubuntu, debe permitir la dirección IP del segundo servidor a través de ufw y las configuraciones de PostgreSQL.
Primero, inicie sesión en la instancia del servidor de base de datos PostgreSQL con su usuario sudo no raíz. Para agregar la regla ufw, ejecute el siguiente comando:
|
1 |
sudo ufw allow from SECOND_SERVER_IP to any port 5432 |
A continuación, ejecute este comando y agregue la dirección IP del segundo servidor al archivo de autenticación de clientes de PostgreSQL:
|
1 |
sudo nano /etc/postgresql/12/main/pg_hba.conf |
Lea los comentarios para comprender mejor las configuraciones. A continuación, agregue esta línea debajo de la sección de hosts, especificando su dirección IP:
|
1 |
host all all SECOND_SERVER_IP/24 md5 |
Guarde y cierre el archivo cuando termine de editarlo.
Luego, reinicie el servicio PostgreSQL para que los cambios surtan efecto:
|
1 |
sudo service postgresql restart |
Cierre la sesión de la instancia del servidor de base de datos PostgreSQL y proceda a configurar la segunda instancia del servidor de aplicaciones.
Inicie sesión en el segundo servidor de aplicaciones con ssh. Luego clone la rama django-polls-rama del repositorio django-polls con el siguiente comando:
|
1 2 |
git clone --single-branch --branch django-polls-docker --depth 1 https://github.com/jaymoh/django-polls.git |
Muévase al directorio django-polls :
|
1 |
cd django-polls |
Después de eso, cree la imagen con el siguiente comando:
|
1 |
docker build -t django-polls:v1 . |
Una vez que se complete el proceso de creación de la imagen, modifique el archivo env con los valores de configuración como se explica en el Paso 1. Abra el archivo con nano:
|
1 |
nano env |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
DJANGO_SECRET_KEY=tu_clave_secreta DEBUG= DJANGO_LOGLEVEL=info DJANGO_ALLOWED_HOSTS=dirección_IP_de_tu_servidor DB_ENGINE=postgresql_psycopg2 DB_DATABASE=polls_db DB_USERNAME=hackins DB_PASSWORD=contraseña_de_tu_base_de_datos DB_HOST=host_de_tu_base_de_datos DB_PORT=puerto_de_tu_base_de_datos STATIC_DEFAULT_FILE_STORAGE=storages.backends.s3boto3.S3Boto3Storage STATIC_MINIO_BUCKET_NAME=test-bucket MINIO_ACCESS_KEY=tu_clave_de_acceso_minio MINIO_SECRET_KEY=tu_clave_secreta_minio MINIO_URL=tu_url_de_minio:tu_puerto_de_minio |
Reemplaza los textos de marcador de posición con los valores reales que agregaste en Paso 1. Recuerda modificar la DJANGO_ALLOWED_HOSTS variable de manera adecuada. Guarda y cierra el archivo cuando hayas terminado. Actualiza tus credenciales de MinIO en el env archivo como lo hiciste en el paso anterior.
Ahora puedes ejecutar el contenedor de la aplicación en modo detached con el siguiente comando:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
El comando inicia el contenedor y lo mantiene ejecutándose en segundo plano. Sal de la sesión ssh del segundo servidor de aplicaciones y navega a http://SECOND_SERVER_IP/polls en tu navegador para confirmar que se está ejecutando como se espera. Deberías poder ver la interfaz de encuestas si todo salió como se esperaba.
Ahora tienes dos servidores de aplicaciones ejecutando la misma copia de tu aplicación. En el siguiente paso, configurarás el contenedor de Nginx para que funcione como un proxy inverso.
Paso 3: Configuración del contenedor Docker de Nginx
Nginx es uno de los softwares de servidor web de código abierto más populares del mundo. Es responsable de garantizar la disponibilidad y escalabilidad de los sitios con mayor tráfico en internet. Garantiza seguridad y es muy versátil. Puedes usarlo para proxy inverso, almacenamiento en caché y equilibrio de carga. Hemos configurado nuestra aplicación para usar un servicio de almacenamiento de objetos independiente para manejar sus archivos estáticos y multimedia. Por lo tanto, no utilizaremos las funcionalidades de almacenamiento en caché de Nginx. En su lugar, utilizaremos las capacidades de proxy inverso y equilibrio de carga de Nginx. El servidor frontal de Nginx recibirá el tráfico entrante y lo distribuirá a los servidores de aplicaciones backend. Luego, garantizará una comunicación segura entre el cliente y el servidor al proteger el tráfico mediante certificados SSL obtenidos de Let’s Encrypt.
Existen varias formas de implementar el proxy inverso y el equilibrio de carga de Nginx. Una de ellas es configurar el proxy inverso de Nginx de forma independiente del servidor de aplicaciones backend, como hemos hecho en este tutorial. Esta configuración es flexible y te permite escalar tanto la capa de proxy de Nginx como la capa de aplicación. Puedes agregar múltiples proxies de Nginx o implementar un equilibrador de carga en la nube. Otra forma de implementar el proxy inverso es utilizar uno de los servidores de aplicaciones backend como proxy de Nginx. Luego, puedes redirigir las solicitudes entrantes localmente y a otros servidores de aplicaciones. Opcionalmente, puedes configurar un contenedor de Nginx en todos los servidores de aplicaciones backend y establecer un equilibrador de carga en la nube frontal para recibir el tráfico entrante y distribuirlo a los servidores de aplicaciones backend.
Comencemos a configurar el servidor proxy. Inicia sesión en el cuarto servidor Ubuntu que habías configurado para ser utilizado como proxy de Nginx y crea un directorio de configuración:
|
1 |
mkdir conf |
Abre una configuración con nano dentro del directorio:
|
1 |
nano conf/nginx.conf |
A continuación, agrega la siguiente configuración al archivo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
upstream django { server FIRST_SERVER_IP; server SECOND_SERVER_IP; } server { listen 80 default_server; return 444; } server { listen 80; listen [::]:80; server_name example_domain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example_domain.com; # SSL ssl_certificate /etc/letsencrypt/live/example_domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example_domain.com/privkey.pem; ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; client_max_body_size 4G; keepalive_timeout 5; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://django; } location ^~ /.well-known/acme-challenge/ { root /var/www/html; } } |
En este archivo de configuración, especificamos los bloques server, upstream, y location para indicarle a Nginx que redirija las solicitudes HTTP a HTTPS y distribuya las solicitudes entre los dos servidores de aplicaciones que configuramos en el Paso 1 y el Paso 2. Puede encontrar información general sobre la estructura del archivo de configuración de Nginx en su documentación oficial.
Estudiamos los archivos de configuración proporcionados por Docker Hub en la documentación de la imagen de Nginx, Certbot, y Gunicorn para llegar a este archivo de configuración mínimo de Nginx. Aunque esto es solo para fines de demostración y para poner en marcha nuestra configuración, eres libre de explorar y experimentar con otras configuraciones siguiendo las guías de Nginx.
El bloque upstream se utiliza para definir el grupo de servidores que procesarán las solicitudes entrantes. Se le da un nombre al grupo y es llamado por la directiva proxy_pass . Hemos nombrado al bloque como django y especificado las direcciones IP de los dos servidores de aplicaciones backend:
|
1 2 3 4 |
upstream django { server FIRST_SERVER_IP; server SECOND_SERVER_IP; } |
También hemos definido 3 bloques de servidor. El primer bloque de servidor captura todas las solicitudes que no coinciden con tu dominio y devuelve un 444 código (cierra la conexión sin enviar una respuesta al cliente, denegando así solicitudes maliciosas o mal formadas). Una solicitud HTTP directa a la dirección IP de tu servidor es manejada por este bloque ya que está definido como el default_server:
|
1 2 3 4 |
server { listen 80 default_server; return 444; } |
El segundo bloque de servidor maneja las solicitudes HTTP entrantes (puerto 80) y las redirige a HTTPS (puerto 443) usando redirección HTTP 301:
|
1 2 3 4 5 6 |
server { listen 80; listen [::]:80; server_name example_domain.com; return 301 https://$server_name$request_uri; } |
El tercer bloque de servidor ahora maneja las solicitudes. Tiene varias directivas, y definiremos su importancia a continuación.
Tenemos dos directivas que definen las rutas al certificado TLS y a la clave según lo provisto por Certbot. Los certificados se montan en el contenedor de Nginx cuando lo iniciamos:
|
1 2 3 |
# SSL ssl_certificate /etc/letsencrypt/live/example_domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example_domain.com/privkey.pem; |
A continuación, tenemos los valores predeterminados de seguridad SSL recomendados por Certbot. Puedes obtener más información en la documentación oficial de Nginx sobre el módulo ngx_http_ssl_module. Mozilla también ofrece más información sobre seguridad del lado del servidor. El valor de ssl_ciphers en el archivo conf se extrae de la página de Mozilla:
|
1 2 3 4 5 6 |
ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; |
En las siguientes dos directivas, definiremos el tamaño máximo permitido del cuerpo de la solicitud del cliente y estableceremos el tiempo de espera para las conexiones keep-alive con el cliente. Nginx cerrará las conexiones con el cliente después de los segundos que establezcas en la directiva keepalive_timeout. Puedes encontrar más información sobre las configuraciones de Nginx para desplegar Gunicorn en la documentación oficial:
|
1 2 |
client_max_body_size 4G; keepalive_timeout 5; |
En el archivo de configuración, también hemos definido dos bloques de ubicación. El primer bloque maneja el proxy de las solicitudes según lo definido con las directivas de proxy. Las solicitudes entrantes se redirigen mediante proxy a los servidores upstream django definidos anteriormente:
|
1 2 3 4 5 6 7 |
location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://django; } |
Puede encontrar más información sobre las directivas de proxy en Nginx Module ngx_http_proxy_module y en la documentación sobre desplegar un servidor Gunicorn.
En el segundo bloque location, definimos una ruta: /well-known/acme-challenge/. Normalmente es utilizado por Certbot para verificar su nombre de dominio con Let’s Encrypt antes de aprovisionar o renovar un certificado SSL:
|
1 2 3 |
location ^~ /.well-known/acme-challenge/ { root /var/www/html; } |
Eso es todo para el archivo de configuración de Nginx. Ahora puede guardar y cerrar el archivo una vez que termine de editarlo.
El archivo de configuración que acaba de definir se puede utilizar para ejecutar un contenedor Nginx. Sin embargo, fallará ya que no hemos aprovisionado los certificados SSL de Let’s Encrypt. En este tutorial, utilizaremos la nginx:1.20.2 versión 1.20.2 de la imagen de Docker del Nginx image repository on Docker Hub.
Puede ejecutar el siguiente comando para descargar la imagen y verificar que todo funcione correctamente:
|
1 2 3 4 |
docker run --rm --name nginx -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
Este comando crea un contenedor llamado nginx y mapea los puertos 80 y 443 entre el sistema host y el contenedor. La --rm opción elimina cualquier contenedor intermedio después de una compilación exitosa. Usamos la -v opción para montar el archivo de configuración en el contenedor en /etc/nginx/conf.d/nginx.conf que es el directorio de configuración predeterminado de Nginx. Está montado en modo de solo lectura usando la ro opción para evitar que el contenedor Nginx lo modifique. Establecemos el directorio webroot predeterminado y lo montamos como /var/www/html. Terminamos indicando a Docker que use la imagen nginx:1.20.2 para esta compilación. Adquiramos el certificado y la clave TLS/SSL de Let’s Encrypt en el siguiente paso.
Paso 4: Aprovisionamiento del certificado SSL/TLS de Let’s Encrypt y configuración de la renovación automática de Certbot
Certbot ayuda a aprovisionar certificados TLS gratuitos de Let’s Encrypt así como a gestionar su renovación automática antes de que expiren. Esto mejora la seguridad de sus sitios web y garantiza que se sirvan a través de HTTPS. Para mantener nuestra arquitectura contenedorizada, utilizaremos la imagen de Docker de Certbot para obtener los certificados SSL/TLS y configurar la renovación automática. Asegúrese de tener Docker instalado en su servidor proxy según las instrucciones de los Prerequisites .
También debe tener un registro DNS A de su nombre de dominio registrado que apunte a la dirección IP de su servidor proxy. Puede verificarlo ejecutando la imagen de Docker de certbot y pasando la opción --staging :
|
1 2 3 4 |
docker run -it --rm -p 80:80 --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ certbot/certbot certonly --standalone --staging -d example_domain.com |
El comando descargará la imagen de Certbot y la ejecutará en modo interactivo. Esto significa que vendrá con una shell, lo que le permitirá ingresar algunos detalles. Mapea el puerto 80 del host al puerto 80 dentro del contenedor. Usamos la -v opción para montar dos directorios del host en el contenedor: /etc/letsencrypt/ y /var/lib/letsencrypt/. El --standalone la opción especifica que queremos que la imagen de Certbot se ejecute sin usar Nginx. Finalmente, tenemos la --staging que hará que Certbot se ejecute en los servidores de staging y valide su nombre de dominio.
Introduzca su dirección de correo electrónico y acepte los Términos de servicio cuando se le solicite. A continuación se muestra el resultado de una validación exitosa:
|
1 2 3 4 5 6 7 |
Cuenta registrada. Solicitando un certificado para example_domain.com Exitosamente recibido certificado. El certificado está guardado en: /etc/letsencrypt/live/example_domain.com/fullchain.pem La clave está guardada en: /etc/letsencrypt/live/example_domain.com/privkey.pem Este certificado expira el 2022-04-29. Estos archivos se actualizarán updated cuando el certificado se renueve. |
SIGUIENTES PASOS:
El certificado deberá renovarse antes de que expire. Certbot puede renovar automáticamente el certificado en segundo plano, pero es posible que deba seguir algunos pasos para habilitar esa funcionalidad. Consulte este enlace para obtener instrucciones.
Puede ver el certificado utilizando el comando cat :
|
1 |
sudo cat /etc/letsencrypt/live/example_domain.com/fullchain.pem |
El comando anterior debería mostrar su certificado en la terminal. Una vez que haya confirmado que Certbot ha aprovisionado su certificado, ahora puede probar la configuración de Nginx que había creado en el Paso 3. Ejecute el comando Docker a continuación para iniciar el contenedor Nginx:
|
1 2 3 4 5 6 |
docker run --rm --name nginx -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
En este comando, hemos utilizado la opción -v para montar la ubicación de los directorios de certificados SSL/TLS de Let’s Encrypt.
Cuando el contenedor esté en funcionamiento, abra la página web en su navegador: http://example_domain.com. Es probable que vea una advertencia sobre que el sitio web no es seguro:
Esto se debe a que solo habíamos aprovisionado certificados de staging/prueba y no de producción de Let’s Encrypt. Obtengamos los certificados de producción ejecutando el siguiente comando de Certbot sin la opción --staging :
|
1 2 3 4 |
docker run -it --rm -p 80:80 --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ certbot/certbot certonly --standalone -d example_domain.com |
En la solicitud, confirme que desea renovar y reemplazar el certificado existente escribiendo 2 y presione ENTER. Esto debería aprovisionar un certificado listo para producción. Ahora puede ejecutar el contenedor Nginx y todo debería funcionar correctamente:
|
1 2 3 4 5 6 |
docker run --rm --name nginx -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
Once the container is up and running, open the webpage in your browser: http://example_domain.com de nuevo. Verá que su navegador es redirigido a HTTPS incluso si introduce HTTP. Esto significa que tanto nuestro servidor en la configuración de Nginx como los certificados SSL/TLS aprovisionados están funcionando correctamente. Navegue a la ruta polls route http://example_domain.com/polls ya que no tenemos una ruta definida para la ruta de inicio /. Deberías ver la interfaz de las encuestas:
Hasta ahora, has configurado con éxito una arquitectura lista para producción. Has implementado dos servidores backend que procesarán las solicitudes entrantes redirigidas desde el servidor proxy. El servidor proxy se encargará del equilibrio de carga y de asegurar el tráfico utilizando los certificados TLS provistos.
Sin embargo, debes tener en cuenta que los certificados de Let’s Encrypt caducan en 90 días. Por lo tanto, debes renovarlos antes de que se cumpla el plazo de 90 días. Dado que el contenedor de Nginx estará en ejecución, debes utilizar el modo webroot en lugar del modo standalone cuando ejecutes el comando certbot para la renovación del certificado. Recuerda que habías especificado el directorio /var/www/html/.well-known/acme-challenge/ en el archivo de configuración de Nginx en el Paso 3. Certbot utilizará esta ruta para almacenar los archivos de validación. Además, el cliente de Let’s Encrypt llamará a esta ruta con solicitudes de validación cuando intentes renovar los certificados. Una vez que el comando de renovación termine de ejecutarse, puedes recargar Nginx para aplicar los cambios.
Detén el contenedor presionando CTRL+C en tu terminal, y volvamos a ponerlo en marcha en modo detached con la bandera -d:
|
1 2 3 4 5 6 |
docker run --rm --name nginx -d -p 80:80 -p 443:443 \ -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ -v /var/www/html:/var/www/html \ nginx:1.20.2 |
Esto dejará el contenedor de Nginx ejecutándose en segundo plano. Probemos el procedimiento de renovación del certificado con la bandera --dry-run ejecutando el siguiente comando:
|
1 2 3 4 5 |
docker run -it --rm --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ -v "/var/www/html:/var/www/html" \ certbot/certbot renew --webroot -w /var/www/html --dry-run |
En este comando, hemos especificado el complemento --webroot así como la ruta a utilizar para las solicitudes de validación con la bandera -w. También especificamos la bandera --dry-run para verificar el procedimiento de renovación automática sin aprovisionar realmente un certificado.
Deberías ver una salida similar en una simulación exitosa:
|
1 2 3 4 5 6 7 8 |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Procesando /etc/letsencrypt/renewal/example_domain.com.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Simulando la renovación de un certificado existente para example_domain.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Enhorabuena, todas las renovaciones simuladas tuvieron éxito: /etc/letsencrypt/live/hackinroms.com/fullchain.pem (éxito) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
Cada vez que renueves un certificado para tu aplicación en ejecución, debes recargar Nginx para que el contenedor comience a usar el nuevo certificado. El siguiente comando de Docker recarga el contenedor nginx (recuerda que nombramos al contenedor como nginx):
|
1 |
docker kill -s HUP nginx |
El comando envía una señal Unix HUP al proceso Nginx que se ejecuta dentro del contenedor Docker nginx. Esto hace que Nginx recargue sus configuraciones y comience a usar los certificados renovados.
Dado que tenemos TLS/SSL instalado en nuestro servidor proxy y nuestro sitio web se sirve con HTTPS, ahora necesitamos asegurar nuestros servidores de aplicaciones backend para permitir únicamente solicitudes del servidor proxy.
Paso 5: Asegurar los servidores backend de Django contra el acceso externo
El servidor proxy que implementó en este tutorial maneja la terminación SSL, donde descifra la conexión SSL y reenvía paquetes no cifrados a los servidores de aplicaciones del backend. Dado que protegeremos los servidores del backend contra cualquier acceso externo, este nivel de seguridad debería funcionar para la mayoría de los casos. Sin embargo, si está implementando aplicaciones que transmiten datos confidenciales, como información bancaria o datos de salud, entonces debería implementar cifrado de extremo a extremo.
En este tutorial, los servidores Gunicorn en el backend están siendo protegidos por Nginx, ya que no están destinados a estar de cara al público. El servidor proxy Nginx es como una puerta de enlace a los servidores del backend, lo que evita que los clientes externos accedan directamente a los servidores de aplicaciones del backend. Debe asegurarse de que todas las solicitudes pasen a través del servidor proxy. Siendo ese el caso, resulta que Docker tiene un problema por el cual elude las reglas de firewall de ufw y abre puertos externamente, lo que puede dejar su infraestructura insegura. Esto es realmente evidente ya que configuramos nuestros servidores de aplicaciones en el Paso 1 y Paso 2 sin permitir el puerto 80 en las reglas de ufw. Sin embargo, aún puede acceder a las páginas web cuando visita cualquiera de las direcciones IP públicas del servidor en el navegador. Una forma de solucionar este problema es utilizando iptables directamente sin pasar por ufw. Puede leer los documentos oficiales de Docker e iptables para obtener más información. Otra forma recomendada es utilizar firewalls en la nube.
Modifiquemos las configuraciones de UFW para bloquear el acceso externo a todos los puertos que puedan haber sido abiertos por Docker. Cuando asignamos el puerto del host 80 al puerto del contenedor Docker 8000 con la bandera -p 80:8000 en el comando de Docker, también abrimos inadvertidamente el puerto 80 en la máquina host. Puede desactivar este acceso modificando la configuración de UFW como se describe en el repositorio ufw-docker README.
Cambiemos esto para el primer servidor de aplicaciones Django. Inicie sesión en el servidor y abra el archivo en /etc/ufw/after.rules con nano como usuario sudo:
|
1 |
sudo nano /etc/ufw/after.rules |
El archivo contiene las siguientes reglas de ufw:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# # rules.input-after # # Reglas que deben ejecutarse después de las reglas añadidas por la línea de comandos de ufw. Las reglas # personalizadas deben añadirse a una de estas cadenas: # ufw-after-input # ufw-after-output # ufw-after-forward # # No elimine estas líneas requeridas, de lo contrario habrá errores *filter :ufw-after-input - [0:0] :ufw-after-output - [0:0] :ufw-after-forward - [0:0] # Fin de las líneas requeridas # no registrar servicios ruidosos por defecto -A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input -A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input -A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input -A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input -A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input -A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input # no registrar transmisiones ruidosas -A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input # no elimine la línea 'COMMIT' o estas reglas no se procesarán COMMIT |
Añada el siguiente bloque de líneas de configuración de UFW en la parte inferior del archivo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] :DOCKER-USER - [0:0] -A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN -A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j RETURN COMMIT # END UFW AND DOCKER |
Las reglas que has añadido impiden el acceso público a los puertos que abre Docker. Además, permiten el acceso desde los 10.0.0.0/8, 172.16.0.0/12, y 192.168.0.0/16 rangos de IP privadas. Puedes leer más detalles sobre las reglas en el README de ufw-docker. Guarda y cierra los archivos cuando termines de editar. Esta configuración debería funcionar si hubieras configurado una red de nube privada virtual (VPC), con tus tres servidores en la VPC, y luego hubieras especificado las IP privadas de los servidores de Django en la directiva upstream del archivo de configuración de Nginx.
Sin embargo, utilizamos IP públicas y es posible que no tengamos una VPC. Por lo tanto, debes añadir una regla a ufw para permitir el tráfico desde el servidor proxy Nginx a través del puerto 80 de ambos servidores de aplicaciones Django. Puedes añadir una regla de permiso a ufw especificando la IP del servidor de origen al puerto 80 usando el siguiente comando:
|
1 |
sudo ufw allow from NGINX_PROXY_IP to any port 80 |
Una vez que hayas terminado con las modificaciones, reinicia tu servidor de aplicaciones Django para que los cambios surtan efecto, ya que ejecutar sudo ufw reload solo parece fallar al aplicar los cambios:
|
1 |
sudo reboot |
Cuando el servidor se haya reiniciado, inicia el contenedor como lo hiciste en el Paso 1 o el Paso 2:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
A continuación, intenta visitar la IP del primer servidor Django en el navegador para ver si presenta la interfaz de Polls: http://FIRST_SERVER_IP/polls. Fallará. Ahora, cierra la sesión del primer servidor y repite los pasos que has realizado aquí para el segundo servidor. Abre el archivo /etc/ufw/after.rules con nano como usuario sudo:
|
1 |
sudo nano /etc/ufw/after.rules |
Como hiciste antes, desplázate hasta el final y añade el bloque de configuraciones de UFW:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] :DOCKER-USER - [0:0] -A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN -A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j RETURN COMMIT # FIN DE UFW Y DOCKER |
Guarde y cierre el archivo una vez que haya agregado el bloque anterior.
A continuación, agregue una regla de permiso a ufw especificando la IP del servidor de origen para el puerto 80 usando el siguiente comando:
|
1 |
sudo ufw allow from NGINX_PROXY_IP to any port 80 |
Reinicie su servidor para que los cambios surtan efecto:
|
1 |
sudo reboot |
Cuando el servidor vuelva a estar encendido, inicie el contenedor nuevamente con el comando:
|
1 |
docker run -d --rm --name polls --env-file env -p 80:8000 django-polls:v1 |
Pruebe si puede ver la interfaz de encuestas yendo directamente a la dirección IP del segundo servidor: http://SECOND_SERVER_IP/polls. También debería fallar.
Esta arquitectura ya está lista para ser probada. Puede visitar https://example_domain_here/polls para ver la interfaz de encuestas predeterminada desde su navegador. Esto significa que el servidor proxy Nginx todavía tiene acceso a los servidores backend de Django.
Conclusión
En esta guía, le mostramos cómo implementar una infraestructura escalable utilizando contenedores Docker. La infraestructura incluye un servidor de base de datos PostgreSQL independiente, dos servidores de aplicaciones backend y un servidor proxy Nginx para equilibrar la carga y distribuir el tráfico entre los dos servidores. Aunque basamos nuestra aplicación en la aplicación Django Polls, puede personalizar esta arquitectura para varias aplicaciones utilizando diferentes frameworks, como Node.js, Laravel, etc.
Esta es una guía básica para comenzar. Algunas mejoras que puede agregar son alojar su imagen en un repositorio de imágenes como Docker Hub que permita una distribución sencilla de la imagen a múltiples servidores. También puede agregar canalizaciones de integración y despliegue continuos para compilar, probar y desplegar imágenes automáticamente en los servidores de aplicaciones cada vez que ocurra un evento. Por ejemplo, un evento podría ser enviar código nuevo a una rama específica en un repositorio de git. Es posible que también desee automatizar lo que sucede cuando el contenedor encuentra un error. La documentación oficial de Docker proporciona una buena guía sobre Iniciar contenedores automáticamente en caso de errores o reinicio del sistema.
¡Feliz computación!








Comentarios
Aún no hay comentarios. Sea el primero.