Cargando...

Python, Flask y MongoDB con Docker Compose

Aprende cómo ejecutar tu aplicación en Flask conectado con el sistema gestor de base de datos (SGDB) MongoDB, sobre uno de los lenguajes de programación más demandado en el mercado, como lo es, Python, automatizando dicho Stack Tecnológico con Docker Compose, teniendo en cuenta así, conceptos de microservicios.

Crear un Tech Stack con Docker puede parecer algo complejo al principio, porque puede tomar un tiempo considerable al estar realizando múltiples pruebas para que la misma funcione. Este es un documento donde procura ayudarte a comprender cómo se relacionan Python y Flask; MongoDB y Python (y el mismo Flask respectivamente). Además, busca que tengas un proyecto a modo de plantilla (esqueleto) del que puedas usar para tus proyectos creando tus aplicaciones con Flask y MongoDB como SGBD. Todo esto, por supuesto, utilizando Docker Compose, automatizando la instalación de los mismo, así como configuraciones relevantes a los mismo como la conexión y credenciales a los servers que se crean con Flask y MongoDB. También compartiendo un conjunto de instrucciones para que puedas valerte por ti mismo en cuanto desees arrancar o destruir el proyecto cuando sea necesario.

Descargar el código fuente

Se descarga el repositorio Python-Flask-MongoDB-with-Docker-Compose  en GitHub. Este repositorio recoge las configuraciones Docker necesarias para levantar el Tech Stack con Docker-Compose. En estos casos, te invito a que, si deseas participar en la mejora del mismo, bienvenido sea tu aporte al repositorio, que también es tuyo.

Clonas el repositorio por medio de Git.

$ git clone https://github.com/jersonmartinez/Python-Flask-MongoDB-with-Docker-Compose

Si te encuentras con algunas dificultades sobre qué es Git y cómo funciona, te dejo este recurso que he escrito para ti, para que logres comprender con mayor calidez, de qué va todo. Además te servirá por si deseas Mejorar la productividad de tu empresa con Git.

Arrancar el proyecto con Docker Compose

Para poner en marcha la instalación de la infraestructura y que los contenedores sean creados, es tan sencillo como ir a la terminal donde puedas correr Docker y ubicarse en el repositorio "Python-Flask-MongoDB-with-Docker-Compose" que anteriormente se ha clonado y lanzar la siguiente instrucción:

$ docker-compose up -d
...
Creating cs_mongodb ... done
Creating cs_api     ... done

Esta instrucción crea la red, volumen, descarga las imágenes docker, construye los contenedores de acuerdo a la configuración otorgada desde el fichero docker-compose.yml y Dockerfile. Como resultado final, se obtienen los dos contenedores que se manejan para esta práctica.

Verificar contenedores levantados

Dos maneras por medio de comandos para verificar si los contenedores han sido creados correctamente y están levantados, la primera es consultando a Docker por medio de un "docker ps".

$ docker ps
CONTAINER ID   IMAGE                                          COMMAND                  CREATED          STATUS          PORTS                                  NAMES
d9fde48ae8ad   python-flask-mongodb-with-docker-compose_web   "flask run --host=0.…"   27 minutes ago   Up 27 minutes   0.0.0.0:8000->5000/tcp                 cs_api
e0f51f3e6e9b   mongo:latest                                   "docker-entrypoint.s…"   27 minutes ago   Up 27 minutes   0.0.0.0:27017-27019->27017-27019/tcp   cs_mongodb

La otra manera es comprobar por medio de Docker Compose, con la diferencia de docker, este solo tiene Compose como dato extra, siendo la instrucción "docker-compose ps".

$ docker-compose ps
   Name                Command             State                                      Ports
-------------------------------------------------------------------------------------------------------------------------------
cs_api       flask run --host=0.0.0.0      Up      0.0.0.0:8000->5000/tcp
cs_mongodb   docker-entrypoint.sh mongod   Up      0.0.0.0:27017->27017/tcp, 0.0.0.0:27018->27018/tcp, 0.0.0.0:27019->27019/tcp

Es un hecho que lo contenedores están corriendo, ya que docker lo anuncia en su salida a la consulta si están ejecutando procesos. Los contenedores que están corriendo son: cs_api y cs_mongodb. El primer contenedor (cs_api) contiene Python y Flask corriendo. El segundo contenedor (cs_mongodb), su nombre ya lo delata, por supuesto, contiene el SGBD MongoDB. Éstos requieren estar conectados para que desde cs_api, que es donde está alojado el servidor web corriendo Flask, pueda tener comunicación con el servidor de base de datos en el contenedor cs_mongodb.

Consultas a los servidores

Por medio de las siguientes instrucciones, se obtiene una prueba definitiva de que sí funciona la infraestructura que se ejecuta por medio de contenedores Docker. Una de las consultas se realiza por medio de la web y la otra por medio de la herramienta CURL, para tener una salida en la terminal.

Por medio de CURL.

Se realiza la primera consulta al servidor web, por medio de la dirección loopback (127.0.0.1), con el puerto 8000 que ha expuesto Docker. Se obtiene la respuesta de "Hello, Crashell!", sin embargo, este viene con un HTML incrustado, para motivos de vista web (haciendo la consulta por medio de la barra de direcciones del navegador, se aprecia este resultado). Además, en la segunda consulta, esta pregunta por el endpoint /api, donde su labor es conectarse por medio de Python / Flask, a MongoDB y preguntar si hay conexión o no.

# Connecting with Web Server.
$ curl 127.0.0.1:8000
<h1 style="background-color: #262626; color: white; padding: 20px; text-align:center;">Hello, Crashell!</h1>

# Connecting with MongoDB
$ curl 127.0.0.1:8000/api
"Connected to the MongoDB Server!"

Por medio del navegador:

Python, Flask and MongoDB with Docker Compose





Query connecting with MongoDB from Python and Flask with Docker Compose

Las pruebas de que todo funciona, están plasmadas de dos maneras. Una respuesta extraída desde CURL y la otra desde el navegador.

Explicación de estructura del proyecto

La estructura del proyecto está conformada por algunos ficheros de configuración de credenciales, donde se almacenan variables de entorno como el .env y el fichero mongo-init.js, que define un nuevo usuario y roles del mismo en MongoDB. Además de los ficheros de configuración de los contenedores como docker-compose.yml y app/Dockerfile. Por otro lado, está un algoritmo en Python, usando el Framework Flask app/app.py y el fichero app/requirements.txt que anuncia los requerimientos en cuanto a librerías.

Python-Flask-MongoDB-with-Docker-Compose
│   .env
│   docker-compose.yml
│   mongo-init.js
│
├───app
│   │   app.py
│   │   Dockerfile
│   │   requirements.txt
│
└───mongo-volume

Configuración de credenciales

En el fichero .env (environment) se definen las variables de entorno que se ocupan desde el docker-compose.yml. Las variables son las siguientes:

  • WEB_HOST
    • Nombre del contenedor que sirve de servidor web.
  • MONGO_HOST
    • Nombre del host MongoDB.
  • MONGO_PORT
    • Puerto que de salida, que docker expone del contenedor MongoDB.
  • MONGO_USER
    • Nombre de usuario del MongoDB.
  • MONGO_PASS
    • Contraseña asignada al nuevo usuario MongoDB.
  • MONGO_DB
    • Nombre de la base de datos que se crea para el MongoDB.
WEB_HOST=cs_api

MONGO_HOST=cs_mongodb
MONGO_PORT=27017
MONGO_USER=root-crashell
MONGO_PASS=password-crashell
MONGO_DB=db_crashell

Definición del nuevo usuario y roles en MongoDB

Este procedimiento se lleva a cabo automatizando la creación de un nuevo usuario por medio de un script .js llamado mongo-init.js donde también se le asocian roles. El script es ejecutado en cuanto se crea el contenedor, por lo cual, al momento de acceder al contenedor, este tiene el usuario creado y configurado con sus roles, listo para ser usado.

db.createUser(
    {
        user: 'root-crashell',
        pwd: 'password-crashell',
        roles: [
            { role: "clusterMonitor", db: "admin" },
            { role: "dbOwner", db: "db_name" },
            { role: 'readWrite', db: 'db_crashell' }
        ]
    }
)

El directorio mongo-volume, es un directorio que se encuentra vacío en primera instancia, donde no ha encontrado actividad en los contenedores o dicho de otra manera, mientras los contenedores no hayan sido lanzados, no hay ningún dato ahí, pero ¿Qué guarda ese directorio? almacena específicamente datos relacionados a las colecciones, índices, métricas de diagnóstico de datos, journal y logs. Este es un directorio que sincroniza con el MongoDB y se ocupa como un volumen de datos, de este modo, se logra persistencia de los datos almacenados en dicho gestor.

Microservicios con Docker Compose

El fichero de configuración que contiene los servicios que se relacionan entre sí para un propósito en conjunto es el docker-comopose.yml. En este se encuentran las configuraciones específicas sobre cada servicio, el cómo ejecuta el servidor, por cuál dirección IP, puerto, variables de entorno, enlace, red, volumen de datos, entre otras directivas en particular.

version: '3.9'
services:
  web:
    env_file:
      - .env
    container_name: ${WEB_HOST}
    hostname: ${WEB_HOST}
    build: ./app
    entrypoint:
      - flask
      - run
      - --host=0.0.0.0
    environment:
      FLASK_DEBUG: 1
      FLASK_APP: ./app.py
      FLASK_RUN_HOST: 0.0.0.0
      TEMPLATES_AUTO_RELOAD: 'True'
      FLASK_ENV: development
    ports: 
      - '8000:5000'
    links:
      - database
    depends_on:
      - database
    volumes:
      - ./app:/app
    networks:
      - default
  database:
    image: mongo:latest
    env_file:
      - .env
    container_name: ${MONGO_HOST}
    hostname: ${MONGO_HOST}
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASS}
      MONGO_INITDB_DATABASE: ${MONGO_DB}
    volumes:
      - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
      - ./mongo-volume:/data/db
      - ./mongo-volume/log:/var/log/mongodb/
    ports:
      - '27017-27019:27017-27019'
    networks:
      - default
volumes:
  persistent:

Servicio web
Este define el servidor web, donde se instala Python y Flask, donde desde el mismo, por medio de un algoritmo en Python, se realiza la conexión al contenedor donde se encuentra el SGBD, MongoDB.

Características:

  • Incluye el fichero .env, que tiene las variables de entorno. De esta manera importa dichas variables para que, durante la ejecución del servicio, puedan ser reconocidas.
  • Se le asigna un nombre y un hostname al contenedor. Se hace notar que en este punto, empieza a invocar las variables que fueron definidas en el fichero .env.
  • Realiza una construcción apartir del Dockerfile ubicado en el directorio app/.
  • Ordena ejecutar un flask run --host=0.0.0.0.
  • Envía valores a las variables: 
    • FLASK_DEBUG (Orientando que es depurable).
    • FLASK_APP (El script .py que ejecutará como principal).
    • FLASK_RUN_HOST (Orientando la dirección 0.0.0.0).
    • TEMPLATES_AUTO_RELOAD (Actualiza el servidor automáticamente al encontrar nuevos cambios).
    • FLASK_ENV (En modo desarrollo).
  • Internamente usa el puerto 5000 y se expone al host anfitrión (el que aloja a Docker), que pueda acceder por el puerto 8000.
  • Se enlaza con el servicio database, donde este le conocerá por medio de la red interna. Además se encuentra en estado dependiente del servicio database, donde se encuentra el MongoDB, por tal razón, el primer contenedor en arrancar es el MongoDB y luego que haya iniciado, entonces continúa con el proceso de arranque del servicio web.
  • Crea su propio volumen de datos. Sincroniza el directorio app/. Este directorio es donde se encuentra el proyecto web y donde comienza la magia en el desarrollo de proyectos.
  • Comparten la misma red, por la cual, se conoce con el servicio database.
web:
  env_file:
    - .env
  container_name: ${WEB_HOST}
  hostname: ${WEB_HOST}
  build: ./app
  entrypoint:
    - flask
    - run
    - --host=0.0.0.0
  environment:
    FLASK_DEBUG: 1
    FLASK_APP: ./app.py
    FLASK_RUN_HOST: 0.0.0.0
    TEMPLATES_AUTO_RELOAD: 'True'
    FLASK_ENV: development
  ports: 
    - '8000:5000'
  links:
    - database
  depends_on:
    - database
  volumes:
    - ./app:/app
  networks:
    - default

Servicio database
Este define el servidor de base de datos, donde se instala MongoDB, con sus respectivas configuraciones, credenciales de usuario y roles asociados. Este servicio se consume por una API escrita en Python donde principalmente se valida la conexión a la misma, se pregunta por una base de datos o se verifica que el servidor no se encuentra disponible.

Características:

  • Utiliza la imagen más reciente y estable de MongoDB.
  • Incluye el fichero .env, que tiene las variables de entorno. De esta manera importa dichas variables para que durante la ejecución del servicio, puedan ser reconocidas.
  • Se le asigna un nombre y un hostname al contenedor. Se hace notar que en este punto, empieza a invocar las variables que fueron definidas en el fichero .env.
  • Envía valores a las variables: 
    • MONGO_INITDB_ROOT_USERNAME (Se asigna el valor del nombre de usuario almacenado en la variable de entorno en el fichero .env).
    • MONGO_INITDB_ROOT_PASSWORD (Se asigna el valor de la contraseña almacenada en la variable de entorno en el fichero .env).
    • MONGO_INITDB_DATABASE (Se asigna el valor de la base de datos almacenada en la variable de entorno en el fichero .env).
  • Crea un volume que sincroniza con el directorio mongo-volume/. Además, se ejecuta el script .js que crea un usuario con sus respectivas credenciales, además de una base de datos.
  • Abre los puertos en el rango de 2701[7-9] con el rango homónimo 2701[7-9] que se encuentra en el contenedor, por lo cuál, son expuestos los mismos puertos.
  • Comparten la misma red, por la cual, se conoce con el servicio web.
database:
  image: mongo:latest
  env_file:
    - .env
  container_name: ${MONGO_HOST}
  hostname: ${MONGO_HOST}
  environment:
    MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
    MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASS}
    MONGO_INITDB_DATABASE: ${MONGO_DB}
  volumes:
    - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
    - ./mongo-volume:/data/db
    - ./mongo-volume/log:/var/log/mongodb/
  ports:
    - '27017-27019:27017-27019'
  networks:
    - default

Instalación de Python y dependencias desde un Dockerfile

El fichero Dockerfile que está dentro del directorio app/, es el encargado de usar la imagen de python en su versión 3.7 alpine para generar el contenedor, donde contiuamente, instala una serie de dependencias, dentro de las cuales, existe Flask Framework.

Características:

  • Usa una imagen específica de python para crear el contenedor.
  • Dentro del contenedor, crea un directorio app/.
  • Selecciona app/ como directorio de trabajo y luego copia todo lo que hay dentro al directorio /app que existe dentro del contenedor.
  • Instala las cabeceras de Linux, el compilador GCC y otros.
  • Por medio de pip, se instala todas las dependencias existentes en el fichero requirements.txt.
  • Se expone el puerto 5000 del contenedor web. Se sabe que luego el servicio traduce de 5000 a 8000 en el docker-compose.yml.
# syntax=docker/dockerfile:1

FROM python:3.7-alpine

RUN mkdir /app
WORKDIR /app
COPY . /app

RUN apk add --no-cache gcc musl-dev linux-headers
RUN pip install -r requirements.txt

EXPOSE 5000

Las dependencias para Python que están señaladas en el fichero requirements.txt son las siguientes:

Flask==2.1.2
requests==2.25.1
xmltodict==0.13.0
pymongo==4.1.1

Algoritmo en Python utilizando Flask para conectarse al MongoDB

La parte de infraestructura ha acabado y el desarrollo comienza, por lo cual, el algoritmo que saluda al principio y muestra si hay conexión o no al MongoDB tiene las siguientes características:

  • Contiene una clase ConnectionMongoDB que se le pasan las credenciales de conexión y tiene un método getDB.
  • El método getDB realiza la conexión por medio de MongoClient (pymongo).
  • Verifica si el servidor está disponible y si la base de datos no coincide con las existentes.
  • El método get_api, que se solicita desde 127.0.0.1:8000/api, realiza una llamada a getDB y este retorna el estado.
  • El método hello lo único que hace es saludar.
import json
from flask import Flask, json, Response
from pymongo import MongoClient
from pymongo.errors import ServerSelectionTimeoutError, OperationFailure

app = Flask(__name__)

class ConnectionMongoDB:
    def __init__(self, data):
        self.server     = data['server']
        self.port       = data['port']
        self.username   = data['username']
        self.password   = data['password']
        self.db         = data['db']
        
    def getDB(self):
        mongoClient = MongoClient("mongodb://" + str(self.username) + ":" + str(self.password) + "@" + str(self.server) + ":" + str(self.port) + "/?authMechanism=DEFAULT&authSource=" + str(self.db), serverSelectionTimeoutMS=500)

        try:
            if mongoClient.admin.command('ismaster')['ismaster']:
                return "Connected to the MongoDB Server!"
        except OperationFailure:
            return ("Database not found.")
        except ServerSelectionTimeoutError:
            return ("MongoDB Server is down.")

@app.route('/api')
def get_api():
    data = {
        "server":   "cs_mongodb",
        "port":     '27017',
        "username": "root-crashell",
        "password": "password-crashell",
        "db":       "db_crashell"
    }

    response = ConnectionMongoDB(data).getDB()
    return Response(response=json.dumps(response), status=200, mimetype='application/json')

@app.route('/', methods=('GET', 'POST'))
def hello():
    return '<h1 style="background-color: #262626; color: white; padding: 20px; text-align:center;">Hello, Crashell!</h1>'

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

Hacer limpieza en los contenedores e imágenes

La experiencia dicta algunas situaciones incómodas, como que los volúmenes causan estragos por no saber eliminarlos, como el directorio __pycache__ que es creado por Python, además del mongo-volume. Eliminar la cache, recrear un servicio y mostrar los logs de la ejecución de la API con Flask.

Remover volúmenes (Cualquiera de las dos opciones son excelentes)

docker volume prune -f
docker volume rm $(docker volume ls -q)

Limpiar volúmenes y la cache generada por Python

rm -rf mongo-volume app/__pycache__/ && mkdir mongo-volume

Eliminar cache en el sistema de Docker

docker system prune -a -f && docker builder prune -a -f

Recrear un servicio, por ejemplo: web

docker-compose up --build --force-recreate --no-deps -d web

Verifca los logs del server Flask.

docker logs --tail 1000 -f cs_api

Aquellos que dominan estas últimas instrucciones. Tienen un mejor flujo de trabajo, supervisando que los problemas de este estilo, solo sean detalles a corregir.

Los objetivos están cumplidos, aprender y ejecutar; seguir aprendiendo y seguir ejecutando.




  • John Doe
    43 Sales$156,24 Totals
    62%
  • Rosy O'Dowell
    12 Leads$56,24 Totals
    32%

With supporting text below as a natural lead-in to additional content.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled.