https://d226lax1qjow5r.cloudfront.net/blog/blogposts/dockerize-python-queue-manager-project-for-easy-deployment-dr/E_Python-Queue-Manager_1200x600.png

Dockerize Python Queue Manager Project para un fácil despliegue

Publicado el April 28, 2021

Tiempo de lectura: 8 minutos

El mes pasado, te mostré cómo crear una aplicación de gestión de colas basada en SMS con Python y Flask. aplicación de gestión de colas basada en SMS con Python y Flask. Esa aplicación fue genial para mostrar un ejemplo básico del uso de la Nexmo SMS API, pero en realidad sólo era bueno para la creación de prototipos y hacer el desarrollo local. En este post, voy a caminar a través de algunos pasos que puede tomar para hacer que la aplicación más listo para la producción, con el resultado final de ser un Dockerizado de la aplicación que se puede implementar directamente en Heroku.

Por el camino, hablaremos un poco de:

  • Buenas prácticas para la gestión de secretos

  • Contenedores y su utilidad

  • Trabajar con un servidor de aplicaciones capaz de gestionar el tráfico de producción

Requisitos previos

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

Elija un punto de partida

Si has seguido mi primer post sobre la creación de una aplicación de gestión de colas, todo lo que necesitas para empezar con esta es cubrir todos los requisitos previos enumerados anteriormente. Si te acabas de unir, puedes clonar la aplicación terminada de antes:

git clone https://github.com/nexmo-community/sms-queue-notify.git

También puede empezar con la versión versión terminada y Dockerizada del proyecto:

git clone https://github.com/nexmo-community/docker-queue-manager.git

Muchos de los pasos de configuración y comandos de construcción en este post todavía tendrá que hacer, pero usted no tendrá que hacer ningún cambio en el código.

Empiece donde empiece, asegúrese de que se encuentra en el directorio de su proyecto:

cd sms-queue-notify

o

cd docker-queue-manager

Gestionar secretos

Anteriormente, te hice colocar tu clave Nexmo, secreto y número de teléfono directamente en la parte superior de main.py. Si bien esto funcionó para demostrar una aplicación sencilla sin un montón de piezas móviles, en general, usted querría asegurarse de que sus credenciales están separados de su código. Así que la primera modificación que haremos en main.py es leer nuestros secretos directamente de las variables de entorno. Cambie las siguientes líneas:

NEXMO_KEY = <Your Nexmo Key>
NEXMO_SECRET = <Your Nexmo Secret>
NEXMO_NUMBER = <Your Nexmo Number>

a:

NEXMO_KEY = os.environ['NEXMO_KEY']
NEXMO_SECRET = os.environ['NEXMO_SECRET']
NEXMO_NUMBER = os.environ['NEXMO_NUMBER']

A continuación, deberás asegurarte de no publicar accidentalmente tus secretos en ningún sitio público. Si aún no tienes uno, crea un archivo .gitignore y asegúrate de que tienes .env en la lista. Ya que estás, crea un .dockerignore archivo con lo siguiente:

.env
.git

Ahora puede crear un nuevo archivo llamado .env para guardar tu información sensible. El contenido del archivo debe ser:

NEXMO_KEY=<Your Nexmo Key>
NEXMO_SECRET=<Your Nexmo Secret>
NEXMO_NUMBER=<Your Nexmo Number>

Tenga en cuenta que el formato es importante aquí. No hay espacios alrededor de los signos iguales, y a diferencia de cuando tenía estos valores en main.pyno deben ir entre comillas.

Si quisieras probar tu aplicación ahora, podrías ejecutar lo siguiente en el terminal para establecer variables de entorno basadas en el archivo .env archivo:

set -o allexport
source .env
set +o allexport

Usaremos Docker para probar los cambios en nuestra aplicación, que puede leer desde el archivo .env directamente.

Cambios de configuración

Al final de main.pyverás lo siguiente:

if __name__ == '__main__':
    app.run(debug=True, threaded=True)

Esta configuración, con debug=Truees ideal para probar la aplicación, ya que permite realizar cambios sin tener que reiniciar el servidor cada vez. Este modo de depuración es sólo para fines de desarrollo, y no debe utilizarse en producción.

La otra parte de la configuración, threaded=Truese refiere a los eventos enviados por el servidor, que requieren hilos para funcionar correctamente. Como aprenderás en breve, manejaremos las peticiones con un servidor de aplicaciones separado, así que también podemos eliminar esta parte de la configuración. La configuración main.py actualizado debería tener este aspecto:

if __name__ == '__main__':
    app.run()

Crear un archivo Dockerfile

Para facilitar el despliegue de nuestro proyecto, vamos a empaquetarlo todo en un contenedor Docker. Los contenedores son una forma ligera de asegurarse de que su aplicación tiene todos los recursos que necesita para ejecutarse, incluyendo el sistema operativo y las dependencias correctas. Empaquetar nuestra aplicación como un contenedor hace que sea posible desplegarla a través de una variedad de plataformas sin tener que preocuparse de qué otros procesos, configuración y software ya existen.

Docker crea imágenes de contenedores utilizando algo llamado Dockerfile, que enumera línea por línea los pasos necesarios para crear el entorno en el que se ejecutará tu aplicación. Es una receta que le dice a Docker cómo replicar la configuración que sabes que es necesaria para que tu aplicación se ejecute correctamente. Cada línea del Dockerfile crea una capa, y cuando se reconstruye una imagen Docker sólo las capas que tienen cambios serán reconstruidas. Esto significa que usted quiere comenzar con la configuración más general en la parte superior (sistema operativo y paquetes necesarios) y trabajar hacia requisitos más específicos.

Para nuestra aplicación, vamos a empezar con un nuevo archivo llamado Dockerfile que contiene lo siguiente:

FROM python:3.6-slim-buster

COPY requirements.txt requirements.txt

RUN pip install -r requirements.txt

Esto indica a Docker que comience con una imagen base que incluya una versión ligera de Debian Buster así como la versión 3.6 de Python. Esta es una imagen base estándar que está disponible en Docker Hubun registro público de imágenes de contenedores.

Las siguientes dos líneas del Dockerfile aseguran que las dependencias requeridas para nuestra aplicación (Flask, Flask-SQLAlchemy, y el Nexmo SDK) están instaladas. A menos que se realicen cambios en requirements.txt o se utilice una imagen base diferente, estos pasos sólo tendrán que hacerse una vez. Construcciones posteriores (para cosas como cambios en el código) pueden utilizar estas capas ya existentes.

Servidor de aplicaciones de producción

Flask tiene un servidor web incorporado para fines de prueba, que es lo que usamos en el post anterior. Este servidor no está diseñado para su uso en producción, está pensado principalmente para manejar una solicitud a la vez, y no va a escalar para manejar el tráfico esperado de una aplicación de producción.

En la producción, usted quiere asegurarse de que tiene un servidor web dedicado y un servidor de aplicaciones separado que maneje la comunicación con tu aplicación Python. Dos opciones comunes son Nginx y Gunicorn. Como estamos planeando desplegar a Heroku, que proporciona un servidor web para usted, sólo tendremos que incluir Gunicorn (así como gevent para manejar los hilos). Añade lo siguiente a tu Dockerfile:

RUN pip install gunicorn gevent

Configuración de la base de datos

Si recuerdas del primer postcreamos nuestra base de datos usando algunos comandos de Python directamente en la línea de comandos. Esto no funciona tan bien cuando estás desplegando tu aplicación en un contenedor, ya que no quieres estar haciendo pasos manuales para configurar tu entorno. Para ayudar a mantener las cosas automatizadas, crea un nuevo archivo create_db.py con el siguiente aspecto:

from main import db
db.create_all()

Ha sido fácil. Ahora podemos terminar nuestro Dockerfile con lo siguiente:

COPY . /app

WORKDIR /app

CMD python create_db.py && gunicorn -k gevent -b 0.0.0.0:$PORT main:app

Estos últimos pasos copian el contenido del directorio de su proyecto a una carpeta llamada /app en el contenedor, que luego se establece como el directorio de trabajo. La última línea le dice al contenedor qué comandos debe ejecutar cuando se inicia: en primer lugar crear la base de datos, y cuando esto se hace poner en marcha un servidor gunicorn para ejecutar nuestra aplicación. La variable de entorno $PORT es establecida por Heroku cuando el contenedor se ejecuta.

Pruebas locales con Docker

Ahora que el Dockerfile está completo, es fácil probar que todo funciona localmente. En primer lugar, asegúrate de que Docker se está ejecutando en tu ordenador. A continuación, ejecute lo siguiente en el directorio del proyecto para construir una imagen Docker, utilizando --tag para establecer un nombre fácil de referenciar:

docker build --tag queue_app .

Si la compilación se realiza correctamente, ya puede ejecutar el contenedor:

docker run -d -p 5000:5000 --env-file .env -e PORT=5000 queue_app

Observe que estamos cargando nuestros secretos desde el archivo .env y configurando la variable de entorno PORT la variable de entorno.

Una vez que el contenedor se esté ejecutando, deberías poder abrir un navegador, ir a localhost:5000y ver tu aplicación.

Todavía hay un paso más si quieres probar completamente tu aplicación. En el post anterior configuraste ngrok para que tu aplicación fuera accesible a través de la web. Tendrás que hacer esto de nuevo si quieres probar el envío de un mensaje SMS a la aplicación. Abre una nueva ventana de terminal y ejecuta lo siguiente:

ngrok http 5000

A continuación, asegúrese de ir al panel de Nexmo y copiar la URL de reenvío en la configuración de su número en el campo Inbound Webhook URL así: https://<your ngrok ID>.ngrok.io/webhooks/inbound-sms (ver post anterior para más detalles).

Ahora deberías poder interactuar con tu aplicación a través de texto como antes. Sólo que ahora, si detienes tu contenedor y lo reinicias, verás que la base de datos se reiniciará por completo.

Base de datos Postgres

En nuestra versión de desarrollo de la aplicación, utilizamos un archivo SQLite para almacenar información sobre quién estaba esperando en la cola. SQLite creaba la base de datos como un archivo en el directorio del proyecto, lo que facilitaba la configuración. En una configuración basada en contenedores esto no funciona, ya que el sistema de archivos del contenedor no persistirá si el contenedor necesita ser reiniciado. También hace que sea difícil escalar la aplicación a través de múltiples contenedores, ya que no hay fuente de datos compartida.

Por suerte, utilizamos Flask-SQLAlchemy para abstraer los detalles de la base de datos de nuestro código, por lo que el cambio de nuestro SQLite para un Heroku-proporcionado Postgres es increíblemente simple. La base de datos Postgres se encuentra fuera del contenedor, por lo que persistirá incluso cuando el contenedor se reinicie y puede ser accedida por múltiples contenedores.

Cuando Heroku crea una base de datos Postgres, la url de la base de datos se almacena en la variable de entorno DATABASE_URL variable de entorno. El único cambio que necesitamos hacer en nuestro código para cambiar de SQLite a la base de datos Postgres de Heroku es reemplazar esta línea en main.py:

db_path = "sqlite:///queue.db"

con esto:

db_path = os.environ['DATABASE_URL']

Entonces tenemos que actualizar la línea de nuestro Dockerfile que dice:

RUN pip install gunicorn gevent

que decir:

RUN pip install gunicorn gevent psycopg2-binary

El paquete psycopg2 es un adaptador de base de datos Postgres hecho específicamente para Python.

El último paso es crear una base de datos Postgres en Heroku, lo que requiere que primero inicies sesión en la CLI de Heroku y crees una aplicación Heroku:

heroku login
heroku create <your application name>

A continuación, cree la base de datos, asegurándose de incluir el nombre de su aplicación:

heroku addons:create heroku-postgresql:hobby-dev -a <your application name>

Despliegue su contenedor en Heroku

Con tu aplicación y base de datos inicializadas en Heroku, sólo hay unos pocos pasos más necesarios para desplegar tu aplicación Dockerizada. En primer lugar, querrás establecer tus credenciales de Nexmo como Heroku Config Vars, lo que se hace en el panel de control de Heroku en "Configuración":

Config Vars interface in HerokuConfig Vars interface in Heroku

A continuación, deberá reconstruir su contenedor Docker para asegurarse de que recoge los cambios recientes:

docker build --tag queue_app .

A continuación, tendrás que iniciar sesión en el registro de contenedores de Heroku:

heroku container:login

Y finalmente, empujarás y liberarás tu contenedor en Heroku:

heroku container:push web -a <your application name>
heroku container:release web -a <your application name>

¡Ya está! Bueno, casi. Lanza tu aplicación desde tu panel de control de Heroku para asegurarte de que funciona, luego ve a tu panel de control de Nexmo y actualiza el campo de tu número Inbound Webhook URL para que se vea así: https://<your application name>.herokuapp.com/webhooks/inbound-sms.

¡Lo ha conseguido! Con el poder de la contenedorización, ahora tiene una aplicación lista para la producción que es fácilmente escalable y replicable.

Si te encuentras con algún problema o tienes alguna pregunta, ponte en contacto con nosotros en nuestra Comunidad Slack. Gracias por leernos.

Compartir:

https://a.storyblok.com/f/270183/384x384/fdfdc77b6d/zachwalchuk.png
Zach WalchukAntiguos alumnos de Vonage

Zach es un antiguo Educador de Desarrolladores en Vonage