Docker para Data Science

Como científico de datos seguro que creas modelos y código que son muy útiles, pero no sepas cómo poner en producción. Sin embargo, ese código o modelo no es del todo útil si no sabes ponerlo en producción. Para ello, una herramienta fundamental es Docker, ya que permite poner código en producción en diferentes plataformas Cloud y sea cual sea el lenguaje.

De hecho en este blog ya hemos visto (por encima) Docker en el post en el que expliqué cómo poner un modelo de R en producción. Así pues, esta vez vamos a entrar en profundidad y vamos a ver cómo puedes usar Docker para Data Science. ¡Vamos con ello!

Introducción a Docker

Qué es Docker

Imagínate que tienes una aplicación (sea en R o Python) en tu ordenador y la quieres poner en producción. La aplicación la has desarrollado en un Sistema Operativo, con un lenguaje de programación con una versión específica y unas librerías con unas versiones específicas también.

Vamos, que si quieres asegurarte de que tu aplicación funciona en el servidor, el servidor tendrá que tener exactamente lo mismo que tienes tú en tu ordenador. Sino, es posible que haya problemas de incompatibilidad.

Como comprenderás, esto es algo inasumible: un servicio Cloud no puede tener todos los Sistemas Operativos, ni todos los lenguajes ni todas sus versiones, puesto que sería algo inabarcable. Aquí es cuando Docker entra en acción.

Docker es una plataforma de software libre que te permite paquetizar y aislar tu software. Es decir, con Docker puedes crear un elemento (llamado Imagen) que incluirá todo lo necesario para ejecutar tu aplicación.

Por qué usar Docker como Data Scientist

Considerando lo anterior, gracias a Docker podrás ejecutar tu aplicación en cualquier sistema con Docker, independientemente de su sistema operativo o de que tenga, o no, el lenguaje y las librerías que necesita tu aplicación.

Esto es muy interesante sobre todo en dos situaciones:

  1. Puesta en producción de productos de ML: si quieres poner un modelo o una aplicación que use ML en producción, Docker será una forma muy clara de hacerlo. Al fin y al cabo, todas las plataformas Cloud ofrecen la posibilidad de subir una imagen Docker y desplegarla en varios de sus servicios.
  2. Creación de un entorno aislado. Muchas veces los distintos softwares pueden dar problemas. Yo por ejemplo he tenido ese problema al ejecutar Keras desde R en un Mac M1. En esos casos una opción es crear tu código y ejecutarlo dentro de una imagen Docker, de tal forma que te asegures que funciona.

Ahora ya sabes qué es Docker y por qué es importante conocerlo si te dedicas al Data Science, pero… ¿cómo funciona? ¡Veamos cómo puedes aprender a incluir tu aplicación en Docker!

Cómo Dockerizar una aplicación

Para Dockerizar una aplicación vamos a tener que seguir los siguientes pasos:

  1. Instalar Docker Desktop en tu ordenador.
  2. Crear un Dockerfile.
  3. Crear la imagen Docker a partir del Dockerfile.
  4. Lanzar tu aplicación.

Cómo ves son cuatro pasos, así que veamos el primero:

Cómo instalar Docker en tu ordenador

Instalar Docker en tu ordenador es muy sencillo. Si tienes un Windows simplemente debes ir a esta página (enlace) y descargar la aplicación. Si, en cambio, usas Mac, puedes descargad Docker desde este otro enlace.

Una vez hayas descargado e instalado la aplicación podrás acceder a ella. De momento no la vamos a usar, pero para asegurarte de que funciona, tendrás que ver algo así:

Además, otra opción para comprobar que has instalado Docker de forma correcta es abriendo una terminal. En ella ejecuta el siguiente comando:

docker version

Si docker está instalado de forma correcta verás algo como esto:

Bien, ya tienes Docker instalado. Ahora veamos qué es un Dockerfile y por qué es fundamental en Docker (ya sea para Data Science o no).

Qué es Dockerfile y cómo crearlo

Qué es un Dockerfile

Dockerfile es un fichero que indica a Docker las instrucciones a seguir para incluir todo lo necesario en la imagen: la aplicación, los programas que necesita, la instalación de las librerías, etc.

Al final y al cabo, lo que hay detrás de Docker es un sistema Linux. Por tanto, tendremos que instalar los programas que necesitemos, incluir nuestro código e indicar a Docker que lo ejecute.

Para ello, Docker cuenta con varios comandos que permiten saber qué elementos incluir en nuestra imagen. Los verbos más típicos son FROM, COPY, RUN, CMD y ENTRYPOINT. Veamos qué hacen cada uno de ellos:

Crear un Dockerfile: FROM

Este verbo indica la imagen base sobre la cual vamos a partir. Como he dicho antes, Docker se basa en instalar en un sistema Linux todo lo que necesitemos para ejecutar nuestro código. ¿Significa esto que vas a tener que instalar Python/R, Tnesorflow, etc. desde línea de comandos? Pues no.

Y es que la comunidad suele crear imágenes que incluyen ya contenido genérico. Por ejemplo, si usas Python hay imágenes que ya tienen Python instalado. Incluso, hay imágenes que ya tienen todo lo necesario para que ejecutes Tensorflow. Y exactamente lo mismo pasa con R: hay imágenes que tienen R instalado, RStudio, Shiny… de todo.

Para encontrar estas imágenes lo más común suele ser buscar la imagen en el repositorio de imágenes de Docker. Si buscas “tensorflow”, por ejemplo, encontrarás la imagen oficial de Tensorflow (enlace). En el caso de R, las imágenes más habituales están mantenidas por rocker.

Por ejemplo, supongamos que hemos creado un modelo de Machine Learning en Python y hemos creado una API con FastAPI (en este postte explico cómo crear APIs en Python).

Si buscamos en la documentación de FastAPI verás que indican que esta imagen Docker incluye todo lo necesario para usar Fast API.

Si te fijas, el nombre de la imagen es tiangolo/uvicorn-gunicorn-fastapi. Así pues, si queremos crear una imagen Docker que tenga FastAPI, tendremos que partir de esta imagen. Esto es tan simple como incluir la siguiente línea en nuestro Dockerfile:

FROM tiangolo/uvicorn-gunicorn-fastapi 

Perfecto, ahora sabemos cómo podemos partir de una imagen ya definida. Pero, ¿qué pasa si queremos instalar algo más en Docker? ¡Sigamos con nuestro tutorial de Docker para Data Science, viendo las instrucciones de Dockerfile para ejecutar comandos!

Dockerfile: RUN

La instrucción RUN ejecutará un comando en consola. Esto nos servirá para instalar nuevo software que necesitemos pero que la imagen de la que partimos no incluye. Por ejemplo, esto puede darse si quieres acceder a una base de datos.

RUN puede tener dos formas diferentes:

  • RUN comando : ejecuta el comando el shell.
  • RUN [ejecutable parametro1 parametro2] . Permite pasar parámetros a la ejecución.

Por ejemplo, si queremos contectarnos a PostgreSQL, tendremos que instalar el paquete libq-dev. Para ello nuestro Dockerfile tendría que incluir la siguiente instrucción:

RUN apt-get update
RUN apt-get install -y libpq-dev 

Asimismo para no incluir cada línea en una instrucción diferente, podríamos juntarlas en una única instrucción de la siguiente forma:

RUN apt-get update \
  && apt-get install -y libpq-dev

En nuestro caso usaremos el comando RUN para instalar las dependencias de nuestra aplicación del fichero requirements.txt.

RUN pip install -r requirements.txt

Ahora ya sabes cómo instalar el software que necesitas, ¡veamos cómo puedes incluir tu código en la imagen Docker!

Dockerfile: COPY, ADD y WORKDIR

Tanto COPY como ADD sirven para añadir contenido a nuestro Dockerfile, aunque ADD ofrece más opciones que COPY. Y es que, mientras que COPY únicamente sirve para copiar ficheros en local, con ADD puedes añadir ficheros de una URL.

Ademas, si añades un fichero local comprimido (como un fichero tar.gz), ADD lo descomprimirá en una carpeta nueva.

Aunque ADD parezca muy superior a COPY, a menos que vayas a usar alguna de las funcionalidades de ADD, Docker recomienda el uso de COPY.

Asimismo, tanto ADD como COPY toman dos parámetros:

  • Ruta de origen: indica la ruta donde se encuentra el fichero que quieres copiar.
  • Ruta de destino: ruta en la que quieres “pegar” el fichero. Si no quieres pegarlo en una ruta específica, puedes indicar que se pegue en /, de tal forma que el elemento se pegue en el Working Directory.

Asimismo, para indicar el Working Directory usaremos el verbo WORKDIR. La instrucción WORKDIR permite indicar dónde se ejecutarán las funciones RUN, CMD, ENTRYPOINT, COPY y ADD que vengan a continuación. Asimismo, en caso de que sea necesario, es posible cambiar el WORKDIR varias veces en un mismo Dockerfile (aunque no es algo muy habitual, al menos si usamos Docker para Data Science).

Siguiendo el ejemplo, supongamos que tenemos la siguiente estructura:

.
├── app
│   └── main.py
│
├── Dockerfile
│
└── requirements.txt

Además, el fichero main.py incluye el mismo código que usé en el post de cómo crear APIs en Python:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

@app.get("/get-iris")
def get_iris():

    import pandas as pd
    url ='https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/0e7a9b0a5d22642a06d3d5b9bcbad9890c8ee534/iris.csv'
    iris = pd.read_csv(url)

    return iris

@app.get("/plot-iris")
def plot_iris():

    import pandas as pd
    import matplotlib.pyplot as plt

    url ='https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/0e7a9b0a5d22642a06d3d5b9bcbad9890c8ee534/iris.csv'
    iris = pd.read_csv(url)

    plt.scatter(iris['sepal_length'], iris['sepal_width'])
    plt.savefig('iris.png')
    file = open('iris.png', mode="rb")

    return StreamingResponse(file, media_type="image/png")

Así pues, tendremos que incluir nuestro fichero en nuestra imagen Docker. Para ello simplemente incluiríamos la siguiente instrucción:

# Creo la carpeta
RUN mkdir -p app

# Pego la carpeta
COPY ./app app

Veamos el último punto, cómo ejecutar nuestra aplicación

Dockerfile: EXPOSE, ENTRYPOINT y CMD.

Si tu código va a estar escuchando en un puerto, EXPOSE te permite definir el puerto en el que va a estar escuchando tu aplicación.

Por otro lado, ENTRYPOINT permite definir el comando que se va a ejecutar cuando se lance la imagen Docker. Por su parte, CMD permite definir los argumentos que vaya a usar ENTRYPOINT.

Por ejemplo, supongamos que vamos a ejecutar nuestra API de FastAPI, y queremos que el usuario pueda definir el host y el puerto en el que se ejecuta. Es por ello que usaremos la instrucción CMD. Más en concreto, al instrucción será la siguiente:

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]

Terminando el Dockerfile

Considerando todo lo anterior, nuestro Dockerfile tendrá el siguiente aspecto:

FROM tiangolo/uvicorn-gunicorn-fastapi
COPY requirements.txt .
RUN pip install -r requirements.txt
RUN mkdir -p app
COPY ./app app
EXPOSE 8080
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]

Ahora, tendremos que guardarlo como un fichero sin extensión. Para ello necesitarás un editor de texto como Notepad ++ o Sublime Text. Además, el fichero deberá llamarse Dockerfile (sino no servirá).

Con esto, ya tenemos nuestro Dockerfile listo y podremos crear nuestra imagen Docker, veamos cómo.

Cómo crear una imagen Docker a partir de un Dockerfile

Una vez ya tenemos nuestro fichero Dockerfile, deberemos montar nuestra imagen Docker.

Para ello, lo primero de todo es abrir una terminal en la misma carpeta donde tenemos el Dockerfile. Aquí se explica cómo podemos hacerlo en Window, y aquí se explica cómo podemos hacerlo en Mac.

Una vez abierta la terminal en la carpeta, deberemos ejecutar el siguiente comando:

docker build .

Importante: no debe haber ningún espacio después el punto, sino no funcionará.

Además, podemos fijar un montón de parámetros a la hora de montar la imagen aunque los más utilizados suelen ser:

  • -t : permite dar un nombre y un tag a la imagen. El nombre nos permitirá diferenciar las imágenes, y el tag nos permitirá diferenciar las versiones. Este es un parámetro que personalmente siempre incluyo. De hecho, en este ejemplo yo he ejecutado:
docker build -t app_prueba .
  • --no-cache: si repites el build de una imagen, evita que use la caché.
  • -m: permite limitar la memoria que utilizará el proceso. En algunos SO el propio Docker limita el uso de memoria a 2GB, por lo que con esto podríamos permitirle usar más.

Una vez lances el comando y se ejecute de forma correcta, verás tu imagen en Docker Desktop:

Si has llegado hasta aquí, enhorabuena, has montado tu primera imagen Docker.

Ahora, veámos cómo lanzar la aplicación.

Cómo lanzar una imagen Docker

Lanzar una imagen Docker en local es súper sencillo. Una vez tienes la imagen Docker debes poner el ratón sobre encima de ella y a la derecha del todo aparecerá el botón “Run”. Haciendo clic en él nos saldrá una ventana como la siguiente:

En esta ventana podemos indicar varias cuestiones, como el nombre que queremos que tenga este contenedor o el puerto en el que queremos que se ejecute. Si no indicas nada usará los valores por defecto y se inventará un nombre.

Por otro lado, otra forma de lanzar la aplicación (la que yo personalmente utilizo) es mediante consola. Para ello simplemente debes indicar:

  • El nombre de la aplicación.
  • El puerto en el que escuchas.
  • El puerto en el que escucha el contenedor.

Así pues, considerando el Dockerfile que expone la app en el puerto 80, voy a pedir que escuche en el puerto 80 del localhost. Esto lo consigo lanzando el contenedor con el siguiente comando:

docker run -dp 80:80 -name example_app app:001

Personalmente siempre recomiendo indicar al menos el nombre, ya que facilita que podamos entender qué es cada cosa en caso de que tengamos varios contenedores.

Así pues, si todo funciona veremos que la aplicación se ha lanzado de forma correcta:

¡Ya lo tenemos! Por último, solo queda acceder a esta aplicación, lo cual podemos hacer desde nuestro ordenador (en mi caso en el puerto 80):

Conclusión

Sin duda alguna Docker es una herramienta fundamental para cualquier Data Scientist, puesto que nos acerca mucho a la puesta en producción de aplicaciones y modelos (algo de lo qe hablaré más adelante aprovechando este post).

Así pues, te animo a practicar con Docker y, si te interesa la puesta en producción de modelos y aplicaciones en entornos Cloud, te recomiendo suscribirte para estar alerta cuando escriba sobre ello. ¡Nos vemos en el siguiente post!