Aller au contenu

Ma première application

Concepts

Le but ici est de lancer une application microservice web Jupyter Notebook en tâche de fond.

Application en arrière plan

Afin qu'un conteneur tourne en arrière plan il faut utiliser l'option -d (--detach) :

docker run [OPTIONS] -d <image>

Publication de ports

Un jupyter notebook étant une application web, il est nécessaire de relier un port réseau du conteneur à un port de la machine hôte. On parle alors de port mapping ou de publication de ports en français. En effet, lorsqu'un conteneur est créé, celui-ci n'expose par défaut aucun de ces ports au monde extérieur. En d'autres termes, si le contenur contient une application "réseau" (web, ssh, dns...), cette dernière n'est pas accessible depuis l'extérieur du conteneur. Il faut alors utiliser l'option -p ou (--publish) pour la rendre accessible à l'extérieur de Docker, tel que :

docker run [OPTIONS] -p [host_IP:]<port_on_host>:<port_inside_container>[/protocol] <image>

Voici, sous forme d'exemples, les explications de la commande précédente :

Options Explications
-p 8080:80 (ou -p 8080:80/tcp) Relie le port 8080 de la machine hôte au port 80 dans le conteneur en TCP (par défaut)
-p 8080:80/udp Relie le port 8080 de la machine hôte au port 80 dans le conteneur en UDP
-p 192.168.1.100:8080:80 Relie le port 8080 de la machine hôte avec l'IP 192.168.1.100 au port 80 dans le conteneur en TCP
-p 8080:80/tcp -p 8080:80/udp Relie le port 8080 de la machine hôte au port 80 dans le conteneur en TCP, et relie le port 8080 de la machine hôte au port 80 dans le conteneur en UDP

Remarques

  • L'option -p 8080:80 est équivalente à -p 0.0.0.0:8080:80, ce qui signifie que Docker va publier le port 80 du conteneur sur le port 8080 de toutes les interfaces réseau de la machine hôte;
  • La remarque précédente s'applique de la même manière à -p 8080:80/udp qui est équivalent à -p 0.0.0.0:8080:80/udp.

Danger

La publication d'un port d'un conteneur peut représenter un risque en terme de sécurité puisque celui-ci devient accessible depuis l'extérieur.

Pour des raisons de sécurité, notamment pour les ordinateurs portables connectés sur un réseau public, il est important de limiter les connexions à localhost (127.0.0.1) de façon à ce que seule la machine hôte puisse accéder au port publié. Par exemple :

docker run [OPTIONS] -p 127.0.0.1:8080:80 <image>

Application avec un Jupyter

  • Afin de réaliser cet exercice, téléchargez tout d'abord l'image jupyter/base-notebook basée sur Ubuntu (il existe d'autres images plus complète comme la jupyter/minimal-notebook ou la jupyter/scipy-notebook qui est beaucoup plus lourde) :

    docker pull jupyter/base-notebook:lab-4.0.7
    

    Remarque

    Si le tag (ici lab-4.0.7) est omis, l'image téléchargée sera celle avec le tag par défaut : latest ; dans ce cas, il n'est pas nécessaire de le spécifier dans la commande docker run.

  • Lancez maintenant le conteneur jupyter notebook en tache de fond, de façon à ce que le port 8888 (port sur lequel il écoute dans le conteneur) soit publié sur le port de votre choix (> 1023) sur la machine hôte;

  • Connectez-vous sur l'interface web du jupyter notebook.

    Note

    Pour la dernière question, vous aurez besoin de réaliser un tunnel ssh si vous réalisez ce tutoriel sur un serveur distant ayant la plus part de ses ports fermés. Pour cela, ouvrir un autre terminal et taper :

    ssh -L <port_choisi_local>:127.0.0.1:<port_choisi_vm> user@IP
    
    Cela vous permettra de vous connecter au notebook à l'adresse : http://127.0.0.1:<port_choisi>.

    Solution

    Le choix le plus "judicieux" (mais pas obligatoire) était de faire correspondre le port 8888 du Jupyter Notebook dans le conteneur au port 8888 sur la machine hôte:

    docker run -d --name myjupyter -p 127.0.0.1:8888:8888 jupyter/base-notebook:lab-4.0.7 &&\
    docker ps
    
    En adaptant le tunnel ssh en conséquence :
    ssh -N -f -L 8888:127.0.0.1:8888 user@IP
    
    nous pouvons nous connecter au Jupyter Notebook à l'adresse suivante: http://127.0.0.1:8888.

Une fois sur la page de connexion, il est nécessaire de rentrer un token d'identification afin de pouvoir se connecter au notebook. Il faut pour cela lire la sortie standarde (STDOUT) et la sortie d'erreur (STDERR) à l'aide de la commande docker logs:

docker logs <nom du conteneur>
docker logs -f <nom du conteneur> # -f (--follow) permet de suivre les logs en temps réels

  • Examinez les logs du conteneur afin de récupérer le token

    Solution
    docker logs myjupyter &&\
    docker logs myjupyter 2>&1 | grep "http://127.0.0.1:8888" | tail -n1
    

Connectez-vous maintenant au notebook en cliquant sur le lien donné par la dernière commande, ouvrez un terminal dans le Jupyter Notebook et essayez :

curl -L https://gitlab.in2p3.fr/ri3/ecole-info/2023/anf-conteneurs/docker/-/raw/main/files/python_codes/hello_world.py -o hello_world.py

  • La commande curl n'est pas installée. Installez la dans le conteneur en vous connectant en tant qu'utilisateur root dans le conteneur, à l'aide de :

    docker exec [OPTIONS] --user <username> <nom du conteneur>
    

    Solution

    En effet, l'utilisateur lancé par défaut dans le conteneur jupyter est jovyan. Pour pouvoir se connecter dans le conteneur en tant qu'un autre utilisateur (existant), il faut le préciser à l'aide de l'option -u (--user).

    user@host : docker exec -it --user root myjupyter bash
    (base) root@495f0be775ce:~# apt-get update
    (base) root@495f0be775ce:~# apt-get install -y curl
    

  • Exécutez les programmes python de l'exemple ci-dessus. Pour cela, allez sur la page web du notebook, ouvrez un terminal et réessayez la commande :

    (base) jovyan@495f0be775ce:~$ curl -L https://gitlab.in2p3.fr/ri3/ecole-info/2023/anf-conteneurs/docker/-/raw/main/files/python_codes/hello_world.py -o hello_world.py
    (base) jovyan@495f0be775ce:~$ curl -L https://gitlab.in2p3.fr/ri3/ecole-info/2023/anf-conteneurs/docker/-/raw/main/files/python_codes/pi.py -o pi.py
    (base) jovyan@495f0be775ce:~$ python hello_world.py
    (base) jovyan@495f0be775ce:~$ pip install numpy matplotlib
    (base) jovyan@495f0be775ce:~$ python pi.py
    

Plutôt que de devoir installer la commande curl dans le conteneur pour télécharger les deux codes python que nous avons executés, vous pouvez essayer de monter le répertoire les contenant dans le conteneur :

docker run -d --name myjupyter \
           -v python_codes/:/home/jovyan/work \
           -p 127.0.0.1:8888:8888 \
           jupyter/base-notebook

Remarque

Notez que le binding ne fonctionnera pas correctement si votre uid n'est pas 1000. Le conteneur notebook de jupyter supporte certaines options passées en tant que variables d'environnement via l'option -e. Par exemple, pour lancer un notebook avec l'ensemble du répertoire personnel monté à l'intérieur du conteneur :

docker run -d --restart always --name myjupyter --user root \
           -v $HOME:/home/<username> -p 127.0.0.1:8888:8888 \
           -e NB_USER=$USER -e NB_UID=$(id -u) \
           -e NB_GID=$(id -g) -e NB_GROUP=$(id -ng) \
           jupyter/base-notebook

Astuce

Pour une utilisation permanente, ajouter à votre fichier .bashrc :

 alias jupyter="docker start myjupyter && docker logs myjupyter 2>&1 | grep "http://127.0.0.1:8888" | tail -n1"

Rappelons que tout ce qui a été installé dans le conteneur (comme la commande curl, les modules python numpy et matplotlib) est perdu lorsque le conteneur est détruit. Afin de rendre les modifications pérennes, il faut intégrer les modifications directement dans l'image du conteneur.

Gérer le microservice

Le conteneur peut être désactivé avec :

docker container stop myjupyter

De la même manière, il peut être démarré (redémarré) en remplaçant stop par start (restart), mais le jeton d'identification sera renouvelé :

docker container start myjupyter
docker container restart myjupyter
Pour détruire le conteneur, si celui-ci est arrêté, il suffit de taper :
docker container rm myjupyter
Dans le cas contraire, on peut forcer sa suppression:
docker container rm -f myjupyter

Ou en utilisant la commande kill :

docker container kill myjupyter

En ajoutant l'option --restart always à la commande docker run, le conteneur est toujours redémarré s'il est arrêté. S'il est arrêté manuellement, il n'est redémarré que lorsque le démon Docker redémarre ou que le conteneur lui-même est redémarré manuellement. Cette option peut être intéressante pour une utilisation en production.

docker run -d --restart always \
           -v /some/path/on/host/:/home/jovyan/work \
           -p 127.0.0.1:8888:8888 \
           jupyter/base-notebook
Authors: Aurélien Bailly-Reyre