|
|
--- |
|
|
title: "Dockerizando Shiny" |
|
|
output: github_document |
|
|
--- |
|
|
|
|
|
```{r setup, include=FALSE} |
|
|
knitr::opts_chunk$set(echo = TRUE) |
|
|
``` |
|
|
|
|
|
## Introducción |
|
|
|
|
|
Muchas veces, la versión estándar de los servidores gratuitos de Shiny no alcanzan para nuestras hermosas creaciones en R. La limitación que más me molestaba era la de las 5 instancias activas simultáneamente. Para entornos de test quizá no estaba mal, pero una vez que la aplicación llega a ámbitos más profesionales donde probablemente más personas utilicen la app al mismo tiempo es más difícil. |
|
|
|
|
|
En este breve tutorial llevaremos una apliación de Shiny a un repositorio Docker y de allí mismo haremos un deploy a un servicio de Google Cloud con más RAM y CPUs disponibles. |
|
|
|
|
|
## Ingredientes |
|
|
|
|
|
- Una aplicación Shiny, .Rmd, flexdashboard (como en este ejemplo), etc. |
|
|
- Docker y una cuenta Docker para subir la imagen a un repositorio. |
|
|
- Cuenta en Google Cloud y las APIs básicas activas (el proceso es bastante intuitivo). |
|
|
|
|
|
## Instalando Docker |
|
|
|
|
|
Para instalar Docker, se obtiene el archivo desde la página oficial y se instala normalmente. En OSX, como es mi caso, después de descargar el .dmg se instala de la siguiente manera desde la terminal: |
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
sudo hdiutil attach Docker.dmg |
|
|
sudo /Volumes/Docker/Docker.app/Contents/MacOS/install |
|
|
sudo hdiutil detach /Volumes/Docker |
|
|
``` |
|
|
|
|
|
## Creando el Dockerfile |
|
|
|
|
|
El archivo Dockerfile es un conjunto de líneas de texto que configuran los paquetes que Docker va a utilizar para generar el entorno de nuestra aplicación y es donde también le daremos "órdenes" para ejecutar la app. |
|
|
|
|
|
En mi caso, creé una carpeta que se llama "Docker" con dos elementos: una carpeta llamada "R" donde está todo lo relacionado a la aplicación Shiny y el propio archivo "Dockerfile" (así, sin puntos, ni extensiones, ni cosas raras). |
|
|
|
|
|
En mi caso, el archivo Dockerfile contiene las siguientes líneas, comentadas punto a punto para entender mejor: |
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
FROM rocker/shiny:latest # Se instala el entorno Shiny y R |
|
|
|
|
|
RUN apt-get update -qq && apt-get -y --no-install-recommends install \ # Se instalan librerías necesarias |
|
|
libgdal-dev \ # Ojo que probablemente necesites más o menos |
|
|
libproj-dev \ # depende de los paquetes que uses |
|
|
libgeos-dev \ |
|
|
default-libmysqlclient-dev \ |
|
|
libmysqlclient-dev \ |
|
|
libudunits2-dev \ |
|
|
netcdf-bin \ |
|
|
libxml2-dev \ |
|
|
libcairo2-dev \ |
|
|
libsqlite3-dev \ |
|
|
libpq-dev \ |
|
|
libssh2-1-dev \ |
|
|
unixodbc-dev \ |
|
|
libcurl4-openssl-dev \ |
|
|
libssl-dev |
|
|
|
|
|
RUN apt-get install pandoc # Tenía problemas con Pandoc e instalarlo por separado lo solucionó |
|
|
|
|
|
RUN apt-get update && \ # Actualizar paquetes y limpiar |
|
|
apt-get upgrade -y && \ |
|
|
apt-get clean |
|
|
|
|
|
# Se instalan los paquetes necesarios |
|
|
# notar que ya estamos ejecutando COMANDOS DE R dentro del Docker |
|
|
|
|
|
RUN R -e "install.packages(pkgs=c('shiny','tidyverse', |
|
|
'flexdashboard', 'leaflet', 'sf', 'ggalluvial', |
|
|
'plotly', 'rmarkdown'), repos='https: |
|
|
|
|
|
RUN R -e "install.packages(pkgs=c('devtools'), |
|
|
repos='https: |
|
|
|
|
|
RUN R -e "devtools::install_github('rstudio/leaflet')" |
|
|
|
|
|
RUN mkdir /root/app # Creamos la carpeta de la app |
|
|
|
|
|
COPY R /root/shiny_save # Copia la carpeta "R" a la carpeta shiny_save |
|
|
|
|
|
EXPOSE 3838 #Importantísimo: asignarle un puerto de comunicación a la app. |
|
|
#Después no vamos a poder hacer nada si no se expone el puerto |
|
|
|
|
|
# Se corre la app (en este caso, al ser un flexdashboard |
|
|
# tengo que ejecutarlo con rmarkdown, si no sería shiny::runApp(...)). |
|
|
# Como argumento extra le paso el puerto abierto. |
|
|
CMD ["R", "-e", "rmarkdown::run('/root/shiny_save/dash_v2.Rmd', |
|
|
shiny_args=list(host='0.0.0.0', port=3838))"] |
|
|
|
|
|
``` |
|
|
|
|
|
## Construcción de la imagen |
|
|
|
|
|
Una vez que tengamos el dockerfile y la carpeta con todos los archivos que necesita la app para correr, pasamos a construir la imagen de Docker propiamente dicha. Para esto, abrimos la consola y ejecutamos: |
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
docker login |
|
|
``` |
|
|
Nos pedirá el usuario y la contraseña de Docker. La escribimos y luego: |
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
docker build -t nombreapp . |
|
|
``` |
|
|
|
|
|
Ese comando construirá una imagen llamada "nombreapp" con los parámetros del dockerfile (para eso el punto al final). Esta imagen contiene el entorno R y todo el código que hemos usado, dentro de un sistema Linux. |
|
|
Es por esto que probablemente tarde bastante en ejecutarse la primera vez (a mí me lleva aproximadamente 3 horas), obviamente dependiendo la complejidad de la app. Si hacemos modificaciones al código de R de la aplicación y pusheamos los cambios (ejecutando los mismos parámetros), la construcción tarda muchísimo menos ya que la mayoría de las capas del entorno son las mismas y no se modifican. Esto es algo que me encanta de Docker. El trabajo se divide en "capas", por lo que si corregís una parte pequeña del código, el build y el push de la app tardan segundos. |
|
|
|
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
docker tag nombreapp usuario/nombreapp:latest |
|
|
``` |
|
|
|
|
|
"Tagueamos" la app dentro del repositorio público que estamos por crear. Es como decir que lo estamos "seleccionando" para subirlo. El repositorio se llamará usuario/nombreapp. La última parte ":latest" es un nombre que podemos poner para identificar a la última versión de la app. Lo podemos ir modificando para cada versión. |
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
docker push usuario/nombreapp:latest |
|
|
``` |
|
|
|
|
|
Se pushea la imagen al repositorio, tal como si fuera GitHub. |
|
|
|
|
|
## Deploy en Google Cloud |
|
|
|
|
|
Una vez creado el proyecto, procedemos a abrir la consola de comandos dentro de la misma Web de Google. |
|
|
Lo primero que debemos hacer es loguearnos con nuestras credenciales de Docker. |
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
docker login |
|
|
``` |
|
|
|
|
|
Posteriormente, "traemos" la imagen del Docker a nuestro proyecto de Google Cloud con los últimos cambios. |
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
docker pull usuario/nombreapp:latest |
|
|
``` |
|
|
Luego, análogamente a lo que hacíamos en Docker, se "taguea" la imagen a pushear, lo único es que esta vez no pusheamos al repo de Docker sino al container de imágenes de Google Cloud. Reemplazá PROJECT_ID por el ID de tu proyecto. |
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
docker tag usuario/nombreapp:latest gcr.io/PROJECT_ID/usuario/nombreapp:latest |
|
|
``` |
|
|
|
|
|
Por último, pusheamos la imagen. |
|
|
|
|
|
```{bash, eval = FALSE} |
|
|
docker push gcr.io/PROJECT_ID/usuario/nombreapp:latest |
|
|
``` |
|
|
|
|
|
## Creación de la instancia del servicio |
|
|
|
|
|
Ya casi terminamos. Luego de pusheada la imagen, en la pestaña de Google Cloud Run clickeás "Crear Servicio". Esto nos habilitará una máquina virtual para hacer el deploy y que podamos compartir la app a un montón de gente. |
|
|
|
|
|
Donde dice "Url de la imagen" poné seleccionar y se van a desplegar las opciones de imagen. Nos va a aparecer la imagen "nombreapp" con la versión "latest". Usamos esa y completamos los parámetros con lo que necesite el proyecto. |
|
|
|
|
|
|
|
|
|
|
|
Esperás unos segundos a que Google cree la instancia y listo! Ya está la app deployada en Google Cloud Run. Cualquiera va a poder acceder a ella a través de la URL que te muestra arriba del panel. |
|
|
|
|
|
|
|
|
|