Docker, pour le Machine Learning
Les commandes de base et les Dockerfile
Dans docker, tout commence par la rédaction d'un Dockerfile
, c'est un simple script que vous écrivez pour dire comment vous allez monter et faire fonctionner votre conteneur docker.
Le langage docker est simple à comprendre, les tâches les plus communes ont leur propres commandes, et pour tout le reste vous pouvez utiliser les commandes shell standards (Bash sur Linux, ou PowerShell sur Windows par exemple).
Pour voir comment s'écrit un Dockerfile, comment construire l'image et lancer le conteneur, prenons l'exemple suivant. C'est le Dockerfile standard que j'utilise pour entraîner des modèles de deep learning.
Dockerfile
Un Dockerfile est une suite d'instruction, chaque instruction étant une couche (layer) du Dockerfile. La toute première instruction est toujours la même, elle détermine quelle sera la base de votre conteneur, est-ce que votre conteneur sera construit sur une base d'OS Ubuntu 18.02, 20.04, sur une base Python 3.8, etc. Chaque image doit commencer d'une autre image. Ici l'image en question est nvcr.io/nvidia/tensorflow:21.02-tf2-py3
une image de TensorFlow 2.4 faite par NVidia, ce qui permet de ne pas avoir à se soucier des problèmes d'installation ou de dépendances.
La première couche
Cette première couche commence toujours par un FROM
, pour dire à partir de quelle image de base vous allez construire votre Dockerfile.
Pourquoi FROM
? Il existe ce que l'on appelle des "registres dockers" (docker registry), où de manière similaire à github, gitlab, etc sont recensés les images docker de façon la plupart du temps open source, le plus connu étant docker hub. Pour récupérer une image, la commande similaire au "git clone adresse" est "docker pull adresse", et donc d'où vient votre image de base pour votre Dockerfile ? En anglais, "it has been pulled FROM address".
COPY
est la commande permettant de copier des dossiers depuis votre machine locale vers votre conteneur Docker. Ici COPY requirements.txt .
copie le fichier requirements.txt
vers .
, ie à la racine définie dans l'image nvcr.io/nvidia/tensorflow:21.02-tf2-py3
.
Syntaxe
La syntaxe est COPY dossier_source dossier_cible
.
Par défaut, toutes les instructions lancées dans un conteneur docker se font en mode super-admin. Certaines application ayant besoin d'un répertoire /home/
, il est souvent nécessaire de créer un utilisateur, ce qui est fait dans les lignes suivantes.
Création d'un utilisateur
La commande ARG
permet de définir des variables d'environnement qui ne seront disponibles que durant la construction de l'image, ici les identifiants d'un utilisateur. On a ensuite besoin d'ajouter cet utilisateur et ce group dans les utilisateurs du conteneur, ce qui ce fait via la commande RUN
qui permet de lancer des commandes shell.
Enfin on spécifie qui sera l'utilisateur de ce conteneur, que sera l'utilisateur que l'on vient de créer. Cela se fait via la commande USER
.
A la différence de ARG
, ENV
définit lui des variables d'environnements qui seront toujours disponibles après la construction de l'image, et donc lorsque le conteneur sera lancé. Ici on définit un chemin PATH "$PATH:/home/vorph/.local/bin"
qui est nécessaire pour certaines librairies python dans les fichiers requirements.txt
et requirements-dev.txt
.
Si vous voulez que votre conteneur docker communique vers l'extérieur autre que via le terminal, il faut lui en donner les droits. Cela peut dire exposer certains des ports du conteneur vers votre machine locale. Ici on en expose deux qui sont nécessaires pour les librairies mlflow et mkdocs, via les commandes EXPOSE
.
Construction de l'image
Maintenant que votre Dockerfile est rédigé, vous pouvez construire votre image. Le départ est toujours le même : sudo docker build
, suivi d'argument.
Remarque
les commandes docker dans le terminal ont besoin d'être passé en super-admin, si vous souhaitez ne plus avoir à taper sudo docker build
mais simplement docker build
, docker run
, etc vous devez créer un groupe docker et vous ajouter en tant qu'utilisateur dedans. Pour cela, suivez les instructions de la doc officielle. Manage Docker as a non-root user
docker build
Les deux arguments --build-arg
correspondent aux mêmes arguments ARG
dans le Dockerfile, ARG USER_UID=1000
signifiant que la valeur par défaut de USER_ID est 1000, --build-arg
permet de réécrire au dessus pour être sur d'avoir les bonnes valeurs correspondant au couple uid:gid
de votre machine locale.
--rm
permet de supprimer les conteneurs intermédiaires utilisés uniquement durant la construction.
-f Dockerfile
spécifie quel Dockerfile doit être utilisé pour la construction, ici celui nommé simplement Dockerfile
. Le nommage des Dockerfile se fait de la façon suivante : Dockerfile.suffixe
, par exemple vous pourriez avoir deux Dockerfiles différents
Dockerfile.cpu
,Dockerfile.gpu
,
où les instructions de construction à l'intérieur du Dockerfile seraient différentes que vous utilisiez le gpu ou non. Dans ce cas vous pourriez avoir les commandes suivantes.
docker build
Enfin, -t project_ai
définit le nom que prendra l'image, ici "project_ai".
Le point .
à la fin de la commande docker build signifie à docker qu'il doit chercher le Dockerfile dans le répertoire actuel.
Remarque
Beaucoup d'autres options sont disponibles, n'hésitez pas à regarder la documentation sur les options de construction.
Lancement du conteneur
Docker run
-it
: commande pour que le conteneur soit interactif.--rm
: supprime le conteneur une fois qu'il est stoppé.-P
: publie tous les ports exposés dans le Dockerfile sur l'interface hôte--shm-size
: taille de /dev/shm, /dev/shm est l’implémentation d’un système de fichier temporaire. Il est monté comme un disque dur mais les données sont écrites en RAM. Le nom shm vient de SHared Memory car souvent utilisé pour l’échange de données entre process.
Remarque
Beaucoup d'autres options sont disponibles, n'hésitez pas à regarder la documentation sur les options de lancement.
Docker et OpenCV
La plupart du temps, lorsque l'on utilise OpenCV, on souhaite avoir un retour vidéo. Pour avoir ce retour, il faut que docker en ait les droits.
X server
X server est un système de fenêtrage pour les affichages bitmap, courant sur les systèmes d'exploitation linux. Il existe plusieurs façons de connecter un conteneur au X server d'un hôte pour l'afficher.
- La première est simple, mais non sécurisée.
- La deuxième est plus sûre, mais non isolée.
- La troisième est isolée, mais pas aussi portable.
Première méthode
Le moyen le plus simple est d'exposer votre xhost afin que le conteneur puisse effectuer le rendu sur l'affichage correct en lisant et en écrivant à travers le socket X11 unix.
Docker
On a ici fait plusieurs choses.
- On a rendu le conteneur interactif via la commande
-it
. - On a passé au contenur notre variable d'environnement "DISPLAY".
- Monter un volume pour la socket unix X11.
- Enregistrer l'id du conteneur.
Ca sera un échec :
Car même si on a monté le volume X11 et qu'on a passé au conteneur la variable d'environnement "DISPLAY", il n'a pas les droits par rapport au xhost sur notre machine. La façon la plus sécurisée de le faire consiste à ouvrir xhost uniquement au système spécifique que vous souhaitez, par exemple si vous exécutez un conteneur sur le démon docker de l'hôte local avec l'ID du conteneur stocké dans la variable shell containerId, vous pouvez utiliser la commande suivante.
Bash
Cela ajoutera le nom du conteneur à la liste des noms autorisés de la famille locale. De façon générale, pour donner accès à tous les conteneurs, il suffit de faire :
Deuxième méthode
Utilisateur sans nom
Une façon de faire est d'utiliser vos propres privilèges d'utilisateur pour accéder au display. Ce qui nécessite de monter un volume supplémentaire et de devenir "vous même" dans le conteneur, et plus l'utilisateur "admin".
Docker
Inconvénients
- Vous n'êtes pas nommé, vous êtes un utilisateur lambda et vous n'aurez aucun droit d'écriture dans le conteneur.
- Certaines applications nécessitent un répertoire
/home/
, comme vous n'avez pas de nom, vous n'en avez pas.
Vous identifier comme vous même
Loggez vous avec votre uid:gid
dans le conteneur et ajoutez d'autres volumes, vous pourrez ainsi utiliser votre compte de votre machine local dans votre conteneur.
Docker
L'avantage de cette méthode est que vous aurez un répertoire /home/
dans votre conteneur.
La méthode isolée
Il existe un autre moyen d'émuler la même technique avec la méthode précédente mais de manière plus isolée. Nous pouvons le faire avec quelques modifications à l'image originale en créant un utilisateur avec uid
et gid
correspondant à celui de l'utilisateur hôte. Ceci est un exemple de ce que vous pouvez avoir besoin d'ajouter au Dockerfile.
Docker
Vous pourriez avoir besoin de changer le nombre 1000 par votre uid
et gid
correspondante sur votre machine locale, mais la plupart du temps ces nombres correspondent. Pour les trouver, il suffit de lancer ces commandes unix.
Le suite est maintenant un peu plus compliquée. Il faut faire un fichier d'authentification X11 avec les bonnes permissions et de le monter dans volume que le conteneur va utiliser.
Docker
Maintenant, le conteneur est isolé avec seulement un accès en lecture et écriture à l'authentification X11 et au socket. L'inconvénient de tout cela est que certaines configurations spécifiques à l'utilisateur résident maintenant dans l'image elle-même, ce qui la rend moins portable. Si un autre utilisateur, même sur la même machine hôte, souhaite utiliser la même image, il devra : démarrer une session de terminal interactif avec le conteneur, changer l'uid
et le gid
pour qu'ils correspondent aux siens, livrer le conteneur à une nouvelle image, et lancer le conteneur désiré à partir de celle-ci. Faire ce va-et-vient ajoute également des couches inutiles à votre image.