Blog Post

Wie Du ShinyApps in Docker-Images einbauen kannst

  • Expert:innen Oliver Guggenbühl
  • Datum 15. Mai 2020
  • Thema Data EngineeringRTutorial
  • Format Blog
  • Kategorie Technology
Wie Du ShinyApps in Docker-Images einbauen kannst

In meinem ersten Blogbeitrag dieser Reihe habe ich gezeigt, wie du deine R-Skripte in einem Docker-Container ausführen kannst. Für viele der Projekte, an denen wir hier bei STATWORX arbeiten, verwenden wir das RShiny-Framework, um unsere Produkte in interaktive Webapplikationen zu verwandeln.

Die Verwendung von Containern hat für die Bereitstellung von ShinyApps eine Vielzahl von Vorteilen. Zunächst sind es die üblichen Vorzüge wie einfache Cloud-Bereitstellung, Skalierbarkeit und praktisches Scheduling, aber es behebt auch einen der wesentlichen Nachteile von Shiny: Shiny erstellt nur eine einzige R-Sitzung pro App. Sollten also mehrere User auf dieselbe App zugreifen, dann arbeiten sie alle mit der selben R-Sitzung, was zu einer Vielzahl von Problemen führt. Mithilfe von Docker können wir dieses Problem umgehen und für jede:n User:in eine eigene Container-Instanz starten. Dadurch erhält jede:r User:in Zugriff auf eine eigene Instanz der App und somit eine eigene R-Sitzung. Ich gehe davon aus, dass du meinen vorigen Blogbeitrag über das Einbinden von R-Skripten in ein Docker-Image gelesen hast oder bereits über Grundkenntnisse der Docker-Terminologie verfügst.

Lassen wir also einfache R-Skripte hinter uns und führen wir jetzt ganze ShinyApps in Docker aus!

 

Das Setup

Einrichten eines Projekts

Für die Arbeit mit ShinyApps ist es ratsam, das Projekt-Setup von RStudio zu nutzen, insbesondere wenn man Docker verwendet. Projekte erleichtern nicht nur die Ordnung in RStudio, sondern ermöglichen es uns auch, mit dem Paket renv eine Paketbibliothek für unser spezifisches Projekt einzurichten. Dies ist besonders praktisch, um die benötigten Pakete für ein Programm in ein Docker-Image zu installieren.

Zu Demonstrationszwecken verwende ich eine Beispiel-App, die in einem früheren Blogbeitrag erstellt wurde und die du aus dem STATWORX GitHub Repository klonen kannst. Sie befindet sich im Unterordner „example-app“ und besteht aus den drei typischen Skripten, die von ShinyApps genutzt werden (global.R, ui.R und server.R) sowie aus Dateien, die zur Paketbibliothek renv gehören. Solltest du zur Übung ebenfalls die oben verlinkte Beispiel-App verwenden, musst du kein eigenes RStudio-Projekt einrichten. Stattdessen kannst du example-app.Rproj öffnen, das den von mir bereits eingerichteten Projektkontext startet. Falls du direkt mit einer eigenen App arbeiten möchtest und noch kein Projekt dafür erstellt hast, kannst du dein eigenes Projekt einrichten, indem du die von RStudio bereitgestellten Anweisungen befolgst.

Einrichten einer Paketbibliothek

Das RStudio-Projekt, das ich zur Verfügung gestellt habe, wird bereits mit einer Paketbibliothek geliefert, die in der Datei renv.lock gespeichert ist. Wenn du es vorziehst, mit deiner eigenen Anwendung zu arbeiten, kannst du deine eigene renv.lock Datei erstellen, indem du das renv Paket von deinem RStudio Projekt aus installierst und renv::init() ausführst. Dies initialisiert renv für dein Projekt und erstellt eine renv.lock Datei in deinem Projekt-Stammverzeichnis. Mehr Informationen über renv findest du unter RStudio’s Einführungsartikel.

Das Dockerfile

Das Dockerfile ist erneut das zentrale Element bei der Erstellung des Docker-Images. Während wir bisher nur ein einziges Skript in ein Image eingebaut haben, wollen wir diesen Prozess nun für eine ganze Anwendung wiederholen. Der Schritt von einem einzelnen Skript zu einem Ordner mit mehreren Skripten ist klein, aber es sind einige bedeutende Änderungen erforderlich, damit die App reibungslos läuft.

# Base image https://hub.docker.com/u/rocker/
FROM rocker/shiny:latest

# system libraries of general use
## install debian packages
RUN apt-get update -qq && apt-get -y --no-install-recommends install 
    libxml2-dev 
    libcairo2-dev 
    libsqlite3-dev 
    libmariadbd-dev 
    libpq-dev 
    libssh2-1-dev 
    unixodbc-dev 
    libcurl4-openssl-dev 
    libssl-dev

## update system libraries
RUN apt-get update && 
    apt-get upgrade -y && 
    apt-get clean

# copy necessary files
## app folder
COPY /example-app ./app
## renv.lock file
COPY /example-app/renv.lock ./renv.lock

# install renv & restore packages
RUN Rscript -e 'install.packages("renv")'
RUN Rscript -e 'renv::consent(provided = TRUE)'
RUN Rscript -e 'renv::restore()'

# expose port
EXPOSE 3838

# run app on container start
CMD ["R", "-e", "shiny::runApp('/app', host = '0.0.0.0', port = 3838)"]

Das Base-Image

Die erste Änderung betrifft das Base-Image. Da wir hier eine ShinyApp verdockern, können wir uns eine Menge Arbeit ersparen, indem wir das Base Image „rocker/shiny“ verwenden. Dieses Image kümmert sich um die notwendigen Dependencies für die Ausführung einer ShinyApp und enthält mehrere R-Pakete, die bereits vorinstalliert sind.

Erforderliche Dateien

Es ist natürlich notwendig, alle relevanten Skripte und Dateien für deine ShinyApp in dein Docker-Image einzubauen. Das Dockerfile erledigt genau das, indem es den gesamten Ordner, der die Anwendung enthält, in das Image kopiert.

Um die benötigten R-Pakete in ihrer korrekten Version in das Docker-Image zu installieren, verwenden wir am besten renv. Deshalb kopieren wir zuerst die Datei renv.lock separat in das Image. Das renv-Paket muss ebenfalls separat installiert werden, indem wir die Fähigkeit des Dockerfiles nutzen, mithilfe von RUN Rscript -e R-Code auszuführen. Diese Paketinstallation erlaubt es uns renv direkt aufzurufen und die kopierte Paketbibliothek in renv.lock innerhalb des Images mit renv::restore() wiederherzustellen. Dadurch wird die gesamte Paketbibliothek im Docker-Image installiert, mit genau der gleichen Version und dem gleichen Quellcode aller Pakete wie in der lokalen Projektumgebung. Und all dies mit nur ein paar Zeilen Code in unserem Dockerfile.

Starten der App zur Laufzeit

Ganz am Ende des Dockerfiles weisen wir den Container an, den folgenden R-Befehl auszuführen:

shiny::runApp('/app', host = '0.0.0.0', port = 3838)

Das erste Argument legt den Dateipfad zu den benötigten Skripten fest, welche in diesem Fall unter ./app abgelegt sind. Für den exponierten Port habe ich 3838 gewählt. Dies entspricht der Standardeinstellung für RStudio Server, kann aber nach Belieben geändert werden.

Wenn der letzte Befehl ausgeführt wird, startet jeder Container, der auf diesem Image basiert, die betreffende Anwendung automatisch zur Laufzeit (und schließt sie natürlich wieder, wenn sie beendet wurde).

Der letzte Schliff

Mit dem fertigen Dockerfile fehlen nur noch wenige Schritte bis zum laufenden Docker-Container. Dazu muss zunächst noch das Image erstellt werden, um anschließend einen Container basierend auf diesem Image zu starten.

Erstellen des Images

Also öffnen wir das Terminal, navigieren zu dem Ordner, der unser neues Dockerfile enthält und starten den Erstellungsprozess des Images:

docker build -t my-shinyapp-image . 

Starten eines Containers

Nachdem dieser Prozess abgeschlossen ist, können wir nun unser neu erstelltes Image testen, indem wir einen Container starten:

docker run -d --rm -p 3838:3838 my-shinyapp-image

Und da ist sie, die ShinyApp läuft auf localhost:3838!

docker-shiny-app-example

Ausblick

Jetzt, wo die ShinyApp in einem Docker-Container läuft, ist sie bereit für den Einsatz. Die Containerisierung unserer App macht diesen Prozess bereits sehr viel einfacher. Es gibt jedoch noch weitere Tools, die wir einsetzen, um Sicherheit, Skalierbarkeit und nahtloses Deployment auf dem neuesten Stand der Technik zu gewährleisten. Im nächsten Beitrag erwartet dich deshalb eine Einführung in ShinyProxy, womit du noch tiefer in das Spektrum an Möglichkeiten von RShiny und Docker eintauchen kannst.

Oliver Guggenbühl Oliver Guggenbühl

Erfahre mehr!

Als eines der führenden Unternehmen im Bereich Data Science, Machine Learning und KI begleiten wir Sie in die datengetriebene Zukunft. Erfahren Sie mehr über statworx und darüber, was uns antreibt.