## Source Dockerfile
/geospatial:4
FROM rocker
### JULIA
## Copy Julia's tar.gz and install it
## using 1.8.3 version because it does work with quarto on my computer
://julialang-s3.julialang.org/bin/linux/x64/1.8/julia-1.8.3-linux-x86_64.tar.gz && \
RUN wget https-1.8.3-linux-x86_64.tar.gz && \
tar zxvf julia## Connect to Julia's directory link with real jula's bin
-r julia-1.8.3 /opt/ && \
cp -s /opt/julia-1.8.3/bin/julia /usr/local/bin/julia
ln
## Install Julias package (IJulia <-- to connect with jupyter)
## Installing from julia
-e 'import Pkg; Pkg.add("GeoDataFrames"); Pkg.add("Distributed"); Pkg.add("StatsAPI"); Pkg.add("Plots"); Pkg.add("DelimitedFiles"); Pkg.add("DataFrames"); Pkg.add("CSV"); Pkg.add("GeoStats"); Pkg.add("Rasters"); Pkg.add("Shapefile"); Pkg.add("GeoTables"); Pkg.add("CairoMakie"); Pkg.add("WGLMakie"); Pkg.add("IJulia")'
RUN julia
## non Interactive terminal for this docker for the site
=noninteractive; apt-get -y update \
RUN export DEBIAN_FRONTEND&& apt-get install -y pandoc \
-citeproc
pandoc
### R packages
## Defining web acces for CRAN
="https://cran.rstudio.com/"
ENV R_CRAN_WEB-e "install.packages('INLA',repos=c(getOption('repos'),INLA='https://inla.r-inla-download.org/R/stable'), dep=TRUE)"
RUN R -e "install.packages(c('dyplr','ggplot2','remotes','microbenchmark','purrr','BiocManager','httr','cowplot','torch','PLNmodels','torchvision','reticulate','inlabru', 'lme4', 'ggpolypath', 'RColorBrewer', 'geoR','tidymodels', 'brulee', 'reprex','poissonreg','ggbeeswarm', 'tictoc', 'bench', 'circlize', 'JuliaCall', 'GeoModels','sp','terra','gstat','sf'))"
RUN R -e "BiocManager::install('BiocPkgTools')"
RUN R -e "torch::install_torch(type = 'cpu')"
RUN R -e "JuliaCall::install_julia()"
RUN R
### Ubuntu libraries (for python ?)
-get update \
RUN apt&& apt-get install -y --no-install-recommends \
jags \-bin libgdal-dev gsl-bin libgsl-dev \
mercurial gdal-i386
libc6
### Jupyter Python torch jax etc
## Downloading Python
-get install -y --no-install-recommends unzip python3-pip dvipng pandoc wget git make python3-venv && \
RUN apt-cache flatlatex matplotlib && \
pip3 install jupyter jupyter-get --purge -y remove texlive.\*-doc$ && \
apt-get clean
apt
-learn torchvision torchaudio pyplnmodels optax RUN pip3 install jax jaxlib torch numpy matplotlib pandas scikit
Gestion d’un Dockerfile en projet collaboratif
Lors d’un évènement comme finistR nous travaillons tous, sur des interfaces, des systèmes et des langages différents. Lors de finistR2023 par exemple, nous avons dû utiliser dans un même conteneur docker les langages R
, Python
et Julia
et compiler cela via quarto. Construire un docker est quasiment aussi simple en principe que de suivre la recette d’une salade. Mais lorsque les problèmes arrivent et que la mayonnaise ne marche plus, il ne vous reste plus qu’à manger votre clavier…
Sur cette page vous trouverez premièrement un petit rappel sur ce qu’est un Dockerfile
et le fonctionnement général de docker. Puis deuxièmement des indications plus particulières au conteneur de finistR2023 c’est à dire un docker R
, Python
, Julia
et quarto
I - Un Dockerfile
?
Docker est une technologie permettant de créer et utiliser de manière efficace des conteneurs logiciels. Ces conteneurs logiciel sont en fait des environnements de travail spécialisables, versionnés et facilement partageable. Docker permet donc de partager : des environnements de calculs pour des travaux en équipe, des applications (par exemple {shiny}), mais aussi permet l’intégration continue de site web et autres.
Voici un Dockerfile
utile pour ce projet:
Malgré ce format peu tentant, l’écriture d’un Dockerfile
est facile tant que l’on maîtrise bien les outils que le docker doit contenir.
a) FROM
Le récypient de la reccette
Pour construire un conteneur docker on utilise en général au départ… un conteneur docker. De préférence il faut trouver un conteneur plus ou moins adapté à ce que l’on veux. Ici nous utilisons rocker/geospatial:4
, les conteneurs rocker
contiennent R
avec ici une adaptation particulière au geospatial
.
b) RUN
Les ingrédients de la recette
Avec RUN
il est possible d’effectuer des commandes en bash
pour installer ce dont on a besoin dans le Docker. Pour rappel, ici il faut que nous puissions compiler un site web de manière automatique, et aussi compiler des quarto (Julia
, R
et Python
) à l’origine des pages web. Pour chacune de ces étapes il faut vérifier l’installation du langage, installer les dépendances nécessaires au code. Et enfin s’assurer de la compatibilité entre les outils.
c) Exemple - Instalation de Julia
pour quarto
Ici R
est déjà installé via rocker
mais pas Julia
. Le paragraphe suivant consiste à l’installation complète de Julia-1.8.3
dans un système type ubuntu.
://julialang-s3.julialang.org/bin/linux/x64/1.8/julia-1.8.3-linux-x86_64.tar.gz && \
RUN wget https-1.8.3-linux-x86_64.tar.gz && \
tar zxvf julia## Connect to Julia's directory link with real jula's bin
-r julia-1.8.3 /opt/ && \
cp -s /opt/julia-1.8.3/bin/julia /usr/local/bin/julia ln
Le symbole && \
permet si la ligne à gauche est un succès de lancer la ligne suivante et le tout dans un seul RUN
. En général il vaux mieux avoir peu de longue ligne de code que beaucoup de ligne faisant un appel systématique à RUN
.
wget
téléchargeJulia
tar
décompresse le fichier téléchargécp
copieJulia
dans le dossier des programmesln
créé un lien symbolique qui permet de lancer des script avec la commande$ julia
depuis le terminal.
Cependant pour pouvoir lancer Julia
depuis quarto cela ne suffit pas. Premièrement la version actuelle du langage ne communique pas correctement avec quarto alors que la version Julia-1.8.3 semble bien fonctionner. Deuxièmement il est nécessaire de créer un kernel IJulia
pour connecter Julia
à jupyter. C’est jupyter qui vas ensuite communiquer avec quarto. Pour faire cela, il faut installer le package {IJulia}.
-e 'import Pkg; Pkg.add("IJulia")' RUN julia
On prendra soins d’importer les autres packages dont nos quarto dépendent
-e 'import Pkg; Pkg.add("DataFrames"); Pkg.add("GeoDataFrames"); Pkg.add("IJulia")' RUN julia
Par sécurité nous avons aussi choisi de faire la commande R suivante qui installe une petite dépendance de Julia
dans R
.
-e "JuliaCall::install_julia()" RUN R
II - Construire mon Dockerfile
?
Pour arriver à écrire cela, avoir déjà installé le programme voulu en local est très utile ! Mais si votre ordinateur est un Windows cela ne vous aidera pas… Arriver à vos fin peut être compliqué et dans tout les cas il vas vous falloir essayer … La compilation d’un docker est assez longue donc chaque essais peut-être coûteux. Il existe cependant différents moyens de gagner du temps. Commencez d’abord par chercher les routines d’installation des vos dépendances sur ubuntu.
a) Organiser son fichier
Une image docker lors de sa compilation en local, va garder en cache énormément d’information de manière séquentielle. Changer des lignes au début de mon Dockerfile
est plus coûteux que de changer des lignes à la fin. Ainsi il est toujours plus intéressant lors du développement d’un conteneur de garder les lignes les moins sûres vers la fin du fichier (dans la mesure du possible).
b) Compilation
Pour compiler mon conteneur il suffit de lancer la commande dans le répertoire où est mon Docker.
-f Dockerfile --progress=plain -t nom_image:num_version . docker build
Un changement de numéro de version suffit à générer une nouvelle image en tirant toujours profit des données en cache.
c) Tester rapidement un conteneur (exemple non-interactif)
Le conteneur fabriqué pour le site de finistR est non interactif, une fois activé avec la ligne
:num_version docker run nom_image
il n’est pas possible de lancer des commandes dans l’environnement créé depuis un terminal. Il n’est donc pas évident de tester les installations dans le conteneur.
Une possibilité est de créé un deuxième Dockerfile
(que l’on peut nommer 'DockerfileTest'
). Voici un exemple
## Source DockerfileTest
:num_version
FROM nom_image
RUN quarto check jupyter-e 'using GeoDataFrames' RUN julia
Ce Dockerfile
se build avec la commande
-f DockerfileTest --progress=plain -t nom_image_test:num_version . docker build
Un intérêt de cette méthode est que l’on peux assez rapidement rééditer le conteneur le premier étant inchangé. Dans cet exemple :
quarto check jupyter
permet de vérifier si quarto a bien accès à jupyter mais aussi de vérifier si le kernel {IJulia} existe.julia -e 'using GeoDataFrames'
vérifie si le package a bien été installé dansJulia
.
C’est lors de ce build
que vous pouvez vérifier si ces commandes rendent les résultats attendus.
Dans le cas du conteneur de finistR, la compilation du conteneur sur github dure environ 45 min et le test à la suite d’une pull request dure environ 30 min. Grace à ces conseils un conteneur modifié aux dernières lignes se compile quasi immédiatement en local (idem pour DockerfileTest
).
d) Des bonnes pratiques oui ! Mais du groupe avant tout
L’idéal dans ce genre de projet en groupe, est de pouvoir faire un suivis des packages et langages à installer. Pour cela lorsque que l’on effectue une pull request incluant un nouveau document, il est préférable de joindre explicitement en message :
- les dépendances
- la version du langage utilisé
- le système d’exploitation
En cas de problème avec un conteneur, ces informations sont très utiles pour résoudre les incompatibilités qui ont lieux.
III - La réalité, une limite de mémoire
a) Un Dockerfile
ça doit être petit
Lors de ce projet nous avons utilisé un autre Dockerfile
(voir: Instructions pour le dépot sur le site web), car celui montré plus haut est beaucoup trop lourd (18GB), et … en pratique il ne rentre pas dans un runner GitHub (max 14GB) sans aménagement. Mes connaissances étant limitées, je ne sais comment économiser de la mémoire dans un docker.
b) Abandon : Du docker plein au qmd
fixés.
Pour résoudre ce problème on vas donc transformer ce qmd
(R & Julia) en un md fixe au moins pour la partie Julia. Je crée donc un qmd
contenant uniquement du texte et du Julia. Puis je le fais tourner en local pour produire un fichier md
. Je remplace ensuite le passage en Julia dans mon qmd
(R & Julia de base). Ainsi seul le R est du code réel. Pour finir je remplace les
``` julia
avant chaque chunks de Julia par des
```{r,eval = FALSE}
Ce qui va le faire s’afficher comme un chunk de R avec des couleurs dans le texte mais sans l’évaluer.
Conclusion
Docker c’est formidable ça permet de simplifier énormément de processus en remotes, d’automatiser des actions de contrôles et de diffusion mais aussi de partager des programmes plus simplement. Cependant c’est une technologie qui nécessite une liste claire des dépendances de chaque code. Enfin lorsque des procédés complexes sont nécessaires, comme par exemples mélanger de nombreux outils différents, il vaux tout de même mieux jouer la carte de l’économie, et reporter la plus part des calculs en local. Et ainsi faire travailler un minimum le runner à chaque action.