One place for hosting & domains

      Escalar

      Cómo escalar y proteger una aplicación de Django con Docker, Nginx y Let’s Encrypt


      Introducción

      En los entornos basados en la nube, hay diversas maneras de escalar y proteger aplicaciones de Django. Al escalar horizontalmente y ejecutar varias copias de su aplicación, puede crear un sistema más tolerante a fallos y de alta disponibilidad, a la vez que aumenta su rendimiento para que se puedan procesar solicitudes de forma simultánea. Una manera de escalar horizontalmente una aplicación de Django es proporcionar servidores de aplicaciones adicionales que ejecuten su aplicación de Django y su servidor HTTP WSGI (como Gunicorn o uWSGI). Para dirigir y distribuir las solicitudes entrantes a través de este conjunto de servidores de aplicaciones, puede usar un equilibrador de carga y un proxy inverso como Nginx. Nginx también puede almacenar en caché contenido estático y detener las conexiones de Seguridad de la capa de transporte (TLS), que se utilizan para proporcionar conexiones HTTPS y seguras a su aplicación.

      Ejecutar su aplicación de Django y el proxy Nginx dentro de contenedores de Docker garantiza que estos componentes se comporten de la misma manera independientemente del entorno en el que se implementen. Además, los contenedores proporcionan muchas características que facilitan la creación de paquetes y la configuración de su aplicación.

      En este tutorial, escalará horizontalmente una aplicación Polls de Django y Gunicorn en un contenedor proporcionando dos servidores de aplicaciones que ejecutarán una copia de un contenedor de una aplicación de Django y Gunicorn.

      También habilitará HTTPS al proporcionar y configurar un tercer servidor proxy que ejecutará un contenedor de un proxy inverso Nginx y otro de un cliente Certbot. Certbot proporcionará certificados TLS para Nginx de la entidad de certificación Let’s Encrypt. Esto garantizará que su sitio reciba una calificación de seguridad alta de SSL Labs. Este servidor proxy recibirá todas las solicitudes externas de su aplicación y se ubicará frente a los dos servidores de aplicaciones de Django que preceden en la cadena. Por último, reforzará este sistema distribuido al restringir el acceso externo solo al servidor proxy.

      Requisitos previos

      Para seguir este tutorial, necesitará lo siguiente:

      • Tres servidores con Ubuntu 18.04:

        • Dos se utilizarán como servidores de aplicaciones y se utilizarán para ejecutar su aplicación de Django y Gunicorn.
        • Uno se usará como servidor proxy y se utilizará para ejecutar Nginx y Certbot.
        • Todos los usuarios deben tener un usuario no root con privilegios sudo y firewall activo. Para obtener información sobre cómo configurarlos, consulte esta guía de configuración inicial para servidores.
      • Docker instalado en los tres servidores. Para obtener orientación sobre la instalación de Docker, siga los pasos 1 y 2 de Cómo instalar y usar Docker en Ubuntu 18.04.

      • Un nombre de dominio registrado. En este tutorial, utilizaremos your_domain en todo momento. Puede obtener un ejemplar gratis en Freenom o utilizar el registrador de dominios que desee.

      • Un registro DNS A con your_domain.com orientado a la dirección IP pública de su servidor proxy. Puede seguir esta introducción al DNS de DigitalOcean para obtener información sobre cómo agregarlo a una cuenta de DigitalOcean, si usa una:

      • Un depósito de almacenamiento de objetos S3, como un Space de DigitalOcean, para almacenar los archivos estáticos de su proyecto de Django y un conjunto de claves de acceso para ese espacio. Para obtener información sobre cómo crear Spaces, consulte la documentación de Cómo crear Spaces. Para obtener información sobre cómo crear claves de acceso para Spaces, consulte Compartir el acceso a Spaces con claves de acceso. Con cambios menores, puede usar cualquier servicio de almacenamiento de objetos que admita el complemento django-storages

      • Una instancia de un servidor de PostgreSQL, una base de datos y un usuario para su aplicación de Django. Con cambios menores, puede usar cualquier base de datos que admita Django.

      Paso 1: Configurar el primer servidor de aplicaciones de Django

      Para comenzar, vamos a clonar el repositorio de aplicaciones de Django en el primer servidor de aplicaciones. A continuación, configuraremos y compilaremos la imagen de Docker de la aplicación y probaremos la aplicación ejecutando el contenedor de Django.

      Nota: Si continúa desde Cómo crear una aplicación de Django y Gunicorn con Docker, ya habrá completado el Paso 1, por lo que puede pasar directamente al Paso 2 para configurar el segundo servidor de aplicaciones.

      Comience iniciando sesión en el primero de los dos servidores de aplicaciones de Django. Utilice git para clonar la rama polls-docker del repositorio de GitHub de la aplicación Polls del tutorial de Django. Este repositorio contiene código para la aplicación Polls de muestra de la documentación de Django. La rama polls-docker contiene una versión con Docker de la aplicación Polls. Para obtener información sobre cómo se modificó la aplicación Polls para que funcione de forma eficaz en un entorno con contenedor, consulte Cómo crear una aplicación de Django y Gunicorn con Docker.

      • git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

      Diríjase al directorio django-polls:

      cd django-polls
      

      Este directorio contiene el código de Python de la aplicación de Django, un Dockerfile que Docker utilizará para crear la imagen del contenedor, y un archivo env que contiene una lista de las variables de entorno que se van a pasar al entorno en ejecución del contenedor. Inspeccione el Dockerfile con cat:

      cat Dockerfile
      

      Output

      FROM python:3.7.4-alpine3.10 ADD django-polls/requirements.txt /app/requirements.txt RUN set -ex && apk add --no-cache --virtual .build-deps postgresql-dev build-base && python -m venv /env && /env/bin/pip install --upgrade pip && /env/bin/pip install --no-cache-dir -r /app/requirements.txt && runDeps="$(scanelf --needed --nobanner --recursive /env | awk '{ gsub(/,/, "nso:", $2); print "so:" $2 }' | sort -u | xargs -r apk info --installed | sort -u)" && apk add --virtual rundeps $runDeps && apk del .build-deps ADD django-polls /app WORKDIR /app ENV VIRTUAL_ENV /env ENV PATH /env/bin:$PATH EXPOSE 8000 CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi"]

      Este Dockerfile utiliza la imagen de Docker oficial de Python 3.7.4 como base e instala los requisitos del paquete de Python de Django y Gunicorn, tal como se define en el archivo django-polls/requirements.txt. A continuación, elimina algunos archivos de compilación innecesarios, copia el código de la aplicación en la imagen y establece el PATH de ejecución. Por último, declara que el puerto 8000 se utilizará para aceptar conexiones de contenedores entrantes y ejecuta gunicorn con 3 trabajadores, escuchando en el puerto 8000.

      Para obtener más información sobre cada uno de los pasos de este Dockerfile, consulte el Paso 6 de Cómo crear una aplicación de Django y Gunicorn con Docker.

      Ahora, compile la imagen con docker build:

      Nombramos la imagen polls con el indicador -t y pasamos el directorio actual como contexto de compilación, el conjunto de archivos a los que se debe hacer referencia al construir la imagen.

      Una vez que Docker haya compilado y etiquetado la imagen, enumere las imágenes disponibles utilizando docker images:

      docker images
      

      Debería ver la imagen polls enumerada:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE polls latest 80ec4f33aae1 2 weeks ago 197MB python 3.7.4-alpine3.10 f309434dea3a 8 months ago 98.7MB

      Antes de ejecutar el contenedor de Django, debemos configurar su entorno de ejecución utilizando el archivo env presente en el directorio actual. Este archivo se pasará al comando docker run que se utiliza para ejecutar el contenedor y Docker insertará las variables de entorno configuradas en el entorno en ejecución del contenedor.

      Abra el archivo env con nano o su editor favorito:

      nano env
      

      Configuraremos el archivo de esta manera y deberá agregar algunos valores adicionales como se indica a continuación.

      django-polls/env

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Complete los valores que faltan para las siguientes claves:

      • DJANGO_SECRET_KEY: establézcala en un valor único e impredecible, como se detalla en la documentación de Django. Se proporciona un método para generar esta clave en la sección Ajustar la configuración de la aplicación del tutorial Aplicaciones escalables de Django.
      • DJANGO_ALLOWED_HOSTS: esta variable asegura la aplicación y evita ataques a través del encabezado de host HTTP. Para propósitos de prueba, establézcala en *, un comodín que coincidirá con todos los hosts. En producción, debe establecerlo en your_domain.com. Para obtener más información sobre esta configuración de Django, consulte la sección Configuración principal en la documentación de Django.
      • DATABASE_USERNAME: establézcalo en el usuario de la base de datos de PostgreSQL creado en los pasos de requisitos previos.
      • DATABASE_NAME: establézcalo en polls o el nombre de la base de datos de PostgreSQL creado en los pasos de requisitos previos.
      • DATABASE_PASSWORD: establézcala en la contraseña de la base de datos de PostgreSQL creada en los pasos de requisitos previos.
      • DATABASE_HOST: establézcalo en el nombre de host de su base de datos.
      • DATABASE_PORT: Set establézcalo en el puerto de su base de datos.
      • STATIC_ACCESS_KEY_ID: establézcala en la clave de acceso de su cubo S3 o su Space.
      • STATIC_SECRET_KEY: establézcala en el secreto de la clave de acceso de su cubo S3 o su Space.
      • STATIC_BUCKET_NAME: establézcalo en el nombre de su cubo S3 o su Space.
      • STATIC_ENDPOINT_URL: establézcala en la URL de extremo correspondiente de su cubo S3 o su Space, por ejemplo, https://space-name.nyc3.digitaloceanspaces.com, si su Space está ubicado en la región nyc3.

      Una vez que haya finalizado la edición, guarde y cierre el archivo.

      Ahora, utilizaremos docker run para anular el CMD establecido en Dockerfile y crear el esquema de la base de datos utilizando los comandos manage.py makemigrations y manage.py migrate:

      docker run --env-file env polls sh -c "python manage.py makemigrations && python manage.py migrate"
      

      Ejecutamos la imagen del contenedor polls:latest, pasamos el archivo de variables de entorno que acabamos de modificar y anulamos el comando de Dockerfile con sh -c "python manage.py makemigrations && python manage.py migrate", lo que creará el esquema de la base de datos definido mediante el código de la aplicación. Si lo ejecuta por primera vez, debería ver lo siguiente:

      Output

      No changes detected Operations to perform: Apply all migrations: admin, auth, contenttypes, polls, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying polls.0001_initial... OK Applying sessions.0001_initial... OK

      Esto indica que el esquema de la base de datos se ha creado correctamente.

      Si no está ejecutando migrate por primera vez, Django realizará un no-op a menos que el esquema de la base de datos haya cambiado.

      A continuación, ejecutaremos otra instancia del contenedor de la aplicación y utilizaremos una shell interactiva en su interior para crear un usuario administrativo para el proyecto de Django.

      docker run -i -t --env-file env polls sh
      

      Esto le proporcionará una línea de comandos de shell dentro del contenedor en ejecución que puede usar para crear el usuario de Django:

      python manage.py createsuperuser
      

      Ingrese un nombre de usuario, una dirección de correo electrónico y una contraseña para su usuario y, una vez que haya creado el usuario, presione CTRL+D para salir del contenedor y cerrarlo.

      Por último, generaremos los archivos estáticos de la aplicación y los subiremos al Space de DigitalOcean utilizando collectstatic. Tenga en cuenta que esta operación puede tardar un poco en completarse.

      docker run --env-file env polls sh -c "python manage.py collectstatic --noinput"
      

      Una vez que estos archivos se hayan generado y cargado, obtendrá el siguiente resultado.

      Output

      121 static files copied.

      Ahora, podemos ejecutar la aplicación:

      docker run --env-file env -p 80:8000 polls
      

      Output

      [2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0 [2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) [2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync [2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7 [2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8 [2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9

      Aquí, ejecutamos el comando predeterminado definido en el Dockerfile, gunicorn --bind :8000 --workers 3 mysite.wsgi:application y exponemos el puerto del contenedor 8000 para que el puerto 80 del servidor de Ubuntu se asigne al puerto 8000 del contenedor polls.

      Ahora, debería poder navegar a la aplicación polls desde su navegador web al escribir http://APP_SERVER_1_IP en la barra de direcciones URL. Dado que no hay una ruta definida para la ruta / , es probable que reciba un error de 404 Page Not Found (Página no encontrada), lo que es de esperar.

      Advertencia: Al usar el firewall UFW con Docker, Docker omite cualquier regla de firewall configurada, tal como se documenta en este número de GitHub. Esto explica por qué tiene acceso al puerto 80 de su servidor, a pesar de no haber creado explícitamente una regla de acceso de UFW en ningún paso de los requisitos previos. Abordaremos este problema de seguridad en el Paso 5, al corregir la configuración de UFW. Si no usa UFW y está utilizando firewalls de DigitalOcean para la nube, puede ignorar de forma segura esta advertencia.

      Diríjase a http://APP_SERVER_1_IP/polls para ver la interfaz de la aplicación Polls:

      Interfaz de la aplicación Polls

      Para ver la interfaz administrativa, visite http://APP_SERVER_1_IP/admin. Debería ver la ventana de autenticación de administración de la aplicación Polls:

      Página de autenticación de administración de Polls

      Ingrese el nombre de usuario administrativo y la contraseña que creó con el comando createsuperuser.

      Después de la autenticación, podrá acceder a la interfaz administrativa de la aplicación Polls:

      Interfaz principal de administración de Polls

      Tenga en cuenta que los que los recursos estáticos de las aplicaciones admin y polls se entregan directamente desde el almacenamiento de objetos. Para confirmar esto, consulte Prueba de la entrega de archivos estáticos de Spaces.

      Cuando haya terminado de explorar, presione CTRL+C en la ventana de terminal que está ejecutando el contenedor de Docker para cerrar el contenedor.

      Ahora que ha confirmado que el contenedor de la aplicación se ejecuta de la manera prevista, puede ejecutarlo en modo separado, lo que lo ejecutará en segundo plano y le permitirá salir de su sesión SSH:

      docker run -d --rm --name polls --env-file env -p 80:8000 polls
      

      El indicador -d le indica a Docker que ejecute el contenedor en modo separado y el indicador -rm limpia el sistema de archivos del contenedor una vez que se sale de él. Denominamos polls al contenedor.

      Desconéctese del primer servidor de aplicaciones de Django y diríjase a http://APP_SERVER_1_IP/polls para confirmar que el contenedor se está ejecutando de la manera prevista.

      Ahora que su primer servidor de aplicaciones de Django está en ejecución, puede configurar el segundo.

      Paso 2: Configurar el segundo servidor de aplicaciones de Django

      Como muchos de los comandos que se utilizan para configurar este servidor serán los mismos que los que utilizamos en el paso anterior, se presentarán aquí de forma abreviada. Revise el Paso 1 para obtener más información sobre los comandos que se utilizan en este paso.

      Comience por iniciar sesión en el segundo servidor de aplicaciones de Django.

      Clone la rama polls-docker del repositorio de GitHub django-polls:

      • git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

      Diríjase al directorio django-polls:

      cd django-polls
      

      Compile la imagen con docker build:

      Abra el archivo env con nano o su editor favorito:

      nano env
      

      django-polls/env

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Complete los valores que faltan como se indica en el Paso 1. Cuando haya terminado de editar, guarde y cierre el archivo.

      Por último, ejecute el contenedor de la aplicación en modo separado:

      docker run -d --rm --name polls --env-file env -p 80:8000 polls
      

      Diríjase a http://APP_SERVER_2_IP/polls para confirmar que el contenedor se está ejecutando de la manera prevista. Puede iniciar sesión de forma segura en el segundo servidor de aplicaciones sin cerrar el contenedor en ejecución.

      Con los dos contenedores de aplicaciones de Django en ejecución, puede configurar el contenedor del proxy inverso Nginx.

      Paso 3: Configurar el contenedor de Docker de Nginx

      Nginx es un servidor web versátil que ofrece varias características, como proxy inverso, equilibrio de carga y almacenamiento en caché. En este tutorial, hemos descargado los recursos estáticos de Django al almacenamiento de objetos, por lo que no utilizaremos las capacidades de almacenamiento en caché de Nginx. Sin embargo, utilizaremos Nginx como proxy inverso para nuestros dos servidores de aplicaciones de Django de backend y distribuiremos las solicitudes entrantes entre ellos. Además, Nginx realizará la terminación de TLS y el redireccionamiento utilizando un certificado TLS proporcionado por Certbot. Esto significa que obligará a los clientes a usar HTTPS, redireccionando las solicitudes HTTP entrantes al puerto 443. Luego, descifrará las solicitudes HTTPS y las redirigirá, a través del proxy, a los servidores de Django que preceden en la cadena.

      En este tutorial, hemos tomado la decisión de desacoplar los contenedores de Nginx de los servidores de backend. Dependiendo de su caso de uso, puede optar por ejecutar el contenedor de Nginx en cualquiera de los dos servidores de aplicaciones de Django, redirigiendo las solicitudes, a través del proxy, de forma local. Otra posible arquitectura sería ejecutar dos contenedores de Nginx, uno en cada servidor de backend, con un equilibrador de carga en la nube al frente. Cada arquitectura presenta diferentes ventajas de seguridad y desempeño; debe realizar una prueba de carga de su sistema para descubrir los posibles cuellos de botella. La arquitectura flexible que se describe en este tutorial le permite escalar tanto la capa de la aplicación de Django de backend como la capa del proxy de Nginx. Cuando el contenedor único de Nginx se convierta en un cuello de botella, puede escalar varios proxy de Nginx y agregar un equilibrador de carga en la nube o uno L4 rápido, como HAProxy.

      Con los dos servidores de aplicaciones de Django en ejecución, podemos comenzar a configurar el servidor proxy de Nginx. Inicie sesión en su servidor proxy y cree un directorio llamado conf:

      mkdir conf
      

      Cree un archivo de configuración denominado nginx.conf con nano o su editor favorito:

      nano conf/nginx.conf
      

      Pegue la siguiente configuración de Nginx:

      conf/nginx.conf

      
      upstream django {
          server APP_SERVER_1_IP;
          server APP_SERVER_2_IP;
      }
      
      server {
          listen 80 default_server;
          return 444;
      }
      
      server {
          listen 80;
          listen [::]:80;
          server_name your_domain.com;
          return 301 https://$server_name$request_uri;
      }
      
      server {
          listen 443 ssl http2;
          listen [::]:443 ssl http2;
          server_name your_domain.com;
      
          # SSL
          ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/your_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;
          }
      
      }
      

      Los bloques upstream, server y location configuran Nginx para que redirija las solicitudes HTTP a HTTPS y equilibre su carga entre los dos servidores de aplicaciones de Django configurados en los pasos 1 y 2. Para obtener más información sobre la estructura de los archivos de configuración de Nginx, consulte el artículo Información sobre la estructura de los archivos y los contextos de configuración de Nginx. El artículo Información sobre algoritmos de selección de bloques de servidores y ubicación de Nginx también puede resultarle útil.

      Esta configuración se realizó a partir de archivos de configuración de muestra proporcionados por Gunicorn, Cerbot y Nginx y representa una configuración mínima de Nginx para poner en marcha esta arquitectura. Los ajustes de esta configuración de Nginx están fuera del alcance de este artículo, pero puede usar una herramienta como NGINXConfig para generar archivos de configuración de Nginx seguros y de buen rendimiento para su arquitectura.

      El bloque upstream define el grupo de servidores que se utiliza para redirigir las solicitudes mediante el proxy utilizando la directiva proxy_pass:

      conf/nginx.conf

      upstream django {
          server APP_SERVER_1_IP;
          server APP_SERVER_2_IP;
      }
      . . .
      

      En este bloque, lo denominamos django e incluimos las direcciones IP de los dos servidores de aplicaciones de Django. Si los servidores de aplicaciones se ejecutan en DigitalOcean y tienen habilitadas redes VPC, debe usar sus direcciones IP privadas aquí. Para obtener información sobre cómo habilitar las redes VPC en DigitalOcean, consulte Cómo habilitar redes VPC en Droplets existentes.

      El primer bloque server captura las solicitudes que no coinciden con su dominio y termina la conexión. Por ejemplo, este bloque manejaría una solicitud HTTP directa a la dirección IP de su servidor:

      conf/nginx.conf

      . . .
      server {
          listen 80 default_server;
          return 444;
      }
      . . .
      

      El siguiente bloque server redirige las solicitudes HTTP a su dominio a HTTPS utilizando un redireccionamiento HTTP 301. Luego, el bloque server final se encarga de manejar estas solicitudes:

      conf/nginx.conf

      . . .
      server {
          listen 80;
          listen [::]:80;
          server_name your_domain.com;
          return 301 https://$server_name$request_uri;
      }
      . . .
      

      Estas dos directivas definen las rutas al certificado TLS y la clave secreta. Se proporcionarán utilizando Certbot y se instalarán en el contenedor de Nginx en el siguiente paso.

      conf/nginx.conf

      . . .
      ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
      . . .
      

      Estos parámetros son los valores predeterminados de seguridad SSL recomendados por Certbot. Para obtener más información sobre ellos, consulte el Módulo ngx_http_ssl_module en la documentación de Nginx. La guía Seguridad/TLS del lado del servidor de Mozilla es otro recurso útil que puede usar para ajustar su configuración de SSL.

      conf/nginx.conf

      . . .
          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";
      . . .
      

      Estas dos directivas de la configuración de muestra de Nginx de Gunicorn establecen el tamaño máximo permitido del cuerpo de la solicitud del cliente y asignan el tiempo de espera para las conexiones persistentes con el cliente. Nginx cerrará las conexiones con el cliente una vez transcurridos los segundos de keepalive_timeout.

      conf/nginx.conf

      . . .
      client_max_body_size 4G;
      keepalive_timeout 5;
      . . .
      

      El primer bloque location le indica a Nginx que redirija, a través del proxy, las solicitudes a los servidores upstream django mediante HTTP. También preserva los encabezados HTTP del cliente que capturan la dirección IP de origen, el protocolo utilizado para la conexión y el host de destino:

      conf/nginx.conf

      . . .
      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;
      }
      . . .
      

      Para obtener más información sobre estas directivas, consulte Implementación de Gunicorn y Module ngx_http_proxy_module en la documentación de Nginx.

      El bloque location final captura las solicitudes a la ruta /well-known/acme-challenge/ que utiliza Certbot en los desafíos de HTTP-01 para verificar su dominio con Let’s Encrypt y suministrar o renovar los certificados TLS.  Para obtener más información sobre el desafío HTTP-01 que utiliza Certbot, consulte Tipos de desafíos en la documentación de Let’s Encrypt.

      conf/nginx.conf

      . . .
      location ^~ /.well-known/acme-challenge/ {
              root /var/www/html;
      }
      

      Una vez que haya finalizado la edición, guarde y cierre el archivo.

      Ahora, puede usar este archivo de configuración para ejecutar un contenedor de Docker de Nginx. En este tutorial, utilizaremos la imagen nginx:1.19.0, versión 1.19.0, de la imagen de Docker oficial que mantiene Nginx.

      Cuando ejecutemos el contenedor por primera vez, Nginx arrojará un error y fallará, dado que aún no hemos proporcionado los certificados definidos en el archivo de configuración. De todos modos, ejecutaremos el comando para descargar la imagen de Nginx de forma local y probar que todo lo demás funcione correctamente:

      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.19.0
      

      Aquí, denominamos nginx al contenedor y asignamos los puertos del host 80 y 443 a los puertos de los contenedores respectivos. El indicador -v ubica el archivo config en el contenedor de Nginx en /etc/nginx/conf.d/nginx.conf, que la imagen de Nginx está preconfigurada para cargar. Se coloca en modo ro o de “solo lectura”, por lo que el contenedor no puede modificar el archivo. El directorio web root /var/www/html también se instala en el contenedor. Por último, nginx:1.19.0 le indica a Docker que extraiga y ejecute la imagen nginx:1.19.0 de Dockerhub.

      Docker extraerá y ejecutará la imagen y, luego, Nginx arrojará un error cuando no encuentre el certificado TLS configurado y la clave secreta. Los suministraremos en el siguiente paso utilizando un cliente de Cerbot con Docker y la entidad de certificación Let’s Encrypt.

      Paso 4: Configurar la renovación de certificados de Let’s Encrypt y Certbot

      Certbot es un cliente Let’s Encrypt desarrollado por Electronic Frontier Foundation. Proporciona certificados TLS gratuitos de la entidad de certificación Let’s Encrypt que permiten a los navegadores verificar la identidad de sus servidores web. Como tenemos Docker instalado en nuestro servidor proxy de Nginx, utilizaremos la imagen de Docker de Certbot para suministrar y renovar los certificados TLS.

      Comience por asegurarse de tener un registro DNS A asignado a la dirección IP pública del servidor proxy. A continuación, en su servidor proxy, proporcione una versión provisional de los certificados utilizando la imagen de Docker certbot:

      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 your_domain.com
      

      Este comando ejecuta la imagen de Docker certbot en modo interactivo y reenvía el puerto 80 del host al puerto 80 del contenedor. Crea y monta dos directorios de host en el contenedor: /etc/letsencrypt/ y /var/lib/letsencrypt/. certbot se ejecuta en modo standalone, sin Nginx, y utilizará los servidores staging de Let’s Encrypt para realizar la validación del dominio.

      Cuando se le solicite, ingrese su dirección de correo electrónico y acepte las Condiciones del servicio. Si la validación del dominio es correcta, debería ver el siguiente resultado:

      Output

      Obtaining a new certificate Performing the following challenges: http-01 challenge for stubb.dev Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/your_domain.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/your_domain.com/privkey.pem Your cert will expire on 2020-09-15. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal.

      Puede inspeccionar el certificado utilizando cat:

      sudo cat /etc/letsencrypt/live/your_domain.com/fullchain.pem
      

      Con el certificado TLS suministrado, podemos probar la configuración de Nginx que establecimos en el paso anterior:

      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.19.0
      

      Este es el mismo comando que ejecutamos en el Paso 3, con la adición de los dos directorios de Let’s Encrypt que acabamos de crear.

      Una vez que Nginx esté en ejecución, diríjase a http://your_domain.com. Puede recibir una advertencia en su navegador indicando que la entidad de certificación no es válida. Esto es de esperar dado que suministramos certificados provisionales, no certificados de producción de Let’s Encrypt. Compruebe la barra de direcciones URL de su navegador para confirmar que su solicitud HTTP se haya redireccionado a HTTPS.

      Presione CTRL+C en su terminal para salir de Nginx y volver a ejecutar el cliente certbot, omitiendo el indicador --staging:

      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 your_domain.com
      

      Cuando se le solicite mantener el certificado existente o renovarlo y sustituirlo, presione 2 para renovarlo y, luego, presione ENTER para confirmar su elección.

      Con el certificado TLS de producción suministrado, vuelva a ejecutar el servidor Nginx:

      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.19.0
      

      En su navegador, diríjase a http://your_domain.com. En la barra de direcciones URL, confirme que la solicitud HTTP se haya redireccionado a HTTPS. Dado que la aplicación Polls no tiene una ruta predeterminada configurada, debería ver un error de Django de *Página no encontrada *. Diríjase a https://your_domain.com/polls para ver la interfaz estándar de la aplicación Polls:

      Interfaz de la aplicación Polls

      En este punto, ha suministrado un certificado TLS de producción utilizando el cliente de Docker Certbot y está redirigiendo las solicitudes externas a través del proxy inverso y equilibrando la carga entre los dos servidores de aplicaciones de Django.

      Los certificados de Let’s Encrypt caducan cada 90 días. Para asegurarse de que su certificado permanezca válido, debe renovarlo regularmente antes de su vencimiento programado. Con Nginx en ejecución, debe usar el cliente de Certbot en modo webroot en vez de standalone. Esto significa que Certbot realizará la validación creando un archivo en el directorio /var/www/html/.well-known/acme-challenge/ y la regla location definida en la configuración de Nginx realizada en el Paso 3 capturará las solicitudes de validación de Let’s Encrypt a esta ruta. A continuación, Certbot rotará los certificados, y usted podrá volver a cargar Nginx para que utilice este certificado recién suministrado.

      Hay varias formas de automatizar este procedimiento, pero la renovación automática de certificados TLS está fuera del alcance de este tutorial. Para obtener un proceso similar usando la utilidad de programación cron, consulte el paso 6 de Cómo proteger una aplicación de Node.js en un contenedor con Nginx, Let’s Encrypt y Docker Compose.

      En su terminal, presione CTRL+C para cerrar el contenedor de Nginx. Vuelva a ejecutarlo en modo separado al agregar el indicador -d:

      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.19.0
      

      Con Nginx ejecutándose en segundo plano, utilice el siguiente comando para realizar una ejecución de prueba del procedimiento de renovación de certificados:

      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
      

      Utilizamos el complemento --webroot, especificamos la ruta web root y utilizamos el indicador --dry-run para verificar que todo funciona correctamente sin realizar la renovación de certificados real.

      Si la simulación de la renovación se realiza correctamente, debería ver el siguiente resultado:

      Output

      Cert not due for renewal, but simulating renewal for dry run Plugins selected: Authenticator webroot, Installer None Renewing an existing certificate Performing the following challenges: http-01 challenge for your_domain.com Using the webroot path /var/www/html for all unmatched domains. Waiting for verification... Cleaning up challenges - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - new certificate deployed without reload, fullchain is /etc/letsencrypt/live/your_domain.com/fullchain.pem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates below have not been saved.) Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/your_domain.com/fullchain.pem (success) ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates above have not been saved.) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      En un entorno de producción, después de renovar los certificados, debe volver a cargar Nginx para que los cambios surtan efecto. Para volver a cargar Nginx, ejecute el siguiente comando:

      docker kill -s HUP nginx
      

      Este comando enviará una señal HUP Unix al proceso de Nginx que se está ejecutando en el contenedor de Docker nginx. Al recibir esta señal, Nginx volverá a cargar su configuración y sus certificados renovados.

      Con HTTPS habilitado y todos los componentes de esta arquitectura en ejecución, el paso final es bloquear la configuración al evitar el acceso externo a los dos servidores de aplicaciones backend; todas las solicitudes HTTP deben pasar por el proxy de Nginx.

      Paso 5: Prevención de acceso externo a servidores de aplicaciones de Django

      En la arquitectura que se describe en este tutorial, la terminación de SSL se produce en el proxy de Nginx. Esto significa que Nginx descifra la conexión SSL y los paquetes se redirigen, a través del proxy, a los servidores de aplicaciones de Django no cifrados. Para muchos casos de uso, este nivel de seguridad es suficiente. Para las aplicaciones que incluyen datos financieros o de salud, es conveniente implementar cifrado de extremo a extremo. Puede hacerlo al reenviar paquetes cifrados a través del equilibrador de carga y descifrarlos en los servidores de aplicaciones o volver a cifrarlos en el proxy y descifrarlos nuevamente en los servidores de aplicaciones de Django. Estas técnicas están fuera del alcance de este artículo, pero puede consultar el documento Cifrado de extremo a extremo para obtener más información.

      El proxy de Nginx actúa como una puerta de enlace entre el tráfico externo y la red interna. En teoría, ningún cliente externo debería tener acceso directo a los servidores de aplicaciones internos y todas las solicitudes deberían pasar a través del servidor de Nginx. La nota del Paso 1 describe brevemente un problema abierto con Docker en el que Docker omite la configuración de firewall ufw de manera predeterminada y abre los puertos de forma externa, lo que puede ser peligroso. Para solucionar este problema de seguridad, se recomienda usar firewalls para la nube al trabajar con servidores con Docker. Para obtener más información sobre la creación de firewalls para la nube con DigitalOcean, consulte Cómo crear firewalls. También puede manipular iptables directamente en lugar de usar ufw. Para obtener más información sobre el uso de iptables con Docker, consulte Docker e iptables.

      En este paso, modificaremos la configuración de UFW para bloquear el acceso externo a los puertos del host que abre Docker. Al ejecutar Django en los servidores de aplicaciones, pasamos el indicador -p 80:8000 a docker, que reenvía el puerto 80 del host al puerto 8000 del contenedor. Esto también abrió el puerto 80 a clientes externos, lo que puede verificar al dirigirse a http://your_app_server_1_IP. Para evitar el acceso directo, modificaremos la configuración de UFW utilizando el método que se describe en el repositorio de GitHub ufw-docker.

      Comience por iniciar sesión en el primer servidor de aplicaciones de Django. A continuación, abra el archivo /etc/ufw/after.rules con privilegios de superusuario, utilizando nano o su editor favorito:

      sudo nano /etc/ufw/after.rules
      

      Cuando se le solicite, ingrese su contraseña y, luego, presione ENTER para confirmar.

      Debería ver las siguientes reglas ufw:

      /etc/ufw/after.rules

      #
      # rules.input-after
      #
      # Rules that should be run after the ufw command line added rules. Custom
      # rules should be added to one of these chains:
      #   ufw-after-input
      #   ufw-after-output
      #   ufw-after-forward
      #
      
      # Don't delete these required lines, otherwise there will be errors
      *filter
      :ufw-after-input - [0:0]
      :ufw-after-output - [0:0]
      :ufw-after-forward - [0:0]
      # End required lines
      
      # don't log noisy services by default
      -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
      
      # don't log noisy broadcast
      -A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input
      
      # don't delete the 'COMMIT' line or these rules won't be processed
      COMMIT
      

      Pegue el siguiente bloque de reglas de configuración de UFW en la parte inferior:

      /etc/ufw/after.rules

      . . .
      
      # 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
      

      Estas reglas restringen el acceso público a los puertos que abre Docker y permiten el acceso a los intervalos de IP privadas 10.0.0.0/8, 172.16.0.0/12 y 192.168.0.0/16. Si usa VPC con DigitalOcean, entonces, las Droplets de su red VPC tendrán acceso al puerto abierto a través de la interfaz de red privada, pero los clientes externos no lo tendrán. Para obtener más información sobre las VPC, consulte la documentación oficial de las VPC. Para obtener más información sobre las reglas implementadas en este fragmento de código, consulte ¿Cómo funciona? en el archivo README de ufw-docker.

      Si no está utilizando VPC con DigitalOcean y ha introducido las direcciones IP públicas de los servidores de aplicaciones en el bloque upstream de su configuración de Nginx, deberá modificar explícitamente el firewall UFW para que permita tráfico del servidor de Nginx a través del puerto 80 de los servidores de aplicaciones de Django. Para obtener información sobre la creación de reglas allow con el firewall de UFW, consulte Aspectos básicos de UFW: reglas y comandos comunes de firewall.

      Una vez que haya finalizado la edición, guarde y cierre el archivo.

      Reinicie ufw para que tome la nueva configuración:

      sudo systemctl restart ufw
      

      Diríjase a http://APP_SERVER_1_IP en su navegador web para confirmar que ya no pueda acceder al servidor de aplicaciones a través del puerto 80.

      Repita este proceso en el segundo servidor de aplicaciones de Django.

      Cierre sesión en el primer servidor de aplicaciones o abra otra ventana de terminal, e inicie sesión en el segundo servidor de aplicaciones de Django. A continuación, abra el archivo /etc/ufw/after.rules con privilegios de superusuario, utilizando nano o su editor favorito:

      sudo nano /etc/ufw/after.rules
      

      Cuando se le solicite, ingrese su contraseña y, luego, presione ENTER para confirmar.

      Pegue el siguiente bloque de reglas de configuración de UFW en la parte inferior:

      /etc/ufw/after.rules

      . . .
      
      # 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
      

      Una vez que haya finalizado la edición, guarde y cierre el archivo.

      Reinicie ufw para que tome la nueva configuración:

      sudo systemctl restart ufw
      

      Diríjase a http://APP_SERVER_2_IP en su navegador web para confirmar que ya no pueda acceder al servidor de aplicaciones a través del puerto 80.

      Por último, diríjase a https://your_domain_here/polls para confirmar que el proxy de Nginx siga teniendo acceso a los servidores de Django que preceden en la cadena. Debería ver la interfaz predeterminada de la aplicación de Polls.

      Conclusión

      En este tutorial, configuró una aplicación Polls de Django escalable utilizando contenedores de Docker. A medida que su tráfico y la carga en el sistema aumenten, puede escalar cada capa de forma separada: la capa de redireccionamiento mediante proxy de Nginx, la capa de aplicaciones de backend de Django y la capa de la base de datos de PostgreSQL.

      Crear un sistema distribuido suele implicar tener que tomar varias decisiones de diseño, y encontrará varias arquitecturas disponibles para satisfacer su caso de uso. La arquitectura que se describe en este tutorial se presenta como un plan flexible para diseñar aplicaciones escalables con Django y Docker.

      Probablemente desee controlar el comportamiento de sus contenedores cuando detecten errores o ejecutar contenedores de forma automática al iniciar su sistema. Para hacerlo, puede usar un administrador de procesos como Systemd o implementar directivas de reinicio. Para obtener más información al respecto, consulte Iniciar contenedores de forma automática en la documentación de Docker.

      Al trabajar a gran escala con varios hosts ejecutando la misma imagen de Docker, puede resultar más eficaz automatizar los pasos utilizando una herramienta de administración de configuración como Ansible o Chef. Para obtener más información sobre la administración de configuración, consulte Introducción a la administración de configuración y Configuración de la automatización con Ansible: Un kit del taller de DigitalOcean.

      En lugar de compilar la misma imagen en cada host, también puede simplificar la implementación utilizando un registro de imágenes como Docker Hub, que compila, almacena y distribuye imágenes de Docker en varios servidores. Además de un registro de imágenes, una canalización de integración e implementación continua puede ayudarlo a compilar, probar e implementar imágenes en sus servidores de aplicaciones. Para obtener más información sobre CI/CD, consulte Introducción a las prácticas recomendadas de CI/CD.



      Source link

      Como escalar e proteger um aplicativo Django com o Docker, Nginx e Let’s Encrypt


      Introdução

      Em ambientes baseados em nuvem, existem várias maneiras de dimensionar e proteger um aplicativo Django. Ao escalar horizontalmente e executar várias cópias de seu aplicativo, você pode construir um sistema mais tolerante e altamente disponível, ao mesmo tempo em que também aumenta seu rendimento de modo que as solicitações possam ser processadas simultaneamente. Uma maneira de escalar horizontalmente um aplicativo Django é fornecendo servidores de aplicativos adicionais que executem seu aplicativo Django e seu servidor HTTP WSGI (como o Gunicorn ou o uWSGI). Para encaminhar e distribuir pedidos recebidos neste conjunto de servidores de aplicativos, você pode usar um balanceador de carga e um proxy reverso como o Nginx. O Nginx também é capaz de colocar em cache conteúdo estático e encerrar as conexões via protocolo TLS, usadas para providenciar o HTTPS e conexões seguras ao seu aplicativo.

      Executar seu aplicativo Django e o proxy Nginx dentro dos contêineres Docker garante que esses componentes se comportem da mesma maneira, independentemente do ambiente em que estão implantados. Além disso, os contêineres proporcionam muitos recursos que facilitam o empacotamento e a configuração do seu aplicativo.

      Neste tutorial, você irá escalar horizontalmente um Django e um aplicativo Gunicorn Polls em contêiner fornecendo dois servidores de aplicativos que irão cada um executar uma cópia de um contêiner de aplicativos Django e Gunicorn.

      Você também habilitará o HTTPS fornecendo e configurando um terceiro servidor proxy que irá executar um contêiner de proxy reverso Nginx e um contêiner do cliente Certbot. O Certbot irá fornecer certificados TLS para o Nginx a partir da autoridade de certificação Let’s Encrypt. Isso irá garantir que seu site receba uma alta classificação de segurança do SSL Labs. Este servidor proxy receberá todos os pedidos externos do seu aplicativo e se colocará em frente aos dois servidores upstream do aplicativo Django. Por fim, você irá fortalecer esse sistema distribuído restringindo o acesso externo apenas ao servidor de proxy.

      Pré-requisitos

      Para seguir este tutorial, será necessário:

      • Três servidores Ubuntu 18.04:

        • Dois serão os servidores de aplicativo, usados para executar os aplicativos Django e Gunicorn.
        • Um servidor será um servidor proxy, usado para executar o Nginx e o Certbot.
        • Todos devem possuir um usuário não root com privilégios sudo e um firewall ativo. Para saber como configurar isso, consulte este guia de Configuração inicial do servidor.
      • Docker instalado em todos os três servidores. Como orientação na instalação do Docker, siga os Passos 1 e 2 de Como instalar e usar o Docker no Ubuntu 18.04.

      • Um nome de domínio registrado. Este tutorial utilizará o your_domain.com durante todo o processo. Você pode obter um domínio gratuitamente através do Freenom, ou usar o registrador de domínios de sua escolha.

      • Um registro de DNS de tipo A com o your_domain.com apontando para o endereço IP público do seu servidor proxy. Você pode seguir esta introdução para o DNS da DigitalOcean para obter mais detalhes sobre como adicioná-lo a uma conta da DigitalOcean, caso seja o que estiver usando:

      • Um bucket de armazenamento de objetos S3, como um espaço da DigitalOcean para armazenar os arquivos estáticos do seu projeto Django e um conjunto de chaves de acesso para esse espaço. Para aprender como criar um espaço, consulte a documentação de produto Como criar espaços. Para aprender como criar chaves de acesso para espaços, consulte Compartilhando acesso a espaços com chaves de acesso. Com pequenas alterações, você pode usar qualquer serviço de armazenamento de objetos que o plug-in django-storages suporte.

      • Uma instância de servidor PostgreSQL, banco de dados e usuário para seu aplicativo Django. Com pequenas alterações, você pode usar qualquer banco de dados compatível com o Django.

      Passo 1 — Configurando o primeiro servidor do aplicativo Django

      Para começar, vamos clonar o repositório do aplicativo Django no primeiro servidor de aplicativo. Em seguida, vamos configurar e compilar a imagem do aplicativo Docker e então testar o aplicativo executando o contêiner do Django.

      Nota: se estiver continuando a partir de Como construir um aplicativo Django e Gunicorn com o Docker, você já terá completado o Passo 1 e pode seguir direto ao Passo 2 para configurar o segundo servidor de aplicativo.

      Comece fazendo login no primeiro dos dois servidores do aplicativo Django e use o git para clonar a ramificação polls-docker a partir do repositório GitHub do aplicativo Polls de tutorial do Django. Este repositório contém o código para o aplicativo de amostra Polls da documentação do Django A ramificação polls-docker contém uma versão em Docker do aplicativo Polls. Para aprender como o aplicativo Polls foi modificado para funcionar efetivamente em um ambiente em contêiner, consulte Como construir um aplicativo Django e Gunicorn com o Docker.

      • git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

      Navegue até o diretório django-polls:

      cd django-polls
      

      Esse diretório contém o código Python do aplicativo Django, um Dockerfile que o Docker usará para compilar a imagem do contêiner, bem como um arquivo env que contém uma lista de variáveis de ambiente a serem passadas para o ambiente de execução do contêiner. Inspecione o Dockerfile usando o cat:

      cat Dockerfile
      

      Output

      FROM python:3.7.4-alpine3.10 ADD django-polls/requirements.txt /app/requirements.txt RUN set -ex && apk add --no-cache --virtual .build-deps postgresql-dev build-base && python -m venv /env && /env/bin/pip install --upgrade pip && /env/bin/pip install --no-cache-dir -r /app/requirements.txt && runDeps="$(scanelf --needed --nobanner --recursive /env | awk '{ gsub(/,/, "nso:", $2); print "so:" $2 }' | sort -u | xargs -r apk info --installed | sort -u)" && apk add --virtual rundeps $runDeps && apk del .build-deps ADD django-polls /app WORKDIR /app ENV VIRTUAL_ENV /env ENV PATH /env/bin:$PATH EXPOSE 8000 CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi"]

      Esse Dockerfile usa a imagem Docker oficial do Python 3.7.4 como base e instala os requisitos de pacote Python do Django e do Gunicorn, conforme definido no arquivo django-polls/requirements.txt. Em seguida, ele remove alguns arquivos de compilação desnecessários, copia o código do aplicativo na imagem e define o PATH de execução. Por fim, ele declara que a porta 8000 será usada para aceitar conexões de contêiner recebidas e executa gunicorn com 3 trabalhadores, escutando na porta 8000.

      Para aprender mais sobre cada um dos passos nesse Dockerfile, confira o Passo 6 de Como construir um aplicativo Django e Gunicorn com o Docker.

      Agora, crie a imagem usando o docker build:

      Nós demos o nome de polls para a imagem usando o sinalizador -t e passamos o diretório atual como um contexto de compilação, que é o conjunto de arquivos de referência ao compilar a imagem.

      Depois que o Docker compilar e marcar a imagem, liste as imagens disponíveis usando docker images:

      docker images
      

      Você deve ver a imagem polls listada:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE polls latest 80ec4f33aae1 2 weeks ago 197MB python 3.7.4-alpine3.10 f309434dea3a 8 months ago 98.7MB

      Antes de executarmos o contêiner Django, precisamos configurar seu ambiente de execução usando o arquivo env presente no diretório atual. Esse arquivo será passado para o comando docker run usado para executar contêiner e o Docker irá injetar as variáveis de ambiente configuradas no ambiente de execução do contêiner.

      Abra o arquivo env com o nano ou com o seu editor favorito:

      nano env
      

      Vamos configurar o arquivo dessa forma, e você precisará adicionar alguns valores adicionais, conforme descrito abaixo.

      django-polls/env

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Preencha os valores que estão faltando para as seguintes chaves:

      • DJANGO_SECRET_KEY: defina isso como um valor único e imprevisível, conforme detalhado na documentação do Django. Um método para gerar essa chave é fornecido em Ajustando as configurações do aplicativo do tutorial sobre o Aplicativo Django escalável.
      • DJANGO_ALLOWED_HOSTS: essa variável protege o aplicativo e impede ataques de cabeçalho de host HTTP. Para fins de teste, defina isso como *, um coringa que irá corresponder a todos os hosts. Na produção, você deve definir isso como your_domain.com. Para aprender mais sobre esse ajuste do Django, consulte as Core Settings da documentação do Django.
      • DATABASE_USERNAME: defina isso como o usuário do banco de dados PostgreSQL criado nos passos pré-requisitos.
      • DATABASE_NAME: defina isso como polls ou o nome do banco de dados PostgreSQL criado nos passos pré-requisitos.
      • DATABASE_PASSWORD: defina isso como a senha do usuário do banco de dados PostgreSQL criada nos passos pré-requisitos.
      • DATABASE_HOST: defina isso como o nome do host do seu banco de dados.
      • DATABASE_PORT: defina isso como a porta do seu banco de dados.
      • STATIC_ACCESS_KEY_ID: defina isso como a chave de acesso do seu bucket S3 ou espaço.
      • STATIC_SECRET_KEY: defina isso como o segredo da chave de acesso do seu bucket S3 ou espaço.
      • STATIC_BUCKET_NAME: defina isso como o nome do seu bucket S3 ou espaço.
      • STATIC_ENDPOINT_URL: defina isso como o URL do ponto de extremidade do bucket S3 ou espaço apropriado, como por exemplo https://space-name.nyc3.digitaloceanspaces.com se seu espaço estiver localizado na região nyc3.

      Assim que terminar a edição, salve e feche o arquivo.

      Agora, usaremos o docker run para substituir o conjunto CMD no Dockerfile e criar o esquema de banco de dados usando os comandos manage.py makemigrations e manage.py migrate:

      docker run --env-file env polls sh -c "python manage.py makemigrations && python manage.py migrate"
      

      Executamos a imagem de contêiner polls:latest, passamos o arquivo de variável de ambiente que acabamos de modificar e substituímos o comando do Dockerfile com sh -c "python manage.py makemigrations && python manage.py migrate", o que irá criar o esquema de banco de dados definido pelo código do aplicativo. Se estiver executando isso pela primeira vez, você deve ver:

      Output

      No changes detected Operations to perform: Apply all migrations: admin, auth, contenttypes, polls, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying polls.0001_initial... OK Applying sessions.0001_initial... OK

      Isso indica que o esquema de banco de dados foi criado com sucesso.

      Se estiver executando migrate uma outra vez, o Django irá cancelar a operação a menos que o esquema de banco de dados tenha sido alterado.

      Em seguida, vamos executar outra instância do contêiner de aplicativo e usar um shell interativo dentro dela para criar um usuário administrativo para o projeto Django.

      docker run -i -t --env-file env polls sh
      

      Isso lhe fornecerá um prompt do shell dentro do contêiner em execução que você pode usar para criar o usuário do Django:

      python manage.py createsuperuser
      

      Digite um nome de usuário, endereço de e-mail e senha para o seu usuário e, depois de criá-lo, pressione CTRL+D para sair do contêiner e encerrá-lo.

      Por fim, vamos gerar os arquivos estáticos para o aplicativo e fazer o upload deles para o espaço da DigitalOcean usando o collectstatic. Observe que esse processo pode demorar um pouco de tempo para ser concluído.

      docker run --env-file env polls sh -c "python manage.py collectstatic --noinput"
      

      Depois que esses arquivos forem gerados e enviados, você receberá a seguinte saída.

      Output

      121 static files copied.

      Agora, podemos executar o aplicativo:

      docker run --env-file env -p 80:8000 polls
      

      Output

      [2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0 [2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) [2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync [2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7 [2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8 [2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9

      Aqui, executamos o comando padrão definido no Dockerfile, gunicorn --bind :8000 --workers 3 mysite.wsgi:application e expomos a porta do contêiner 8000 para que a porta 80 no servidor Ubuntu seja mapeada para a porta 8000 do contêiner polls.

      Agora, você deve ser capaz de navegar até o aplicativo polls usando seu navegador Web digitando http://APP_SERVER_1_IP na barra de URL. Como não há nenhuma rota definida para o caminho /, você provavelmente receberá um erro 404 Page Not Found, o que é esperado.

      Aviso: quando se usa o firewall UFW com o Docker, o Docker ignora quaisquer regras configuradas do firewall UFW, conforme documentado neste problema do GitHub. Isso explica por que você tem acesso à porta 80 do seu servidor, mesmo que não tenha criado explicitamente uma regra de acesso no UFW em qualquer passo pré-requisito. No Passo 5, vamos tratar desse problema de segurança corrigindo a configuração do UFW. Se você não estiver usando o UFW e estiver usando os Firewalls em Nuvem da DigitalOcean, você pode ignorar com segurança esse aviso.

      Navegue até http://APP_SERVER_1_IP/polls para ver a interface do aplicativo Polls:

      Interface do aplicativo Polls

      Para visualizar a interface administrativa, visite http://APP_SERVER_1_IP/admin. Você deve ver a janela de autenticação do administrador do aplicativo Polls:

      Página de autenticação de administrador do Polls

      Digite o nome e a senha do usuário administrativo que você criou com o comando createsuperuser.

      Depois de autenticar-se, você pode acessar a interface administrativa do aplicativo Polls:

      Interface administrativa principal do Polls

      Observe que os ativos estáticos para os aplicativos admin e polls estão sendo entregues diretamente do armazenamento de objetos. Para confirmar isso, consulte Testando a entrega de arquivos estáticos de espaços.

      Quando terminar de explorar, aperte CTRL+C na janela do terminal executando o contêiner Docker para encerrar o contêiner.

      Agora que confirmou que o contêiner de aplicativo funciona como esperado, você pode executá-lo em modo separado. Isso irá executá-lo em segundo plano e lhe permitirá fazer logoff da sua sessão SSH:

      docker run -d --rm --name polls --env-file env -p 80:8000 polls
      

      O sinalizador -d instrui o Docker a executar o contêiner em modo separado, o sinalizador -rm limpa o sistema de arquivos do contêiner após a saída do contêiner e damos o nome de polls a ele.

      Faça logoff do primeiro servidor do aplicativo Django e navegue até http://APP_SERVER_1_IP/polls para confirmar se o contêiner está funcionando como esperado.

      Agora que seu primeiro servidor do aplicativo Django está em operação, você pode configurar seu segundo servidor do aplicativo Django.

      Passo 2 — Configurando o segundo servidor do aplicativo Django

      Como muitos dos comandos usados para configurar este servidor serão iguais àqueles do passo anterior, eles serão apresentados aqui de forma abreviada. Por favor, reveja o Passo 1 para obter mais informações sobre qualquer comando em particular neste passo.

      Comece fazendo login no segundo servidor do aplicativo Django.

      Clone a ramificação polls-docker do repositório GitHub django-polls:

      • git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

      Navegue até o diretório django-polls:

      cd django-polls
      

      Compile a imagem usando o docker build:

      Abra o arquivo env com o nano ou com o seu editor favorito:

      nano env
      

      django-polls/env

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Preencha os valores que estão faltando como no Passo 1. Quando terminar a edição, salve e feche o arquivo.

      Por fim, execute o contêiner do aplicativo em modo separado:

      docker run -d --rm --name polls --env-file env -p 80:8000 polls
      

      Navegue até http://APP_SERVER_2_IP/polls para confirmar se o contêiner está funcionando como esperado. Você pode fazer logoff com segurança do segundo servidor de aplicativo sem precisar encerrar seu contêiner em execução.

      Com ambos os contêineres do aplicativo Django em operação, você pode seguir para a configuração do contêiner do proxy reverso do Nginx.

      Passo 3 — Configurando o contêiner Docker do Nginx

      O Nginx é um servidor Web versátil que oferece vários recursos, incluindo proxy reverso, balanceamento de carga e cache. Neste tutorial, nós descarregamos os ativos estáticos do Django para um armazenamento de objetos, então não usaremos as capacidades de cache do Nginx. No entanto, usaremos o Nginx como um proxy reverso para nossos dois servidores de backend do aplicativo Django e distribuiremos pedidos recebidos entre eles. Além disso, o Nginx irá realizar a terminação TLS e redirecionamento utilizando um certificado TLS fornecido pelo Certbot. Isso significa que ele irá forçar os clientes a usar o HTTPS, redirecionando solicitações HTTP recebidas para a porta 443. Em seguida, irá descriptografar as solicitações HTTPS e enviá-las via proxy para os servidores upstream do Django.

      Neste tutorial, tomamos a decisão de projeto de dissociar os contêineres do Nginx dos servidores de backend. Dependendo do seu caso de uso, você pode optar por executar o contêiner do Nginx em um dos servidores do aplicativo Django, fazendo o proxy de pedidos localmente, bem como para o outro servidor Django. Outra arquitetura possível seria executar dois contêineres do Nginx, um em cada servidor de backend, com um balanceador de carga em nuvem no frontend. Cada arquitetura apresenta diferentes vantagens de segurança e desempenho, e você deve fazer o teste de carga no seu sistema para descobrir gargalos. A arquitetura flexível descrita neste tutorial permite que você escale tanto a camada backend do aplicativo Django, quanto a camada de proxy do Nginx. Assim que o contêiner do Nginx único se tornar um gargalo, você pode escalar o processo para vários proxies do Nginx e adicionar um balanceador de carga em nuvem ou um balanceador de carga L4 rápido como o HAProxy.

      Com ambos os servidores do aplicativo Django em operação, podemos começar a configurar o servidor de proxy do Nginx. Faça login em seu servidor de proxy e crie um diretório chamado conf:

      mkdir conf
      

      Crie um arquivo de configuração chamado nginx.conf usando o nano ou seu editor favorito:

      nano conf/nginx.conf
      

      Cole nele a seguinte configuração do Nginx:

      conf/nginx.conf

      
      upstream django {
          server APP_SERVER_1_IP;
          server APP_SERVER_2_IP;
      }
      
      server {
          listen 80 default_server;
          return 444;
      }
      
      server {
          listen 80;
          listen [::]:80;
          server_name your_domain.com;
          return 301 https://$server_name$request_uri;
      }
      
      server {
          listen 443 ssl http2;
          listen [::]:443 ssl http2;
          server_name your_domain.com;
      
          # SSL
          ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/your_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;
          }
      
      }
      

      Esses blocos upstream, server e location configuram o Nginx para redirecionar solicitações HTTP para HTTPS, além do balanceamento de carga entre eles em dois servidores do aplicativo Django configurados nos Passos 1 e 2. Para aprender mais sobre a estrutura do arquivo de configuração do Nginx, consulte este artigo, Compreendendo a estrutura do arquivo de configuração do Nginx e contextos de configuração. Além dele, este artigo, Compreendendo os algoritmos de seleção do servidor Nginx e do bloco de localização, também pode ser útil.

      Essa configuração foi montada a partir de arquivos de configuração de amostra fornecidos pelo Gunicorn, Cerbot e Nginx e serve como uma configuração mínima do Nginx para tornar essa arquitetura funcional. Ajustar essa configuração do Nginx vai além do escopo deste artigo, mas você pode usar uma ferramenta como o NGINXConfig para gerar arquivos de configuração do Nginx com maior desempenho e segurança para sua arquitetura.

      O bloco upstream define o grupo de servidores usados para fazer proxy de solicitações usando a diretiva proxy_pass:

      conf/nginx.conf

      upstream django {
          server APP_SERVER_1_IP;
          server APP_SERVER_2_IP;
      }
      . . .
      

      Neste bloco, damos o nome de django ao upstream e incluímos os endereços IP de ambos os servidores do aplicativo Django. Se os servidores do aplicativo estiverem em execução na DigitalOcean e possuírem a rede VPC ativada, você deve usar aqui seus endereços IP privados. Para aprender como habilitar a rede VPC na DigitalOcean, consulte Como habilitar a rede VPC em Droplets existentes.

      O primeiro bloco server captura solicitações que não correspondam ao seu domínio e encerra a conexão. Por exemplo, uma solicitação HTTP direta para o endereço IP do seu servidor seria manipulada por este bloco:

      conf/nginx.conf

      . . .
      server {
          listen 80 default_server;
          return 444;
      }
      . . .
      

      O próximo bloco server redireciona as solicitações HTTP para seu domínio para HTTPS usando um redirecionamento HTTP 301. Essas solicitações são então manipuladas pelo bloco server final:

      conf/nginx.conf

      . . .
      server {
          listen 80;
          listen [::]:80;
          server_name your_domain.com;
          return 301 https://$server_name$request_uri;
      }
      . . .
      

      Essas duas diretivas definem os caminhos para o certificado TLS e a chave secreta. Eles serão fornecidos usando o Certbot e montados no contêiner do Nginx no próximo passo.

      conf/nginx.conf

      . . .
      ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
      . . .
      

      Esses parâmetros são os padrões de segurança SSL recomendados pelo Certbot. Para aprender mais sobre eles, consulte Module ngx_http_ssl_module da documentação do Nginx. O Security/Server Side TLS do Mozilla é outro guia útil que você ajuste sua configuração SSL.

      conf/nginx.conf

      . . .
          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";
      . . .
      

      Essas duas diretivas da amostra de configuração do Nginx do Gunicorn definem o tamanho máximo permitido do corpo da solicitação do cliente e atribuem o tempo limite para manutenção de conexão com o cliente. O Nginx irá interromper as conexões com o cliente após keepalive_timeout segundos.

      conf/nginx.conf

      . . .
      client_max_body_size 4G;
      keepalive_timeout 5;
      . . .
      

      O primeiro bloco location instrui o Nginx a redirecionar solicitações via proxy para os servidores upstream django via HTTP. Ele também preserva os cabeçalhos HTTP do cliente que capturam o endereço IP originário, protocolo usado para conexão e host de destino:

      conf/nginx.conf

      . . .
      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;
      }
      . . .
      

      Para aprender mais sobre essas diretivas, consulte Deploying Gunicorn e Module ngx_http_proxy_module da documentação do Nginx.

      O bloco location final captura as solicitações para o caminho /well-known/acme-challenge/ usado pelo Certbot para HTTP-01 challenges. Ele faz isso para verificar seu domínio com o Let’s Encrypt e fornecer ou renovar os certificados TLS. Para maiores informações sobre o HTTP-01 challenge usado pelo Certbot, consulte Challenge Types da documentação do Let’s Encrypt.

      conf/nginx.conf

      . . .
      location ^~ /.well-known/acme-challenge/ {
              root /var/www/html;
      }
      

      Assim que terminar a edição, salve e feche o arquivo.

      Agora, você pode usar esse arquivo de configuração para executar um contêiner Docker do Nginx. Neste tutorial, usaremos a imagem nginx:1.19.0, versão 1.19.0 da imagem oficial do Docker mantida pelo Nginx.

      Quando executamos o contêiner pela primeira vez, o Nginx irá gerar um erro e falhar, pois ainda não fornecemos os certificados definidos no arquivo de configuração. No entanto, ainda vamos executar o comando para baixar localmente a imagem do Nginx e testar se todo o resto está funcionando corretamente:

      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.19.0
      

      Aqui, chamamos o contêiner de nginx e mapeamos as portas de host 80 e 443 para as respectivas portas do contêiner. O sinalizador -v monta o arquivo de configuração no contêiner do Nginx em /etc/nginx/conf.d/nginx.conf, que é de onde a imagem do Nginx está configurada para carregar. Ele é montado em ro, ou modo “apenas leitura”, para que o contêiner não consiga modificar o arquivo. O diretório root Web /var/www/html também é montado no contêiner. Por fim, o nginx:1.19.0 instrui o Docker a pegar e executar a imagem nginx:1.19.0 do Dockerhub.

      O Docker irá pegar e executar a imagem, e então o Nginx irá gerar um erro quando não encontrar o certificado TLS e a chave secreta configurados. No próximo passo, vamos providenciá-los usando um cliente Certbot em Docker e a autoridade de certificação Let’s Encrypt.

      Passo 4 — Configurando o Certbot e a renovação de certificados do Lets Encrypt

      O Certbot é um cliente do Let’s Encrypt desenvolvido pela Electronic Frontier Foundation. Ele fornece certificados TLS gratuitos da autoridade de certificação Let’s Encrypt, que permite que navegadores verifiquem a identidade de seus servidores Web. Como temos o Docker instalado em nosso servidor de proxy do Nginx, usaremos a imagem Certbot do Docker para fornecer e renovar os certificados TLS.

      Comece garantindo que você tenha um registro DNS do tipo A mapeado para o endereço IP público do servidor proxy. Em seguida, em seu servidor proxy, forneça uma versão de preparo dos certificados usando a imagem certbot do Docker:

      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 your_domain.com
      

      Esse comando executa a imagem certbot do Docker em modo interativo, e encaminha a porta 80 no host para a porta 80 no contêiner. Ele cria e monta dois diretórios de host no contêiner: /etc/letsencrypt/ e /var/lib/letsencrypt/. O certbot é executado no modo standalone, sem o Nginx, e usará os servidores staging — de preparo — do Let’s Encrypt para realizar a validação de domínio.

      Quando solicitado, digite seu endereço de e-mail e concorde com os Termos de serviço. Se a validação de domínio for bem sucedida, você deve ver o seguinte resultado:

      Output

      Obtaining a new certificate Performing the following challenges: http-01 challenge for stubb.dev Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/your_domain.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/your_domain.com/privkey.pem Your cert will expire on 2020-09-15. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal.

      Você pode inspecionar o certificado usando o cat:

      sudo cat /etc/letsencrypt/live/your_domain.com/fullchain.pem
      

      Com o certificado TLS fornecido, podemos testar a configuração do Nginx montada no passo anterior:

      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.19.0
      

      Esse é a mesma execução de comandos do Passo 3, com a adição de ambos os diretórios recém-criados do Let’s Encrypt.

      Assim que o Nginx estiver em funcionamento, navege até http://your_domain.com. Você pode receber um aviso em seu navegador de que a autoridade de certificação é inválida. Isso é esperado, já que fornecemos os certificados de preparo e não os certificados do Let’s Encrypt de produção. Verifique a barra de URL do seu navegador para confirmar se sua solicitação HTTP foi redirecionada para o HTTPS.

      Aperte CTRL+C em seu terminal para sair do Nginx e execute o cliente certbot novamente, mas, desta vez, omitindo o sinalizador --staging:

      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 your_domain.com
      

      Quando solicitado a manter o certificado existente ou renová-lo, aperte 2 para renová-lo e então ENTER para confirmar sua escolha.

      Com o certificado TLS de produção fornecido, execute o servidor Nginx novamente:

      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.19.0
      

      Em seu navegador, navegue até http://your_domain.com. Na barra de URL, confirme se a solicitação HTTP foi redirecionada para o HTTPS. Como o aplicativo Polls não possui nenhum padrão de rota configurado, você deve ver um erro Page not found do Django. Navegue até https://your_domain.com/polls e você verá a interface padrão do aplicativo Polls:

      Interface do aplicativo Polls

      Neste ponto, você já forneceu um certificado TLS de produção usando o cliente Certbot do Docker, e está aplicando um proxy reverso e balanceamento de carga nas solicitações externas de carga para os dois servidores do aplicativo Django.

      Os certificados do Let’s Encrypt expiram a cada 90 dias. Para garantir que seu certificado permaneça válido, você deve renová-lo regularmente antes de sua expiração programada. Com o Nginx em execução, você deve usar o cliente Certbot no modo webroot em vez do modo standalone. Isso significa que o Certbot irá realizar a validação criando um arquivo no diretório /var/www/html/.well-known/acme-challenge/ e as solicitações de validação do Let’s Encrypt para este caminho serão capturadas pela regra location definida na configuração do Nginx no Passo 3. Então, o Certbot irá rotacionar os certificados e você pode recarregar o Nginx para que ele use esse certificado recém-fornecido.

      Existem várias maneiras de automatizar esse procedimento e a renovação automática de certificados TLS vai além do escopo deste tutorial. Para um processo semelhante usando o utilitário de planejamento cron, consulte o Passo 6 de Como proteger um aplicativo Node.js em contêiner com Nginx, Let’s Encrypt e Docker Compose.

      Em seu terminal, aperte CTRL+C para encerrar o contêiner do Nginx. Execute-o novamente em modo separado adicionando o sinalizador -d:

      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.19.0
      

      Com o Nginx em execução em segundo plano, use o comando a seguir para realizar simulação do procedimento de renovação de certificado:

      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
      

      Usamos o plug-in --webroot, especificamos o caminho do root Web e usamos o sinalizador --dry-run para verificar se tudo está funcionando corretamente sem realmente realizar a renovação de certificado.

      Se a simulação de renovação for bem sucedida, você deve ver o seguinte resultado:

      Output

      Cert not due for renewal, but simulating renewal for dry run Plugins selected: Authenticator webroot, Installer None Renewing an existing certificate Performing the following challenges: http-01 challenge for your_domain.com Using the webroot path /var/www/html for all unmatched domains. Waiting for verification... Cleaning up challenges - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - new certificate deployed without reload, fullchain is /etc/letsencrypt/live/your_domain.com/fullchain.pem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates below have not been saved.) Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/your_domain.com/fullchain.pem (success) ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates above have not been saved.) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      Em uma configuração de produção, após renovar os certificados, é necessário recarregar o Nginx para que as alterações entrem em vigor. Para recarregar o Nginx, execute o seguinte comando:

      docker kill -s HUP nginx
      

      Esse comando enviará um sinal HUP do Unix para o processo do Nginx em execução dentro do contêiner nginx do Docker. Ao receber esse sinal, o Nginx irá recarregar suas configurações e os certificados renovados.

      Com o HTTPS habilitado e todos os componentes dessa arquitetura em operação, o passo final é bloquear a configuração impedindo o acesso externo aos dois servidores backend de aplicativo. Todas as solicitações HTTP devem fluir através do proxy do Nginx.

      Passo 5 — Prevenindo o acesso externo a servidores do aplicativo Django

      Na arquitetura descrita neste tutorial, a terminação SSL ocorre no proxy do Nginx. Isso significa que o Nginx descriptografa a conexão SSL, e os pacotes, não criptografados, são enviados via proxy para os servidores do aplicativo Django. Para muitos casos de uso, esse nível de segurança é o suficiente. Para aplicações envolvendo dados financeiros ou de saúde, pode ser interessante implementar uma criptografia de ponta a ponta. Você pode fazer isso encaminhando pacotes criptografados através do balanceador de carga e descriptografando-os nos servidores do aplicativo, ou criptografando novamente no proxy e mais uma vez descriptografando nos servidores do aplicativo Django. Essas técnicas vão além do escopo deste artigo, mas para aprender mais sobre elas, consulte Criptografia de ponta a ponta.

      O proxy do Nginx age como um gateway entre o tráfego externo e a rede interna. Teoricamente, nenhum cliente externo deve ter acesso direto aos servidores internos do aplicativo, e todas as solicitações devem fluir através do servidor Nginx. A nota no Passo 1 descreve brevemente um problema em aberto com o Docker, onde ele ignora as configurações de firewall do ufw por padrão e abre portas externamente, o que pode não ser seguro. Para lidar com essa preocupação de segurança, é recomendado usar firewalls em nuvem ao trabalhar com servidores que possuam o Docker habilitado. Para obter mais informações sobre a criação de firewalls em nuvem com a DigitalOcean, consulte Como criar firewalls. Você também pode manipular o iptables diretamente em vez de usar o ufw. Para aprender mais sobre como usar o iptables com o Docker, consulte Docker e o iptables.

      Neste passo, vamos modificar a configuração do UFW para bloquear o acesso externo a portas do host abertas pelo Docker. Ao executarmos o Django nos servidores do aplicativo, passamos o sinalizador -p 80:8000 para o docker, que encaminha a porta 80 no host para a porta 8000 no contêiner. Isso também abriu a porta 80 para clientes externos, o que pode ser verificado visitando http://your_app_server_1_IP. Para evitar o acesso direto, vamos modificar a configuração do UFW usando o método descrito no repositório ufw-docker do GitHub.

      Comece fazendo login no primeiro servidor do aplicativo Django. Em seguida, abra o arquivo /etc/ufw/after.rules com privilégios de superusuário, usando o nano ou o seu editor favorito:

      sudo nano /etc/ufw/after.rules
      

      Digite sua senha quando solicitado, e aperte ENTER para confirmar.

      Você deve ver as seguintes regras do ufw:

      /etc/ufw/after.rules

      #
      # rules.input-after
      #
      # Rules that should be run after the ufw command line added rules. Custom
      # rules should be added to one of these chains:
      #   ufw-after-input
      #   ufw-after-output
      #   ufw-after-forward
      #
      
      # Don't delete these required lines, otherwise there will be errors
      *filter
      :ufw-after-input - [0:0]
      :ufw-after-output - [0:0]
      :ufw-after-forward - [0:0]
      # End required lines
      
      # don't log noisy services by default
      -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
      
      # don't log noisy broadcast
      -A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input
      
      # don't delete the 'COMMIT' line or these rules won't be processed
      COMMIT
      

      Role até o final e cole o bloco a seguir de regras de configuração do UFW:

      /etc/ufw/after.rules

      . . .
      
      # 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
      

      Essas regras restringem o acesso público às portas abertas pelo Docker e permitem o acesso dos intervalos de IP privativo 10.0.0.0/8, 172.16.0.0/12 e 192.168.0.0/16. Se estiver usando o VPC com a DigitalOcean, então os Droplets em sua rede VPC terão acesso à porta aberta através da interface de rede privada, mas os clientes externos não terão. Para maiores informações sobre o VPC, consulte a documentação oficial do VPC. Para aprender mais sobre as regras implementadas nesse trecho de código, consulte Como isso funciona? do LEIAME do ufw-docker.

      Se você não estiver usando o VPC com a DigitalOcean, e digitou os endereços IP públicos dos servidores do aplicativo no bloco upstream de sua configuração do Nginx, será necessário modificar explicitamente o firewall UFW para permitir o tráfego do servidor Nginx através da porta 80 nos servidores do aplicativo Django. Para orientação sobre a criação de regras de allow (permissão) com o firewall UFW, consulte Fundamentos do UFW: Regras e comandos comuns do firewall.

      Assim que terminar a edição, salve e feche o arquivo.

      Reinicie o ufw para que a nova configuração entre em vigor:

      sudo systemctl restart ufw
      

      Navegue até http://APP_SERVER_1_IP em seu navegador Web para confirmar se não é mais possível acessar o servidor do aplicativo pela porta 80.

      Repita esse processo no segundo servidor do aplicativo Django.

      Faça logoff do primeiro servidor de aplicativo ou abra outra janela do terminal e faça login no segundo servidor do aplicativo Django. Em seguida, abra o arquivo /etc/ufw/after.rules com privilégios de superusuário, usando o nano ou o seu editor favorito:

      sudo nano /etc/ufw/after.rules
      

      Digite sua senha quando solicitado e aperte ENTER para confirmar.

      Role até o final e cole o bloco a seguir de regras de configuração do UFW:

      /etc/ufw/after.rules

      . . .
      
      # 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
      

      Assim que terminar a edição, salve e feche o arquivo.

      Reinicie o ufw para que a nova configuração entre em vigor:

      sudo systemctl restart ufw
      

      Navegue até http://APP_SERVER_2_IP em seu navegador Web para confirmar se não é mais possível acessar o servidor do aplicativo pela porta 80.

      Por fim, navegue até https://your_domain_here/polls para confirmar se o proxy do Nginx ainda tem acesso aos servidores upstream do Django. Você deve ver a interface padrão do aplicativo Polls.

      Conclusão

      Neste tutorial, você configurou um aplicativo Polls do Django escalável usando contêineres Docker. À medida que seu tráfego cresce e a carga no sistema aumenta, é possível escalar cada camada separadamente: a camada de proxy do Nginx, a camada backend do aplicativo Django e a camada do banco de dados PostgreSQL.

      Ao construir um sistema distribuído, muitas vezes há várias decisões de projeto que você deve enfrentar, e várias arquiteturas podem satisfazer seu caso de uso. A arquitetura descrita neste tutorial tem o intuito de servir como um planejamento flexível para projetar aplicativos escaláveis com o Django e o Docker.

      Você pode desejar controlar o comportamento dos contêineres quando eles encontram erros, ou executar contêineres automaticamente quando o seu sistema for inicializado. Para fazer isso, você pode usar um gerenciador de processos como o Systemd ou implementar políticas de reinicialização. Para mais informações sobre isso, consulte Iniciar contêineres automaticamente da documentação do Docker.

      Ao trabalhar em escala com vários hosts executando a mesma imagem do Docker, pode ser mais eficiente automatizar passos usando uma ferramenta de gerenciamento de configuração como o Ansible ou o Chef. Para aprender mais sobre o gerenciamento de configuração, consulte Uma introdução ao gerenciamento de configuração e Automatizando a configuração do servidor com o Ansible: um kit de oficina da DigitalOcean.

      Em vez de compilar a mesma imagem em todos os hosts, você também pode simplificar a implantação usando um registro de imagem como o Docker Hub, que compila, armazena e distribui centralmente as imagens do Docker em vários servidores. Junto com um registro de imagem, um pipeline de integração e implantação contínuas pode ajudá-lo a compilar, testar e implantar imagens em seus servidores de aplicativo. Para mais informações sobre CI/CD, consulte Introdução a práticas recomendadas de CI/CD.



      Source link

      Como Escalar Automaticamente suas Cargas de Trabalho no Kubernetes da DigitalOcean


      Introdução

      Ao trabalhar com uma aplicação criada no Kubernetes, os desenvolvedores frequentemente precisam provisionar pods adicionais para lidar com períodos de pico de tráfego ou aumento da carga de processamento. Por padrão, provisionar esses pods adicionais é uma etapa manual; o desenvolvedor deve alterar o número de réplicas desejadas no objeto do deployment para contar com o aumento do tráfego e alterá-lo novamente quando os pods adicionais não forem mais necessários. Essa dependência da intervenção manual pode não ser o ideal em muitos cenários. Por exemplo, sua carga de trabalho pode atingir o horário de pico no meio da noite, quando ninguém está acordado para escalar os pods, ou seu site pode receber um aumento inesperado no tráfego quando uma resposta manual não seria rápida o suficiente para lidar com a carga. Nessas situações, a abordagem mais eficiente e menos sujeita a erros é automatizar o escalonamento dos seus clusters com o Horizontal Pod Autoscaler (HPA).

      Usando informações do Metrics Server, o HPA detectará aumento no uso de recursos e responderá escalando sua carga de trabalho para você. Isso é especialmente útil nas arquiteturas de microsserviço e dará ao cluster Kubernetes a capacidade de escalar seu deployment com base em métricas como a utilização da CPU. Quando combinado como o DigitalOcean Kubernetes (DOKS), uma oferta de Kubernetes gerenciada que fornece aos desenvolvedores uma plataforma para fazer o deploy de aplicações containerizadas, o uso do HPA pode criar uma infraestrutura automatizada que se ajusta rapidamente às mudanças no tráfego e na carga.

      Nota: Ao considerar a possibilidade de usar o autoscaling para sua carga de trabalho, lembre-se de que o autoscaling funciona melhor para aplicativos sem estado ou stateless, especialmente aqueles capazes de ter várias instâncias da aplicação em execução e aceitando tráfego em paralelo. Esse paralelismo é importante porque o principal objetivo do autoscaling é distribuir dinamicamente a carga de trabalho de uma aplicação por várias instâncias no cluster Kubernetes para garantir que sua aplicação tenha os recursos necessários para atender o tráfego de maneira ágil e estável, sem sobrecarregar nenhuma instância única.

      Um exemplo de carga de trabalho que não apresenta esse paralelismo é o autoscaling de banco de dados. A configuração do autoscaling para um banco de dados seria muito mais complexa, pois você precisaria considerar race conditions, problemas com a integridade dos dados, sincronização de dados e adições e remoções constantes de membros do cluster de banco de dados. Por razões como essas, não recomendamos o uso da estratégia de autoscaling deste tutorial para bancos de dados.

      Neste tutorial você vai configurar um deployment de exemplo do Nginx no DOKS que pode auto escalar horizontalmente para dar conta do aumento da carga de CPU. Você conseguirá isso ao fazer o deploy do Metrics Server em seu cluster para reunir métricas de pod para o HPA usar para determinar quando escalar.

      Pré-requisitos

      Antes de começar este guia, você precisará do seguinte:

      • Um cluster Kubernetes na DigitalOcean com sua conexão configurada como padrão kubectl. As instruções sobre como configurar o kubectl são mostradas no passo Connect to your Cluster quando você cria seu cluster. Para criar um cluster Kubernetes na DigitalOcean, consulte Kubernetes Quickstart.

      • O gerenciador de pacotes Helm instalado em sua máquina local e o Tiller instalado em seu cluster. Para fazer isso, execute os passos 1 e 2 do tutorial How To Install Software on Kubernetes Clusters with the Helm Package Manager

      Passo 1 — Criando um Deployment de Teste

      Para mostrar o efeito do HPA, você primeiro fará o deploy de uma aplicação que você utilizará para fazer autoscale. Este tutorial usa uma imagem Nginx Docker padrão como um deployment porque ela é totalmente capaz de operar em paralelo, é amplamente usada no Kubernetes com ferramentas como o Nginx Ingress Controller, e é leve para configurar. Esse deployment do Nginx servirá uma página estática Welcome to Nginx!, que vem por padrão na imagem base. Se você já possui um deployment que gostaria de escalar, sinta-se à vontade para usá-lo e pule este passo.

      Crie o deployment de exemplo usando a imagem base do Nginx executando o seguinte comando. Você pode substituir o nome web se desejar atribuir um nome diferente ao seu deployment:

      • kubectl create deployment web --image=nginx:latest

      A flag --image=nginx:latest criará o deployment a partir da versão mais recente da imagem base do Nginx.

      Após alguns segundos, seu pod web será lançado. Para ver este pod, execute o seguinte comando, que mostrará os pods em execução no namespace atual:

      Isso lhe dará uma saída semelhante à seguinte:

      Output

      NAME READY STATUS RESTARTS AGE web-84d7787df5-btf9h 1/1 Running 0 11s

      Observe que há apenas um pod deployado originalmente. Depois que o autoscaling é acionado, mais pods serão criados automaticamente.

      Agora você tem um deployment básico em funcionamento no cluster. Este é o deployment que você irá configurar para o autoscaling. Seu próximo passo é configurar esse deployment para definir suas solicitações de recursos e limites.

      Passo 2 — Definindo Limites e Solicitações de CPU em seu Deployment

      Neste passo, você irá definir solicitações e limites no uso da CPU para seu deployment. Limites ou Limits no Kubernetes são definidos no deployment para descrever a quantidade máxima de recursos (CPU ou Memória) que o pod pode usar. Solicitações ou Requests são definidas no deployment para descrever quanto desse recurso é necessário em um node para que esse node seja considerado como um node válido para escalonamento. Por exemplo, se seu servidor web tivesse uma solicitação de memória definida em 1 GB, apenas os nodes com pelo menos 1 GB de memória livre seriam considerados para escalonamento. Para o autoscaling, é necessário definir esses limites e solicitações, pois o HPA precisará ter essas informações ao tomar decisões de escalonamento e provisionamento.

      Para definir solicitações e limites, você precisará fazer alterações no deployment que você acabou de criar. Este tutorial usará o seguinte comando kubectl edit para modificar a configuração do objeto API armazenada no cluster. O comando kubectl edit abrirá o editor definido por suas variáveis de ambiente KUBE_EDITOR ou EDITOR, ou cairá de volta no vi para Linux ou notepad para Windows por padrão.

      Edite seu deployment:

      • kubectl edit deployment web

      Você verá a configuração para o deployment. Agora você pode definir limites de recursos e solicitações especificadas para o uso de CPU do seu deployment. Esses limites definem a linha de base de quanto de cada recurso um pod deste deployment pode usar individualmente. Definir isso dará ao HPA um quadro de referência para saber se um pod está sendo sobrecarregado. Por exemplo, se você espera que seu pod tenha um limit superior de 100 milicores de CPU e o pod esteja usando 95 milicores atualmente, a HPA saberá que está com 95% da capacidade. Sem fornecer esse limite de 100 milicores, o HPA não pode decifrar a capacidade total do pod.

      Podemos definir os limites e solicitações na seção resources:

      Deployment Configuration File

      . . .
        template:
          metadata:
            creationTimestamp: null
            labels:
              app: web
          spec:
            containers:
            - image: nginx:latest
              imagePullPolicy: Always
              name: nginx
              resources: {}
              terminationMessagePath: /dev/termination-log
              terminationMessagePolicy: File
            dnsPolicy: ClusterFirst
            restartPolicy: Always
            schedulerName: default-scheduler
            securityContext: {}
            terminationGracePeriodSeconds: 30
      status:
        availableReplicas: 1
      . . .
      

      Para este tutorial, você definirá requests para CPU como 100m e memória para 250Mi. Esses valores são apenas para fins de demonstração; cada carga de trabalho é diferente, portanto, esses valores podem não fazer sentido para outras cargas de trabalho. Como regra geral, esses valores devem ser definidos no máximo que um pod dessa carga de trabalho deve usar. Recomenda-se o monitoramento da aplicação e a coleta de dados de uso de recursos sobre o desempenho em períodos de baixa e de pico para ajudar a determinar esses valores. Esses valores também podem ser ajustados e alterados a qualquer momento, assim você sempre pode voltar e otimizar seu deployment posteriormente.

      Vá em frente e insira as seguintes linhas destacadas na seção resources do seu container Nginx:

      Deployment Configuration File

      . . .
        template:
          metadata:
            creationTimestamp: null
            labels:
              app: web
          spec:
            containers:
            - image: nginx:latest
              imagePullPolicy: Always
              name: nginx
              resources:
                limits:
                  cpu: 300m
                requests:
                  cpu: 100m
                  memory: 250Mi
              terminationMessagePath: /dev/termination-log
              terminationMessagePolicy: File
            dnsPolicy: ClusterFirst
            restartPolicy: Always
            schedulerName: default-scheduler
            securityContext: {}
            terminationGracePeriodSeconds: 30
      status:
        availableReplicas: 1
      . . .
      

      Depois de inserir essas linhas, salve e saia do arquivo. Se houver um problema com a sintaxe, o kubectl irá reabrir o arquivo para você com um erro publicado para que você obtenha mais informações.

      Agora que você definiu seus limites e solicitações, você precisa garantir que suas métricas sejam reunidas para que o HPA possa monitorar e aderir corretamente a esses limites. Para fazer isso, você irá configurar um serviço para reunir as métricas de CPU. Para este tutorial, você usará o projeto Metrics Server para coletar essas métricas, que você instalará com um chart do Helm.

      Passo 3 — Instalando o Metrics Server

      Agora você instalará o Kubernetes Metric Server. Esse é o servidor que extrai as métricas do pod, que reunirá as métricas que o HPA usará para decidir se o autoscaling é necessário.

      Para instalar o Metrics Server usando o Helm, execute o seguinte comando:

      • helm install stable/metrics-server --name metrics-server

      Isso instalará a versão estável mais recente do Metrics Server. A flag --name nomeia este release como metrics-server.

      Depois de aguardar a inicialização deste pod, tente usar o comando kubectl top pod para exibir as métricas do seu pod:

      Este comando tem como objetivo fornecer uma visão em nível de pod do uso de recursos em seu cluster, mas devido à maneira como o DOKS lida com o DNS, esse comando retornará um erro neste momento:

      Output

      Error: Metrics not available for pod Error from server (ServiceUnavailable): the server is currently unable to handle the request (get pods.metrics.k8s.io)

      Esse erro ocorre porque os nodes DOKS não criam um registro DNS para eles mesmos e, como o Metrics Server entra em contato com os nodes por meio de seus nomes de host, os nomes de host não são resolvidos corretamente. Para corrigir esse problema, altere a maneira como o Metrics Server se comunica com os nodes adicionando flags de runtime ao container do Metrics Server usando o seguinte comando:

      • kubectl edit deployment metrics-server

      Você estará adicionando uma flag na seção command.

      metrics-server Configuration File

      . . .
        template:
          metadata:
            creationTimestamp: null
            labels:
              app: metrics-server
              release: metrics-server
          spec:
            affinity: {}
            containers:
            - command:
              - /metrics-server
              - --cert-dir=/tmp
              - --logtostderr
              - --secure-port=8443
              image: gcr.io/google_containers/metrics-server-amd64:v0.3.4
              imagePullPolicy: IfNotPresent
              livenessProbe:
                failureThreshold: 3
                httpGet:
                  path: /healthz
      . . .
      

      A flag que você está adicionando é --kubelet-preferred-address-types=InternalIP. Essa flag informa ao metrics server para contatar os nodes usando seu internalIP em oposição ao nome do host. Você pode usar essa flag como uma solução alternativa para se comunicar com os nodes por meio de endereços IP internos.

      Adicione também a flag --metric-resolution para alterar a taxa padrão na qual o Metrics Server extrai as métricas. Para este tutorial, configuraremos o Metrics Server para realizar pontos de coletas de dados a cada 60s, mas se você quiser mais dados de métricas, poderá solicitar ao Metrics Server que extraia as métricas a cada 10s ou 20s. Isso lhe fornecerá mais pontos de dados de uso de recursos por período de tempo. Sinta-se livre para ajustar esta resolução para atender às suas necessidades.

      Adicione as seguintes linhas destacadas ao arquivo:

      metrics-server Configuration File

      . . .
        template:
          metadata:
            creationTimestamp: null
            labels:
              app: metrics-server
              release: metrics-server
          spec:
            affinity: {}
            containers:
            - command:
              - /metrics-server
              - --cert-dir=/tmp
              - --logtostderr
              - --secure-port=8443
              - --metric-resolution=60s
              - --kubelet-preferred-address-types=InternalIP
              image: gcr.io/google_containers/metrics-server-amd64:v0.3.4
              imagePullPolicy: IfNotPresent
              livenessProbe:
                failureThreshold: 3
                httpGet:
                  path: /healthz
      . . .
      

      Após a adição da flag, salve e saia do seu editor.

      Para verificar se o Metrics Server está em execução, use o kubectl top pod após alguns minutos. Como antes, este comando nos fornecerá o uso de recursos em um nível de pod. Dessa vez, um Metrics Server funcionando permitirá que você veja as métricas em cada pod:

      Isso fornecerá a seguinte saída, com o seu pod do Metrics Server em execução:

      Output

      NAME CPU(cores) MEMORY(bytes) metrics-server-db745fcd5-v8gv6 3m 12Mi web-555db5bf6b-f7btr 0m 2Mi

      Agora você tem um Metrics Server funcional e pode visualizar e monitorar o uso de recursos de pods em seu cluster. Em seguida, você irá configurar o HPA para monitorar esses dados e reagir a períodos de alto uso da CPU.

      Passo 4 — Criando e Validando o Autoscaler Horizontal de Pod

      Por fim, é hora de criar o Horizontal Pod Autoscaler (HPA) para seu deployment. O HPA é o objeto real do Kubernetes que verifica rotineiramente os dados de uso de CPU coletados do Metrics Server e escala seu deployment com base nos limites que você definiu no Passo 2.

      Crie o HPA usando o comando kubectl autoscale:

      • kubectl autoscale deployment web --max=4 --cpu-percent=80

      Este comando cria o HPA para seu deployment web. Ele também usa a flag --max para definir o máximo de réplicas nas quais web pode ser escalado, o que, neste caso, você define como 4.

      A flag --cpu-percent informa ao HPA em qual porcentagem de uso do limite que você definiu no Passo 2 você deseja que o autoscale ocorra. Isso também usa os requests para ajudar a provisionar os pods escalados para um node que possa acomodar a alocação inicial de recursos. Neste exemplo, se o limite que você definiu para o seu deployment no Passo 1 fosse 100 milicores (100m), esse comando dispararia um autoscale assim que o pod atingisse 80m no uso médio da CPU. Isso permitiria que o deployment fosse escalado automaticamente antes de estourar seus recursos de CPU.

      Agora que seu deployment pode ser escalado automaticamente, é hora de testar isso.

      Para validar, você irá gerar uma carga que colocará seu cluster acima do seu limite e assistirá o autoscaler assumir o controle. Para começar, abra um segundo terminal para observar os pods provisionados no momento e atualizar a lista de pods a cada 2 segundos. Para fazer isso, use o comando watch neste segundo terminal:

      O comando watch emite o comando dado como argumento continuamente, exibindo a saída no seu terminal. A duração entre repetições pode ser configurada mais finamente com a flag -n. Para os fins deste tutorial, a configuração padrão de dois segundos será suficiente.

      O terminal agora exibirá a saída do kubectl top pods inicialmente e, a cada 2 segundos, atualizará a saída que esse comando gera, que será semelhante a esta:

      Output

      Every 2.0s: kubectl top pods NAME CPU(cores) MEMORY(bytes) metrics-server-6fd5457684-7kqtz 3m 15Mi web-7476bb659d-q5bjv 0m 2Mi

      Anote o número de pods atualmente deployados para o web.

      Volte ao seu terminal original. Agora você abrirá um terminal dentro do seu pod web atual usando kubectl exec e criará uma carga artificial. Você pode fazer isso entrando no pod e instalando o stress CLI tool.

      Digite seu pod usando kubectl exec, substituindo o nome do pod realçado pelo nome do seu pod web:

      • kubectl exec -it web-f765fd676-s9729 /bin/bash

      Este comando é muito semelhante em conceito ao de usar ssh para efetuar login em outra máquina. O /bin/bash estabelece um shell bash no seu pod.

      Em seguida, no shell bash dentro do seu pod, atualize os metadados do repositório e instale o pacote stress.

      • apt update; apt-get install -y stress

      Nota: Para containers baseados no CentOS, isso seria assim:

      Em seguida, gere alguma carga de CPU no seu pod usando o comando stress e deixe-o executar:

      Agora, volte ao seu comando watch no segundo terminal. Aguarde alguns minutos para o Metrics Server reunir dados de CPU acima do limite definido pelo HPA. Observe que as métricas por padrão são coletadas na taxa que você definir como --metric-resolution ao configurar o metrics server. Pode demorar um minuto para que as métricas de uso sejam atualizadas.

      Após cerca de dois minutos, você verá pods adicionais web subindo:

      Output

      Every 2.0s: kubectl top pods NAME CPU(cores) MEMORY(bytes) metrics-server-db745fcd5-v8gv6 6m 16Mi web-555db5bf6b-ck98q 0m 2Mi web-555db5bf6b-f7btr 494m 21Mi web-555db5bf6b-h5cbx 0m 1Mi web-555db5bf6b-pvh9f 0m 2Mi

      Agora você pode ver que o HPA provisionou novos pods com base na carga de CPU coletada pelo Metrics Server. Quando estiver satisfeito com esta validação, use CTRL+C para interromper o comando stress no seu primeiro terminal e então, saia do shell bash do seu pod.

      Conclusão

      Neste artigo, você criou um deployment que será escalado automaticamente com base na carga de CPU. Você adicionou limites de recursos e solicitações de CPU ao seu deployment, instalou e configurou o Metrics Server em seu cluster por meio do uso do Helm e criou um HPA para tomar decisões de escalabilidade.

      Esse foi um deployment de demonstração tanto do Metrics Server quanto do HPA. Agora você pode ajustar a configuração para se adequar aos seus casos de uso específicos. Certifique-se de verificar a documentação do Kubernetes HPA para ajuda e informação sobre requests e limits. Além disso, confira o Projeto Metrics Server para ver todas as configurações ajustáveis que podem ser aplicadas ao seu caso de uso.

      Se você gostaria de fazer mais com o Kubernetes, visite nossa Página da Comunidade Kubernetes ou explore nosso Serviço Gerenciado de Kubernetes.



      Source link