Dockerización de aplicaciones Python

    Introducción

    Docker es una herramienta ampliamente aceptada y utilizada por las principales empresas de TI para crear, gestionar y proteger sus aplicaciones.

    Los contenedores, como Docker, permiten a los desarrolladores aislar y ejecutar múltiples aplicaciones en un solo sistema operativo, en lugar de dedicar una máquina virtual para cada aplicación en el servidor. El uso de estos contenedores más livianos conduce a costos más bajos, mejor uso de recursos y mayor rendimiento.

    Si está interesado en leer más, debería echar un vistazo a Docker: una introducción de alto nivel.

    En este artículo, escribiremos una aplicación web Python simple usando Flask y la prepararemos para «dockerizar», seguido de la creación de una imagen de Docker y su implementación en un entorno de prueba y producción.

    Nota : Este tutorial asume que tiene Docker instalado en su máquina. De lo contrario, puede seguir la Guía de instalación oficial de Docker .

    ¿Qué es Docker?

    Docker es una herramienta que permite a los desarrolladores enviar sus aplicaciones (junto con bibliotecas y otras dependencias), lo que garantiza que puedan ejecutarse exactamente con la misma configuración, independientemente del entorno en el que estén implementadas.

    Esto se hace aislando las aplicaciones en contenedores individuales que, aunque separados por contenedores, comparten el sistema operativo y las bibliotecas adecuadas.

    Docker se puede dividir en:

    • Docker Engine : una herramienta de empaquetado de software utilizada para contener aplicaciones.
    • Docker Hub : una herramienta para administrar sus aplicaciones de contenedor en la nube.

    ¿Por qué contenedores?

    Es importante comprender la importancia y la utilidad de los contenedores. Aunque es posible que no marquen una gran diferencia con una sola aplicación implementada en el servidor o en proyectos domésticos, los contenedores pueden salvar la vida cuando se trata de aplicaciones robustas y con muchos recursos, especialmente si comparten el mismo servidor o si se implementan en muchos entornos diferentes.

    Esto se resolvió en primer lugar con máquinas virtuales como VMWare e hipervisores , aunque se ha demostrado que no son óptimas en cuanto a eficiencia, velocidad y portabilidad.

    Te puede interesar:Vue-Router: Navegación por aplicaciones de Vue.js

    Los contenedores Docker son alternativas ligeras a las máquinas virtuales; a diferencia de las máquinas virtuales, no necesitamos preasignar RAM, CPU u otros recursos para ellos y no necesitamos iniciar una nueva máquina virtual para todas y cada una de las aplicaciones, ya que estamos trabajando con un solo sistema operativo.

    Los desarrolladores no necesitan cargarse con el envío de versiones especiales de software para diferentes entornos y pueden concentrarse en crear la lógica empresarial central detrás de la aplicación.

    Configuración del proyecto

    Flask es un micro-framework de Python que se utiliza para crear aplicaciones web simples y avanzadas. Debido a su facilidad de uso y configuración, lo usaremos para nuestra aplicación de demostración.

    Si aún no tiene Flask instalado, es fácil hacerlo con un solo comando:

    $ pip install flask
    

    Después de instalar Flask, cree una carpeta de proyecto, nombrada FlaskApppara un ejemplo. En esta carpeta, cree un archivo base, llamado algo así como app.py.

    Dentro de app.pyimportar el Flaskmódulo y crear una aplicación web usando lo siguiente:

    from flask import Flask
    
    app = Flask(__name__)`
    

    A continuación, definamos la ruta básica /y el controlador de solicitud correspondiente:

    @app.route("/")
    def index():
      return """
      <h1>Python Flask in Docker!</h1>
      <p>A sample web-app for running Flask inside Docker.</p>
      """
    

    Finalmente, iniciemos la aplicación si se invoca el script como programa principal:

    if __name__ == "__main__":
        app.run(debug=True, host="0.0.0.0")
    
    $ python3 app.py
    

    Navegue su navegador a http://localhost:5000/. ¡Debería aparecer el mensaje «Dockerzing Python app using Flask»!

    Dockerizar la aplicación

    Para ejecutar una aplicación con Docker, tenemos que construir un contenedor con todas las dependencias utilizadas en él, que en nuestro caso es solo Flask. Para hacer esto, incluiremos un requirements.txtarchivo que contiene las dependencias requeridas y crearemos un Dockerfile que se basa en el archivo para construir una imagen.

    Te puede interesar:Presentamos Camo: un ODM ES6 basado en clases para bases de datos similares a Mongo

    Además, cuando lancemos el contenedor, tendremos que tener acceso a los puertos HTTP en los que se ejecuta la aplicación.

    Preparando la aplicación

    Incluir dependencias en el requirements.txtarchivo es muy fácil. Simplemente necesitamos incluir el nombre y la versión de la dependencia:

    Flask==1.0.2
    

    A continuación, debemos asegurarnos de que todos los archivos de Python necesarios para que se ejecute nuestra aplicación estén dentro de una carpeta de nivel superior, por ejemplo, llamada app.

    También se recomienda nombrar el punto de entrada principal, app.pyya que es una buena práctica nombrar el objeto Flask creado en el script apppara facilitar la implementación.

    docker-flask-tutorial
        ├── requirements.txt
        ├── Dockerfile
        └── app
            └── app.py
            └── <other .py files>
    

    Creando un Dockerfile

    Un Dockerfile es esencialmente un archivo de texto con instrucciones claramente definidas sobre cómo crear una imagen de Docker para nuestro proyecto.

    A continuación, crearemos una imagen de Docker basada en Ubuntu 16.04 y Python 3.X:

    FROM ubuntu:16.04
    
    MAINTAINER Madhuri Koushik "[email protected]"
    
    RUN apt-get update -y && 
        apt-get install -y python3-pip python3-dev
    
    COPY ./requirements.txt /requirements.txt
    
    WORKDIR /
    
    RUN pip3 install -r requirements.txt
    
    COPY . /
    
    ENTRYPOINT [ "python3" ]
    
    CMD [ "app/app.py" ]
    

    Aquí hay algunos comandos que merecen una explicación adecuada:

    • DE : cada Dockerfile comienza con una FROMpalabra clave. Se utiliza para especificar la imagen base a partir de la cual se construye la imagen. La siguiente línea proporciona metadatos sobre el mantenedor de la imagen.
    • EJECUTAR : podemos agregar contenido adicional a la imagen ejecutando tareas de instalación y almacenando los resultados de estos comandos. Aquí, simplemente actualizamos la información del paquete, instalamos python3y pip. Usamos pipen el segundo RUNcomando para instalar todos los paquetes en el requirements.txtarchivo.
    • COPY : el COPYcomando se usa para copiar archivos / directorios desde la máquina host al contenedor durante el proceso de compilación. En este caso, estamos copiando los archivos de la aplicación incluidos requirements.txt.
    • WORKDIR : establece el directorio de trabajo en el contenedor que utilizan RUN, COPY, etc.
    • ENTRYPOINT : define el punto de entrada de la aplicación
    • CMD : ejecuta el app.pyarchivo en el appdirectorio.

    Cómo se crean las imágenes de Docker

    Las imágenes de Docker se crean mediante el docker buildcomando. Al construir una imagen, Docker crea las llamadas «capas». Cada capa registra los cambios resultantes de un comando en el Dockerfile y el estado de la imagen después de ejecutar el comando.

    Docker almacena en caché internamente estas capas para que, al reconstruir imágenes, necesite volver a crear solo aquellas capas que han cambiado. Por ejemplo, una vez que carga la imagen base para ubuntu:16.04, todas las compilaciones posteriores del mismo contenedor pueden reutilizar esto, ya que esto no cambiará. Sin embargo, durante cada reconstrucción, es probable que el contenido del directorio de la aplicación sea diferente y, por lo tanto, esta capa se reconstruirá cada vez.

    Siempre que se reconstruye una capa, todas las capas que le siguen en el Dockerfile también deben reconstruirse. Es importante tener esto en cuenta al crear Dockerfiles. Por ejemplo, primero instalamos COPYel requirements.txtarchivo e instalamos las dependencias antes COPYde instalar el resto de la aplicación. Esto da como resultado una capa de Docker que contiene todas las dependencias. No es necesario reconstruir esta capa incluso si otros archivos de la aplicación cambian, siempre que no haya nuevas dependencias.

    Te puede interesar:Copia de seguridad y restauración de bases de datos de PostgreSQL

    Por lo tanto, optimizamos el proceso de compilación de nuestro contenedor separando pip installla implementación del resto de nuestra aplicación.

    Construyendo la imagen de Docker

    Ahora que nuestro Dockerfile está listo y entendemos cómo funciona el proceso de compilación, sigamos adelante y creemos la imagen de Docker para nuestra aplicación:

    $ docker build -t docker-flask:latest .
    

    Ejecución de la aplicación en modo de depuración con reinicio automático

    Debido a las ventajas de la contenerización descritas anteriormente, tiene sentido desarrollar aplicaciones que se implementarán en contenedores dentro del propio contenedor. Esto asegura que desde el principio, el entorno en el que se construye la aplicación esté limpio y, por lo tanto, elimine las sorpresas durante la entrega.

    Sin embargo, mientras se desarrolla una aplicación, es importante tener ciclos rápidos de reconstrucción y prueba para verificar cada paso intermedio durante el desarrollo. Para este propósito, los desarrolladores de aplicaciones web dependen de las funciones de reinicio automático proporcionadas por marcos como Flask. También es posible aprovechar esto desde dentro del contenedor.

    Para habilitar el reinicio automático, iniciamos el contenedor Docker mapeando nuestro directorio de desarrollo al directorio de la aplicación dentro del contenedor. Esto significa que Flask observará los archivos en el host (a través de esta asignación) para detectar cualquier cambio y reiniciará la aplicación automáticamente cuando detecte algún cambio.

    Además, también necesitamos reenviar los puertos de la aplicación desde el contenedor al host. Esto es para permitir que un navegador que se ejecuta en el host acceda a la aplicación.

    Para lograr esto, iniciamos el contenedor Docker con opciones de mapeo de volumen y reenvío de puertos:

    $ docker run --name flaskapp -v$PWD/app:/app -p5000:5000 docker-flask:latest
    

    Esto hace lo siguiente:

    • Inicia un contenedor basado en la docker-flaskimagen que construimos previamente.
    • El nombre de este contenedor se establece en flaskapp. Sin la --nameopción, Docker elige un nombre arbitrario (y muy interesante) para el contenedor. Especificar un nombre explícitamente nos ayudará a localizar el contenedor (para detener, etc.)
    • La -vopción monta la carpeta de la aplicación en el host en el contenedor.
    • La -popción asigna el puerto del contenedor al host.

    Ahora se puede acceder a la aplicación en http://localhost:5000o http://0.0.0.0:5000/:

    Si realizamos cambios en la aplicación cuando el contenedor se está ejecutando y guardamos el archivo, Flask detecta los cambios y reinicia la aplicación:

    Te puede interesar:Automatización de confirmaciones de control de versiones

    Para detener el contenedor, presione Ctrl-C y retire el contenedor ejecutando docker rm flaskapp.

    Ejecución de la aplicación en modo de producción

    Si bien ejecutar la aplicación con Flask directamente es lo suficientemente bueno para el desarrollo, necesitamos utilizar un método de implementación más sólido para la producción.

    Normalmente, una aplicación web Flask en producción puede necesitar manejar múltiples conexiones paralelas y, por lo tanto, generalmente se implementa en un servidor web compatible con WSGI.

    Una alternativa popular es nginx + uwsgi y en esta sección veremos cómo configurar nuestra aplicación web para producción. Nginx es un servidor web de código abierto y uWSGI es un «servidor de contenedores de aplicaciones rápido y con recuperación automática».

    Primero, creamos una fachada que iniciará nuestra aplicación en modo de desarrollo o de producción y, dependiendo del modo, elegirá ejecutar nginx o Python directamente.

    Llamaremos a este archivo launch.shy será un simple script de shell. Este archivo se basa en entry-point.sh :

    #!/bin/bash
    
    if [ ! -f /debug0 ]; then
      touch /debug0
    
      while getopts 'hd:' flag; do
        case "${flag}" in
          h)
            echo "options:"
            echo "-h        show brief help"
            echo "-d        debug mode, no nginx or uwsgi, direct start with 'python3 app/app.py'"
            exit 0
            ;;
          d)
            touch /debug1
            ;;
          *)
            break
            ;;
        esac
      done
    fi
    
    if [ -e /debug1 ]; then
      echo "Running app in debug mode!"
      python3 app/app.py
    else
      echo "Running app in production mode!"
      nginx && uwsgi --ini /app.ini
    fi
    

    A continuación, creamos un archivo de configuración de uWSGI para nuestra aplicación y una configuración de nginx .

    Básicamente, este archivo describe el punto de entrada de nuestra aplicación a uWSGI / nginx:

    [uwsgi]
    plugins = /usr/lib/uwsgi/plugins/python3
    chdir = /app
    module = app:app
    uid = nginx
    gid = nginx
    socket = /run/uwsgiApp.sock
    pidfile = /run/.pid
    processes = 4
    threads = 2
    

    Finalmente, modificamos nuestro Dockerfile para incluir nginx y uWSGI. Además de instalar nginx, uWSGI y el complemento uWSGI Python3, ahora también copia el nginx.confen la ubicación adecuada y configura los permisos de usuario necesarios para ejecutar nginx.

    Además, el Dockerfile ENTRYPOINTestá configurado para el script de shell que nos ayuda a ejecutar el contenedor en modo de depuración o producción:

    Te puede interesar:Monitoreo de cambios de datos usando un HIDS
    FROM ubuntu:16.04
    
    MAINTAINER Madhuri Koushik "[email protected]"
    
    RUN apt-get update -y && 
        apt-get install -y python3-pip python3-dev && 
        apt-get install -y nginx uwsgi uwsgi-plugin-python3
    
    COPY ./requirements.txt /requirements.txt
    COPY ./nginx.conf /etc/nginx/nginx.conf
    
    WORKDIR /
    
    RUN pip3 install -r requirements.txt
    
    COPY . /
    
    RUN adduser --disabled-password --gecos '' nginx
      && chown -R nginx:nginx /app 
      && chmod 777 /run/ -R 
      && chmod 777 /root/ -R
    
    ENTRYPOINT [ "/bin/bash", "/launcher.sh"]
    

    Ahora, podemos reconstruir la imagen:

    $ docker build -t docker-flask:latest .
    

    Y ejecute la aplicación usando nginx:

    $ docker run -d --name flaskapp --restart=always -p 80:80 docker-flask:latest
    

    Esta imagen es autónoma y solo necesita que se especifique la asignación de puertos durante la implementación. Esto iniciará y ejecutará el comando en segundo plano. Para detener y eliminar este contenedor, ejecute el siguiente comando:

    $ docker stop flaskapp && docker rm flaskapp
    

    Además, si necesitamos depurar o agregar funciones, podemos ejecutar fácilmente el contenedor en modo de depuración montando nuestra propia versión del árbol de fuentes:

    $ docker run -it --name flaskapp -p 5000:5000 -v$PWD/app:/app docker-flask:latest -d
    

    Gestión de dependencias externas

    Al enviar aplicaciones como contenedores, un elemento clave a recordar es que se incrementan las responsabilidades del desarrollador hacia la gestión de dependencias. Además de identificar y especificar las dependencias y versiones correctas, también son responsables de la instalación y configuración de estas dependencias en el entorno del contenedor.

    Afortunadamente, requirements.txtes un mecanismo sencillo para especificar dependencias. Se pippuede agregar cualquier paquete que esté disponible a través de .

    Pero nuevamente, cada vez requirements.txtque se modifica el archivo, la imagen de Docker debe reconstruirse.

    Instalación de dependencias al inicio

    Ocasionalmente, es posible que sea necesario instalar dependencias adicionales durante el inicio. Digamos que está probando un nuevo paquete durante el desarrollo y no desea reconstruir la imagen de Docker cada vez o desea utilizar la última versión disponible en el momento del lanzamiento. Es posible lograr esto modificando el lanzador para que se ejecute pipal inicio del inicio de la aplicación.

    De manera similar, también podemos instalar dependencias de paquetes adicionales a nivel de sistema operativo. Modifiquemos el launcher.sh:

    #!/bin/bash
    
    if [ ! -f /debug0 ]; then
        touch /debug0
    
        if [ -e requirements_os.txt ]; then
            apt-get install -y $(cat requirements_os.txt)
        fi
        if [ -e requirements.txt ]; then
            pip3 install -r requirements.txt
        fi
    
        while getopts 'hd' flag; do
            case "${flag}" in
                h)
                    echo "options:"
                    echo "-h        show brief help"
                    echo "-d        debug mode, no nginx or uwsgi, direct start with 'python3 app/app.py'"
                    exit 0
                    ;;
                d)
                    echo "Debug!"
                    touch /debug1
                    ;;
            esac
        done
    fi
    
    if [ -e /debug1 ]; then
        echo "Running app in debug mode!"
        python3 app/app.py
    else
        echo "Running app in production mode!"
        nginx && uwsgi --ini /app.ini
    fi
    

    Ahora, en el requirements_os.txt, podemos especificar una lista de nombres de paquetes separados por espacios en una línea y estos, junto con los paquetes requirements.txt, se instalarán antes de que se inicie la aplicación.

    Te puede interesar:Ejecución de SQL en datos CSV: conversión y extracción de datos

    Aunque esto se proporciona para su comodidad durante el desarrollo, no es una buena práctica instalar dependencias durante el inicio por varias razones:

    • Derrota uno de los objetivos de la contenedorización que es arreglar y probar las dependencias que no cambian debido al cambio del entorno de implementación.
    • Agrega una sobrecarga adicional al inicio de la aplicación, lo que aumentará el tiempo de inicio del contenedor.
    • Extraer dependencias cada vez que se inicia la aplicación es un mal uso de los recursos de red.

    Conclusión

    En este artículo, nos sumergimos en Docker, una herramienta de contenedorización ampliamente utilizada. Creamos una aplicación web simple con Flask, una imagen Docker personalizada basada en Ubuntu para ejecutar nuestra aplicación web en modo de desarrollo y producción.

    Finalmente, configuramos la implementación para nuestra aplicación web usando nginx y uWSGI dentro del contenedor Docker y exploramos métodos para instalar dependencias externas.

    La contenerización es una tecnología poderosa que permite el desarrollo y la implementación rápidos de aplicaciones en la nube y esperamos que pueda aplicar lo que aprendió aquí en sus propias aplicaciones.

     

    Rate this post

    Etiquetas: