TL;DR: La configuración de Docker necesaria para subir la API que hicimos en el posteo anterior la pueden encontrar aquí: https://github.com/augustohassel/Demo-APIs 👽
En este segundo posteo de la serie, vamos a intentar explicar en pocas palabras (y viniendo de un ‘no experto’) qué es Docker, para luego hacer un paso a paso que nos ayude a escribir nuestro primer Dockerfile
que sirva para correr la API que hemos creado en el posteo anterior.
El construir y mantener una máquina virtual desde cero en la nube con el sólo motivo de que sirva para alojar nuestra API es una tarea engorrosa y costosa… Es por esto que nos vemos en la necesidad de buscar otras alternativas para lograr tener mayor control sobre los ambientes de desarrollo, intentando reducir al mínimo la complejidad y los componentes que son necesarios para que funcione nuestra aplicación. Aquí es donde entra Docker en el juego!
Mantenemos los mismos supuestos que en el posteo anterior y agregamos uno:
¿Qué es Docker? Docker es un proyecto Open Source con el cual podemos empaquetar aplicaciones en contenedores, los cuales contienen todo lo necesario para que dichas aplicaciones se puedan ejecutar. Entonces… ¿qué es un contenedor? Bueno, un contenedor es una unidad estandarizada de software que empaqueta todo el código y todas sus dependencias, de manera que una aplicación pueda correr rápidamente independientemente del ambiente en el que se encuentre. Es así que los contenedores aíslan las aplicaciones del ambiente en el que se encuentran, de manera tal que siga funcionando, a pesar de que corran en instancias de desarrollo distintas.
Hoy los contenedores de Docker se puede usar en todos lados: Linux, Windows, Data Centers, la Nube, Serverless (este sería nuestro caso!!!)
Primero vamos a ‘intentar’ instalar Docker Desktop desde el sitio oficial: link.
En mi caso no cumplo con los requisitos mínimos 😡, ya que tengo Windows 10 Home, así que tendré que dar una vuelta de tuerca a esta situación de la siguiente manera:
Quienes tengan Linux o Windows 10 Pro no tendrán necesidad de seguir estos pasos… Si quieren que haga un tutorial sobre cómo hacerlo, tranquilamente lo puedo pensar para otro posteo! Por lo pronto, ahora puedo usar Ubuntu en mi máquina con Windows! 🔥
Vamos a continuar directamente desde la máquina virtual con Ubuntu 😉
La instalación del engine de Docker se reduce a seguir los pasos de este tutorial: https://docs.docker.com/install/linux/docker-ce/ubuntu/
Una vez esté instalado, podremos correr el siguiente código para determinar si es que ha funcionado correctamente la instalación: sudo docker run hello-world
. Si todo ha salido bien, entonces verán la siguiente consola:
Uno de los grandes beneficios de Docker es la gran comunidad que hay detrás creando imágenes (simplificando, las imágenes son como planitillas que capturan el estado de un contenedor y sirven para crear los mismos) que nos permitirán levantar rápidamente contenedores con diversas aplicaciones/configuraciones pre instaladas. Algunos link importantes a tener encuenta:
Si se ponen a ver las imágenes del proyecto Rocker, verán que hay imágenes que ya vienen preparadas para correr Shiny o RStudio Server directamente dockerizadas!!!
Vamos a probar descargando una imagen oficial que contiene R. Para hacerlo, tenemos que usar el comando docker pull
con la imagen en cuestión: sudo docker pull r-base
. Esta es la pantalla que verán si todo va bien:
Una vez que tengamos las imágenes descargadas, podemos listarlas directamente con el comando sudo docker images
.
Ahora vamos a una parte más divertida! Podemos crear un contenedor con la imagen que acabamos de descargar y lograremos correr R aún si no tenemos R instalado en nuestra máquina! Para hacerlo usaremos el comando sudo docker run -ti --rm r-base
. En el comando estamos especificando con ‘–rm’ que el contenedor se tendrá que eliminar una vez que salgamos de la consola y además estamos diciendo con ‘-i’ que queremos que nos quede la consola interactiva dentro del contenedor.
Para ver los contenedores creados que estén activos debemos usar el comando sudo docker ps
.
Docker construye imágenes leyendo un archivo que contiene un set de instrucciones, con comandos en un orden determinado. Este archivo se llama Dockerfile. Creemos uno sencillo con el comando touch Dockerfile
y agreguemos el siguiente contenido con cualquier editor de textos.
FROM r-base:latest
COPY / /
RUN R -e "1+1"
CMD ["Rscript", "simple.R"]
Para seguir las buenas prácticas en la construcción de un archivo Dockerfile les recomiendo leer el siguiente link.
Básicamente lo que estamos diciendo en este archivo es que:
message("Hola")
Obviamente hay más comandos a disposición para usar dentro de un Dockerfile, pero estos nos servirán en un principio.
Para construir la imagen vamos a usar el comando sudo docker build . -t 'simple'
parándonos en el directorio en el cual está guardado el Dockerfile, o colocando la dirección en vez del punto dentro del comando.
Finalmente podemos ejecutar la imagen que acabos de crear con el comando sudo docker run -ti --rm simple
para obtener un gran saludo! 🎊
Si llegaste hasta acá te estarás preguntando: ¿cómo hacemos para que funcione la API del posteo anterior?
Bueno! Primero hagamos un clone de repositorio en github que contiene la API: git clone https://github.com/augustohassel/Demo-APIs.git
.
Luego vamos a ubicarnos dentro de la carpeta que contiene el archivo Dockerfile y vamos a crear el contenedor con el comando que vimos antes sudo docker build . -t 'api'
.
Veremos que en la pantalla empiezan a suceder más cosas que en el ejemplo anterior.
Esto tardará unos minutos mientras construye la imagen con los pasos que configuramos en el Dockerfile. Mientras esto sucede veamos qué se está haciendo detrás:
FROM rocker/verse:latest
RUN apt-get update -qq && apt-get install -y \
libssl-dev \
libcurl4-gnutls-dev \
pandoc \
pandoc-citeproc
RUN R -e "install.packages('plumber')"
RUN R -e "install.packages('rmarkdown')"
RUN R -e "install.packages('quantmod')"
RUN R -e "install.packages('dygraphs')"
RUN R -e "install.packages('xts')"
COPY / /
ENTRYPOINT ["Rscript", "main.R"]
Lo que está sucediendo es lo siguiente:
El archivo main.R contiene el setup inicial de plumber, el llamado de la librería, la lectura del archivo plumber.R y la configuración del puerto.
library(plumber)
r <- plumb("plumber.R")
r$run(port = as.numeric(Sys.getenv('PORT')), host = "0.0.0.0")
Finalmente, para ejectutar la imagen y crear el contenedor vamos a usar el comando sudo docker run -p 80:80 api
. Este comando agrega el parámetro ‘p’ para publicar el contenedor en el puerto en cuestión.
En caso de que no te haya funcionado, probablemente tengas que agregar una línea adicional en el Dockerfile antes de ENTRYPOIDNT:
ENV PORT=80
. Con esto te aseguraras de que la variable PORT está expuesta al contenedor.
Finalmente, si queremos ver que está funcionando todo correctamente, no tenemos más que ir a nuestro navegador y colocar la ruta http://127.0.0.1.